[ Index ] |
PHP Cross Reference of GlotPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Things: GP_Thing class 4 * 5 * @package GlotPress 6 * @subpackage Things 7 * @since 1.0.0 8 */ 9 10 /** 11 * Core base class extended to register things. 12 * 13 * @since 1.0.0 14 */ 15 class GP_Thing { 16 17 var $field_names = array(); 18 var $non_db_field_names = array(); 19 var $int_fields = array(); 20 var $validation_rules = null; 21 var $per_page = 30; 22 var $map_results = true; 23 var $static = array(); 24 25 public $class; 26 public $table_basename; 27 public $id; 28 public $non_updatable_attributes; 29 public $default_conditions; 30 public $table = null; 31 public $errors = array(); 32 33 static $static_by_class = array(); 34 static $validation_rules_by_class = array(); 35 36 public function __construct( $fields = array() ) { 37 global $wpdb; 38 $this->class = get_class( $this ); 39 $this->table = $wpdb->{$this->table_basename}; 40 foreach ( $this->field_names as $field_name ) { 41 $this->$field_name = null; 42 } 43 $this->set_fields( $fields ); 44 45 if ( isset( self::$validation_rules_by_class[ $this->class ] ) ) { 46 $this->validation_rules = &self::$validation_rules_by_class[ $this->class ]; 47 } else { 48 $this->validation_rules = new GP_Validation_Rules( array_merge( $this->field_names, $this->non_db_field_names ) ); 49 // we give the rules as a parameter here solely as a syntax sugar 50 $this->restrict_fields( $this->validation_rules ); 51 self::$validation_rules_by_class[ $this->class ] = &$this->validation_rules; 52 } 53 if ( ! $this->get_static( 'static-vars-are-set' ) ) { 54 foreach ( get_class_vars( $this->class ) as $name => $value ) { 55 $this->set_static( $name, $value ); 56 } 57 $this->set_static( 'static-vars-are-set', true ); 58 } 59 } 60 61 public function get_static( $name, $default = null ) { 62 return isset( self::$static_by_class[ $this->class ][ $name ] ) ? self::$static_by_class[ $this->class ][ $name ] : $default; 63 } 64 65 public function has_static( $name ) { 66 return isset( self::$static_by_class[ $this->class ][ $name ] ); 67 } 68 69 public function set_static( $name, $value ) { 70 self::$static_by_class[ $this->class ][ $name ] = $value; 71 } 72 73 // CRUD 74 75 /** 76 * Retrieves all rows from this table 77 */ 78 public function all( $order = null ) { 79 return $this->many( $this->select_all_from_conditions_and_order( array(), $order ) ); 80 } 81 82 /** 83 * Reloads the object data from the database, based on its id 84 * 85 * @return GP_Thing Thing object. 86 */ 87 public function reload() { 88 global $wpdb; 89 90 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Verified table name. 91 $fields = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table WHERE id = %d", $this->id ) ); 92 $this->set_fields( $fields ); 93 94 return $this; 95 } 96 97 /** 98 * Retrieves one row from the database. 99 * 100 * @since 1.0.0 101 * @since 3.0.0 Added spread operator and require `$query` argument to be set. 102 * 103 * @see wpdb::get_row() 104 * @see wpdb::prepare() 105 * 106 * @param string $query Query statement with optional sprintf()-like placeholders. 107 * @param mixed ...$args Optional arguments to pass to the GP_Thing::prepare() function. 108 * @return GP_Thing|false Thing object on success, false on failure. 109 */ 110 public function one( $query, ...$args ) { 111 global $wpdb; 112 113 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared 114 return $this->coerce( $wpdb->get_row( $this->prepare( $query, ...$args ) ) ); 115 } 116 117 /** 118 * Retrieves one variable from the database. 119 * 120 * @since 1.0.0 121 * @since 3.0.0 Added spread operator and require `$query` argument to be set. 122 * 123 * @see wpdb::get_var() 124 * @see wpdb::prepare() 125 * 126 * @param string $query Query statement with optional sprintf()-like placeholders. 127 * @param mixed ...$args Optional arguments to pass to the GP_Thing::prepare() function. 128 * @return string|null Database query result (as string), or false on failure. 129 */ 130 public function value( $query, ...$args ) { 131 global $wpdb; 132 133 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared 134 $res = $wpdb->get_var( $this->prepare( $query, ...$args ) ); 135 136 return null === $res ? false : $res; 137 } 138 139 /** 140 * Prepares a SQL query for safe execution. Uses sprintf()-like syntax. 141 * 142 * @since 1.0.0 143 * @since 3.0.0 Added spread operator and require `$query` argument to be set. 144 * 145 * @see wpdb::prepare() 146 * 147 * @param string $query Query statement with optional sprintf()-like placeholders. 148 * @param mixed ...$args Optional arguments to pass to the GP_Thing::prepare() function. 149 * @return string Sanitized query string, if there is a query to prepare. 150 */ 151 public function prepare( $query, ...$args ) { 152 global $wpdb; 153 154 if ( ! $args ) { 155 return $query; 156 } 157 158 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared 159 return $wpdb->prepare( $query, ...$args ); 160 } 161 162 163 /** 164 * Retrieves an entire result set from the database, mapped to GP_Thing. 165 * 166 * @since 1.0.0 167 * @since 3.0.0 Added spread operator and require `$query` argument to be set. 168 * 169 * @see wpdb::get_results() 170 * @see wpdb::prepare() 171 * 172 * @param string $query Query statement with optional sprintf()-like placeholders. 173 * @param mixed ...$args Optional arguments to pass to the GP_Thing::prepare() function. 174 * @return GP_Thing[] A list of GP_Thing objects. 175 */ 176 public function many( $query, ...$args ) { 177 global $wpdb; 178 179 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared 180 return $this->map( $wpdb->get_results( $this->prepare( $query, ...$args ) ) ); 181 } 182 183 /** 184 * Retrieves an entire result set from the database. 185 * 186 * @since 1.0.0 187 * @since 3.0.0 Added spread operator and require `$query` argument to be set. 188 * 189 * @see wpdb::get_results() 190 * @see wpdb::prepare() 191 * 192 * @param string $query Query statement with optional sprintf()-like placeholders. 193 * @param mixed ...$args Optional arguments to pass to the GP_Thing::prepare() function. 194 * @return object[] Database query results. 195 */ 196 public function many_no_map( $query, ...$args ) { 197 array_unshift( $args, $query ); 198 return $this->_no_map( 'many', $args ); 199 } 200 201 /** 202 * [find_many description] 203 * 204 * @since 1.0.0 205 * 206 * @param string|array $conditions 207 * @param string|array $order Optional. 208 * @return mixed 209 */ 210 public function find_many( $conditions, $order = null ) { 211 return $this->many( $this->select_all_from_conditions_and_order( $conditions, $order ) ); 212 } 213 214 /** 215 * [find_many_no_map description] 216 * 217 * @since 1.0.0 218 * 219 * @param string|array $conditions 220 * @param string|array $order Optional. 221 * @return mixed 222 */ 223 public function find_many_no_map( $conditions, $order = null ) { 224 return $this->_no_map( 'find_many', array( $conditions, $order ) ); 225 } 226 227 /** 228 * [find_one description] 229 * 230 * @since 1.0.0 231 * 232 * @param string|array $conditions 233 * @param string|array $order Optional. 234 * @return mixed 235 */ 236 public function find_one( $conditions, $order = null ) { 237 return $this->one( $this->select_all_from_conditions_and_order( $conditions, $order ) ); 238 } 239 240 /** 241 * [find description] 242 * 243 * @since 1.0.0 244 * 245 * @param string|array $conditions 246 * @param string|array $order Optional. 247 * @return mixed 248 */ 249 public function find( $conditions, $order = null ) { 250 return $this->find_many( $conditions, $order ); 251 } 252 253 /** 254 * [find_no_map description] 255 * 256 * @since 1.0.0 257 * 258 * @param string|array $conditions 259 * @param string|array $order Optional. 260 * @return mixed 261 */ 262 public function find_no_map( $conditions, $order = null ) { 263 return $this->_no_map( 'find', array( $conditions, $order ) ); 264 } 265 266 /** 267 * [_no_map description] 268 * 269 * @since 1.0.0 270 * 271 * @param string $name Method name. 272 * @param mixed $args Method-dependent arguments. 273 * @return mixed 274 */ 275 private function _no_map( $name, $args ) { 276 $this->map_results = false; 277 $result = $this->$name( ...$args ); 278 $this->map_results = true; 279 280 return $result; 281 } 282 283 /** 284 * [map_no_map description] 285 * 286 * @since 1.0.0 287 * 288 * @param mixed $results The results, unmapped. 289 * @return mixed 290 */ 291 public function map_no_map( $results ) { 292 return $this->_no_map( 'map', $results ); 293 } 294 295 /** 296 * Maps database results to their GP_Thing presentations. 297 * 298 * @since 1.0.0 299 * 300 * @param mixed $results The results from the database. 301 * @return GP_Thing[]|object[] If enabled, a list of objects mapped to GP_Thing. 302 */ 303 public function map( $results ) { 304 if ( isset( $this->map_results ) && ! $this->map_results ) { 305 return $results; 306 } 307 308 if ( ! $results || ! is_array( $results ) ) { 309 $results = array(); 310 } 311 312 $mapped = array(); 313 foreach ( $results as $result ) { 314 $mapped[] = $this->coerce( $result ); 315 } 316 317 return $mapped; 318 } 319 320 /** 321 * Performs a database query. 322 * 323 * @since 1.0.0 324 * @since 3.0.0 Added spread operator and require `$query` argument to be set. 325 * 326 * @see wpdb::query() 327 * @see wpdb::prepare() 328 * 329 * @param string $query Database query. 330 * @param mixed ...$args Optional arguments to pass to the prepare method. 331 * @return int|bool Number of rows affected/selected or false on error. 332 */ 333 public function query( $query, ...$args ) { 334 global $wpdb; 335 336 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Custom prepare function. 337 return $wpdb->query( $this->prepare( $query, ...$args ) ); 338 } 339 340 /** 341 * Inserts a new row 342 * 343 * @param $args array associative array with fields as keys and values as values 344 * @return mixed the object corresponding to the inserted row or false on error 345 */ 346 public function create( $args ) { 347 global $wpdb; 348 $args = $this->prepare_fields_for_save( $args ); 349 $args = $this->prepare_fields_for_create( $args ); 350 $field_formats = $this->get_db_field_formats( $args ); 351 $res = $wpdb->insert( $this->table, $args, $field_formats ); 352 if ( false === $res ) { 353 return false; 354 } 355 $class = $this->class; 356 $inserted = new $class( $args ); 357 $inserted->id = $wpdb->insert_id; 358 $inserted->after_create(); 359 return $inserted; 360 } 361 362 /** 363 * Inserts a record and then selects it back based on the id 364 * 365 * @param $args array see create() 366 * @return mixed the selected object or false on error 367 */ 368 public function create_and_select( $args ) { 369 $created = $this->create( $args ); 370 if ( ! $created ) { 371 return false; 372 } 373 $created->reload(); 374 return $created; 375 } 376 377 /** 378 * Updates a single row 379 * 380 * @param $data array associative array with fields as keys and updated values as values 381 */ 382 public function update( $data, $where = null ) { 383 global $wpdb; 384 if ( ! $data ) { 385 return false; 386 } 387 $where = is_null( $where ) ? array( 'id' => $this->id ) : $where; 388 $fields_for_save = $this->prepare_fields_for_save( $data ); 389 if ( is_array( $fields_for_save ) && empty( $fields_for_save ) ) { 390 return true; 391 } 392 393 $field_formats = $this->get_db_field_formats( $fields_for_save ); 394 $where_formats = $this->get_db_field_formats( $where ); 395 396 return ! is_null( $wpdb->update( $this->table, $fields_for_save, $where, $field_formats, $where_formats ) ); 397 } 398 399 /** 400 * Retrieves an existing thing. 401 * 402 * @since 1.0.0 403 * 404 * @param GP_Thing|int $thing_or_id ID of a thing or GP_Thing object. 405 * @return GP_Thing|false Thing object on success, false on failure. 406 */ 407 public function get( $thing_or_id ) { 408 if ( ! $thing_or_id ) { 409 return false; 410 } 411 412 $id = is_object( $thing_or_id ) ? $thing_or_id->id : $thing_or_id; 413 return $this->find_one( array( 'id' => $id ) ); 414 } 415 416 /** 417 * Saves an existing thing. 418 * 419 * @since 1.0.0 420 * 421 * @param mixed $args Values to update. 422 * @return bool|null Null and false on failure, true on success. 423 */ 424 public function save( $args = null ) { 425 $thing_before = clone $this; 426 427 if ( is_null( $args ) ) { 428 $args = get_object_vars( $this ); 429 } 430 431 if ( ! is_array( $args ) ) { 432 $args = (array) $args; 433 } 434 435 $args = $this->prepare_fields_for_save( $args ); 436 437 $update_res = $this->update( $args ); 438 439 $this->set_fields( $args ); 440 441 if ( ! $update_res ) { 442 return null; 443 } 444 445 $update_res = $this->after_save( $thing_before ); 446 447 return $update_res; 448 } 449 450 /** 451 * Deletes a single row 452 * 453 * @since 1.0.0 454 */ 455 public function delete() { 456 return $this->delete_all( array( 'id' => $this->id ) ); 457 } 458 459 /** 460 * Deletes all or multiple rows 461 * 462 * @since 1.0.0 463 * 464 * @param array $where An array of conditions to use to for a SQL "where" clause, if null, not used and all matching rows will be deleted. 465 */ 466 public function delete_all( $where = null ) { 467 $query = "DELETE FROM $this->table"; 468 $conditions_sql = $this->sql_from_conditions( $where ); 469 if ( $conditions_sql ) { 470 $query .= " WHERE $conditions_sql"; 471 } 472 $result = $this->query( $query ); 473 $this->after_delete(); 474 return $result; 475 } 476 477 /** 478 * Deletes multiple rows 479 * 480 * @since 2.0.0 481 * 482 * @param array $where An array of conditions to use to for a SQL "where" clause, if not passed, no rows will be deleted. 483 */ 484 public function delete_many( array $where ) { 485 if ( empty( $where ) ) { 486 return false; 487 } 488 489 return $this->delete_all( $where ); 490 } 491 492 /** 493 * Sets fields of the current GP_Thing object. 494 * 495 * @param array $fields Fields for a GP_Thing object. 496 */ 497 public function set_fields( $fields ) { 498 $fields = (array) $fields; 499 $fields = $this->normalize_fields( $fields ); 500 foreach ( $fields as $key => $value ) { 501 $this->$key = $value; 502 } 503 } 504 505 /** 506 * Normalizes an array with key-value pairs representing 507 * a GP_Thing object. 508 * 509 * @todo Include default type handling. For example dates 0000-00-00 should be set to null 510 * 511 * @since 1.0.0 512 * @since 3.0.0 Normalizes int fields to be integers. 513 * 514 * @param array $args Arguments for a GP_Thing object. 515 * @return array Normalized arguments for a GP_Thing object. 516 */ 517 public function normalize_fields( $args ) { 518 foreach ( $this->int_fields as $int_field ) { 519 if ( isset( $args[ $int_field ] ) ) { 520 $args[ $int_field ] = (int) $args[ $int_field ]; 521 } 522 } 523 524 return $args; 525 } 526 527 /** 528 * Prepares for enetering the database an array with 529 * key-value pairs, preresenting a GP_Thing object. 530 */ 531 public function prepare_fields_for_save( $args ) { 532 $args = (array) $args; 533 $args = $this->normalize_fields( $args ); 534 unset( $args['id'] ); 535 foreach ( $this->non_updatable_attributes as $attribute ) { 536 unset( $args[ $attribute ] ); 537 } 538 foreach ( $args as $key => $value ) { 539 if ( ! in_array( $key, $this->field_names, true ) ) { 540 unset( $args[ $key ] ); 541 } 542 } 543 544 if ( in_array( 'date_modified', $this->field_names, true ) ) { 545 $args['date_modified'] = $this->now_in_mysql_format(); 546 } 547 548 return $args; 549 } 550 551 public function now_in_mysql_format() { 552 $now = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); 553 return $now->format( DATE_MYSQL ); 554 } 555 556 public function prepare_fields_for_create( $args ) { 557 if ( in_array( 'date_added', $this->field_names, true ) ) { 558 $args['date_added'] = $this->now_in_mysql_format(); 559 } 560 return $args; 561 } 562 563 public function get_db_field_formats( $args ) { 564 $formats = array_fill_keys( array_keys( $args ), '%s' ); 565 return array_merge( $formats, array_fill_keys( $this->int_fields, '%d' ) ); 566 } 567 568 /** 569 * Coerces data to being a thing object. 570 * 571 * @since 1.0.0 572 * 573 * @param array|object $thing Data about the thing retrieved from the database. 574 * @return GP_Thing|false Thing object on success, false on failure. 575 */ 576 public function coerce( $thing ) { 577 if ( ! $thing || is_wp_error( $thing ) ) { 578 return false; 579 } else { 580 $class = $this->class; 581 return new $class( $thing ); 582 } 583 } 584 585 // Triggers 586 587 /** 588 * Is called after an object is created in the database. 589 * 590 * This is a placeholder function which should be implemented in the child classes. 591 * 592 * @return bool 593 */ 594 public function after_create() { 595 return true; 596 } 597 598 /** 599 * Is called after an object is saved to the database. 600 * 601 * This is a placeholder function which should be implemented in the child classes. 602 * 603 * @param GP_Thing $thing_before Object before the update. 604 * @return bool 605 */ 606 public function after_save( $thing_before ) { 607 return true; 608 } 609 610 /** 611 * Is called after an object is deleted from the database. 612 * 613 * This is a placeholder function which should be implemented in the child classes. 614 * 615 * @return bool 616 */ 617 public function after_delete() { 618 return true; 619 } 620 621 /** 622 * Builds SQL conditions from a PHP value. 623 * 624 * Examples: 625 * Input: `null` 626 * Output: `IS NULL` 627 * 628 * Input: `'foo'` 629 * Output: `= 'foo'` 630 * 631 * Input: `1` or `'1'` 632 * Output: `= 1` 633 * 634 * @since 1.0.0 635 * 636 * @param mixed $php_value The PHP value to convert to conditions. 637 * @return string SQL conditions. 638 */ 639 public function sql_condition_from_php_value( $php_value ) { 640 if ( is_array( $php_value ) ) { 641 return array_map( array( &$this, 'sql_condition_from_php_value' ), $php_value ); 642 } 643 $operator = '='; 644 if ( is_integer( $php_value ) || ctype_digit( $php_value ) ) { 645 $sql_value = $php_value; 646 } else { 647 $sql_value = "'" . esc_sql( $php_value ) . "'"; 648 } 649 if ( is_null( $php_value ) ) { 650 $operator = 'IS'; 651 $sql_value = 'NULL'; 652 } 653 return "$operator $sql_value"; 654 } 655 656 public function sql_from_conditions( $conditions ) { 657 if ( is_string( $conditions ) ) { 658 $conditions; 659 } elseif ( is_array( $conditions ) ) { 660 $conditions = array_map( array( &$this, 'sql_condition_from_php_value' ), $conditions ); 661 $string_conditions = array(); 662 663 foreach ( $conditions as $field => $sql_condition ) { 664 if ( is_array( $sql_condition ) ) { 665 $string_conditions[] = '(' . implode( 666 ' OR ', 667 array_map( 668 function( $cond ) use ( $field ) { 669 return "$field $cond"; 670 }, 671 $sql_condition 672 ) 673 ) . ')'; 674 } else { 675 $string_conditions[] = "$field $sql_condition"; 676 } 677 } 678 679 $conditions = implode( ' AND ', $string_conditions ); 680 } 681 return $this->apply_default_conditions( $conditions ); 682 } 683 684 public function sql_from_order( $order_by, $order_how = '' ) { 685 if ( is_array( $order_by ) ) { 686 $order_by = implode( ' ', $order_by ); 687 $order_how = ''; 688 } 689 $order_by = trim( $order_by ); 690 if ( ! $order_by ) { 691 return gp_member_get( $this, 'default_order' ); 692 } 693 return 'ORDER BY ' . $order_by . ( $order_how ? " $order_how" : '' ); 694 } 695 696 public function select_all_from_conditions_and_order( $conditions, $order = null ) { 697 $query = "SELECT * FROM $this->table"; 698 $conditions_sql = $this->sql_from_conditions( $conditions ); 699 if ( $conditions_sql ) { 700 $query .= " WHERE $conditions_sql"; 701 } 702 $order_sql = $this->sql_from_order( $order ); 703 if ( $order_sql ) { 704 $query .= " $order_sql"; 705 } 706 return $query; 707 } 708 709 /** 710 * Sets restriction rules for fields. 711 * 712 * @since 1.0.0 713 * 714 * @param GP_Validation_Rules $rules The validation rules instance. 715 */ 716 public function restrict_fields( $rules ) { 717 // Don't restrict any fields by default. 718 } 719 720 public function validate() { 721 $verdict = $this->validation_rules->run( $this ); 722 $this->errors = $this->validation_rules->errors; 723 return $verdict; 724 } 725 726 public function force_false_to_null( $value ) { 727 return $value ? $value : null; 728 } 729 730 public function fields() { 731 $result = array(); 732 foreach ( array_merge( $this->field_names, $this->non_db_field_names ) as $field_name ) { 733 if ( isset( $this->$field_name ) ) { 734 $result[ $field_name ] = $this->$field_name; 735 } 736 } 737 return $result; 738 } 739 740 public function sql_limit_for_paging( $page, $per_page = null ) { 741 $per_page = is_null( $per_page ) ? $this->per_page : $per_page; 742 if ( 'no-limit' == $per_page || 'no-limit' == $page ) { 743 return ''; 744 } 745 $page = intval( $page ) ? intval( $page ) : 1; 746 return sprintf( 'LIMIT %d OFFSET %d', $per_page, ( $page - 1 ) * $per_page ); 747 } 748 749 public function found_rows() { 750 global $wpdb; 751 return $wpdb->get_var( 'SELECT FOUND_ROWS();' ); 752 } 753 754 public function like_escape_printf( $s ) { 755 global $wpdb; 756 return str_replace( '%', '%%', $wpdb->esc_like( $s ) ); 757 } 758 759 public function apply_default_conditions( $conditions_str ) { 760 $conditions = array(); 761 if ( isset( $this->default_conditions ) ) { 762 $conditions[] = $this->default_conditions; 763 } 764 if ( $conditions_str ) { 765 $conditions[] = $conditions_str; 766 } 767 return implode( ' AND ', $conditions ); 768 } 769 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sat Nov 23 01:01:06 2024 | Cross-referenced by PHPXref 0.7.1 |