cli
15 hours ago
fields
15 hours ago
widgets
15 hours ago
Pods.php
15 hours ago
PodsAPI.php
15 hours ago
PodsAdmin.php
15 hours ago
PodsArray.php
15 hours ago
PodsComponent.php
15 hours ago
PodsComponents.php
15 hours ago
PodsData.php
15 hours ago
PodsField.php
15 hours ago
PodsForm.php
15 hours ago
PodsI18n.php
15 hours ago
PodsInit.php
15 hours ago
PodsMeta.php
15 hours ago
PodsMigrate.php
15 hours ago
PodsRESTFields.php
15 hours ago
PodsRESTHandlers.php
15 hours ago
PodsTermSplitting.php
15 hours ago
PodsUI.php
15 hours ago
PodsView.php
15 hours ago
PodsTermSplitting.php
506 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * @package Pods |
| 5 | */ |
| 6 | class Pods_Term_Splitting { |
| 7 | |
| 8 | /** @var int ID of the formerly shared term */ |
| 9 | private $term_id; |
| 10 | |
| 11 | /** @var int ID of the new term created for the $term_taxonomy_id */ |
| 12 | private $new_term_id; |
| 13 | |
| 14 | /** @var string Taxonomy for the split term */ |
| 15 | private $taxonomy; |
| 16 | |
| 17 | /** @var string */ |
| 18 | private $progress_option_name; |
| 19 | |
| 20 | /** @var array */ |
| 21 | private $previous_progress = []; |
| 22 | |
| 23 | /** |
| 24 | * @param int $term_id ID of the formerly shared term. |
| 25 | * @param int $new_term_id ID of the new term created for the $term_taxonomy_id. |
| 26 | * @param string $taxonomy Taxonomy for the split term. |
| 27 | */ |
| 28 | public function __construct( $term_id, $new_term_id, $taxonomy ) { |
| 29 | $this->term_id = $term_id; |
| 30 | $this->new_term_id = $new_term_id; |
| 31 | $this->taxonomy = $taxonomy; |
| 32 | |
| 33 | $this->progress_option_name = "_pods_term_split_{$term_id}_{$taxonomy}"; |
| 34 | } |
| 35 | |
| 36 | /** |
| 37 | * |
| 38 | */ |
| 39 | public function split_shared_term() { |
| 40 | // Stash any previous progress |
| 41 | $this->previous_progress = $this->get_progress(); |
| 42 | |
| 43 | if ( empty( $this->previous_progress ) ) { |
| 44 | $this->append_progress( 'started' ); |
| 45 | $this->append_progress( "new term ID: {$this->new_term_id}" ); |
| 46 | } |
| 47 | |
| 48 | // Get the Pod information if the taxonomy is a Pod |
| 49 | $taxonomy_pod = $this->get_pod_info(); |
| 50 | |
| 51 | // Is the taxonomy a Pod? |
| 52 | if ( is_array( $taxonomy_pod ) || $taxonomy_pod instanceof Pods\Whatsit ) { |
| 53 | $this->update_podsrel_taxonomy( $taxonomy_pod['id'] ); |
| 54 | |
| 55 | // Update the Pods table if the taxonomy is a table based Pod |
| 56 | if ( 'table' === $taxonomy_pod['storage'] ) { |
| 57 | $this->update_pod_table( $taxonomy_pod['pod_table'] ); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | // Track down all fields related to the target taxonomy and update stored term IDs as necessary |
| 62 | $this->update_relationships_to_term(); |
| 63 | |
| 64 | // Clean up |
| 65 | $this->delete_progress(); |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * Return the Pod information for the specified taxonomy, or null if the taxonomy isn't a Pod |
| 70 | * |
| 71 | * @return array|bool|mixed|null |
| 72 | */ |
| 73 | private function get_pod_info() { |
| 74 | $pod_info = null; |
| 75 | |
| 76 | try { |
| 77 | $api = pods_api(); |
| 78 | |
| 79 | if ( $api->pod_exists( [ 'name' => $this->taxonomy ] ) ) { |
| 80 | // Load the taxonomy Pod |
| 81 | $params = [ |
| 82 | 'name' => $this->taxonomy, |
| 83 | ]; |
| 84 | |
| 85 | $pod_info = $api->load_pod( $params, false ); |
| 86 | } |
| 87 | } catch ( Exception $exception ) { |
| 88 | pods_debug_log( $exception ); |
| 89 | } |
| 90 | |
| 91 | return $pod_info; |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * @param int $pod_id |
| 96 | */ |
| 97 | private function update_podsrel_taxonomy( $pod_id ) { |
| 98 | /** @var wpdb $wpdb */ |
| 99 | global $wpdb; |
| 100 | |
| 101 | $task = "update_podsrel_taxonomy_{$pod_id}"; |
| 102 | |
| 103 | if ( ! $this->have_done( $task ) ) { |
| 104 | if ( pods_podsrel_enabled( null, __METHOD__ ) ) { |
| 105 | // UPDATE {$wpdb->prefix}podsrel SET item_id = {$new_term_id} WHERE pod_id = {$pod_id} AND item_id = {$term_id} |
| 106 | $table = "{$wpdb->prefix}podsrel"; |
| 107 | |
| 108 | $data = [ |
| 109 | 'item_id' => $this->new_term_id, |
| 110 | ]; |
| 111 | |
| 112 | $where = [ |
| 113 | 'pod_id' => $pod_id, |
| 114 | 'item_id' => $this->term_id, |
| 115 | ]; |
| 116 | |
| 117 | $format = '%d'; |
| 118 | $where_format = '%d'; |
| 119 | |
| 120 | $wpdb->update( $table, $data, $where, $format, $where_format ); |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Allow hooking into the term splitting process for taxonomy. |
| 125 | * |
| 126 | * @since 2.8.0 |
| 127 | * |
| 128 | * @param int $pod_id The pod ID for the taxonomy. |
| 129 | * @param int $term_id The current term ID being split. |
| 130 | * @param int $new_term_id The new term ID. |
| 131 | * @param string $task The task being done. |
| 132 | */ |
| 133 | do_action( 'pods_term_splitting_update_taxonomy', $pod_id, $this->term_id, $this->new_term_id, $task ); |
| 134 | |
| 135 | $this->append_progress( $task ); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | /** |
| 140 | * @param string $pod_table |
| 141 | */ |
| 142 | private function update_pod_table( $pod_table ) { |
| 143 | /** @var wpdb $wpdb */ |
| 144 | global $wpdb; |
| 145 | |
| 146 | $task = "update_pod_table_{$pod_table}"; |
| 147 | |
| 148 | if ( ! $this->have_done( $task ) ) { |
| 149 | // Prime the values and update |
| 150 | $data = [ |
| 151 | 'id' => $this->new_term_id, |
| 152 | ]; |
| 153 | |
| 154 | $where = [ |
| 155 | 'id' => $this->term_id, |
| 156 | ]; |
| 157 | |
| 158 | $format = '%d'; |
| 159 | $where_format = '%d'; |
| 160 | |
| 161 | $wpdb->update( $pod_table, $data, $where, $format, $where_format ); |
| 162 | |
| 163 | $this->append_progress( $task ); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | /** |
| 168 | * Track down all fields related to the target taxonomy and update stored term IDs as necessary |
| 169 | */ |
| 170 | private function update_relationships_to_term() { |
| 171 | // Loop through all Pods |
| 172 | try { |
| 173 | $all_pods = pods_api()->load_pods(); |
| 174 | } catch ( Exception $exception ) { |
| 175 | pods_debug_log( $exception ); |
| 176 | |
| 177 | return; |
| 178 | } |
| 179 | |
| 180 | if ( ! is_array( $all_pods ) ) { |
| 181 | return; |
| 182 | } |
| 183 | |
| 184 | foreach ( $all_pods as $this_pod_id => $this_pod ) { |
| 185 | // Loop through all fields in this Pod |
| 186 | foreach ( $this_pod['fields'] as $this_field_name => $this_field ) { |
| 187 | // Ignore everything except relationship fields to this taxonomy |
| 188 | if ( 'pick' !== $this_field['type'] || 'taxonomy' !== $this_field['pick_object'] || $this->taxonomy !== $this_field['pick_val'] ) { |
| 189 | continue; |
| 190 | } |
| 191 | |
| 192 | // Update the term ID in podsrel everywhere it is the value for this field |
| 193 | $this->update_podsrel_related_term( $this_field['id'] ); |
| 194 | |
| 195 | // Fix-up any special-case relationships that store term IDs in their own meta table and/or serialized |
| 196 | switch ( $this_pod['type'] ) { |
| 197 | case 'post_type': |
| 198 | $this->update_postmeta( $this_pod['name'], $this_field_name ); |
| 199 | break; |
| 200 | |
| 201 | case 'comment': |
| 202 | $this->update_commentmeta( $this_field_name ); |
| 203 | break; |
| 204 | |
| 205 | case 'user': |
| 206 | $this->update_usermeta( $this_field_name ); |
| 207 | break; |
| 208 | |
| 209 | case 'settings': |
| 210 | $this->update_setting_meta( $this_pod['name'], $this_field_name ); |
| 211 | break; |
| 212 | } |
| 213 | }//end foreach |
| 214 | }//end foreach |
| 215 | |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * @param int $field_id |
| 220 | */ |
| 221 | private function update_podsrel_related_term( $field_id ) { |
| 222 | /** @var wpdb $wpdb */ |
| 223 | global $wpdb; |
| 224 | |
| 225 | $task = "update_podsrel_related_term_{$field_id}"; |
| 226 | |
| 227 | if ( ! $this->have_done( $task ) ) { |
| 228 | if ( pods_podsrel_enabled( null, __METHOD__ ) ) { |
| 229 | // UPDATE {$wpdb->prefix}podsrel SET related_item_id = {$new_term_id} WHERE field_id = {$field_id} AND related_item_id = {$term_id} |
| 230 | $table = "{$wpdb->prefix}podsrel"; |
| 231 | |
| 232 | $data = [ |
| 233 | 'related_item_id' => $this->new_term_id, |
| 234 | ]; |
| 235 | |
| 236 | $where = [ |
| 237 | 'field_id' => $field_id, |
| 238 | 'related_item_id' => $this->term_id, |
| 239 | ]; |
| 240 | |
| 241 | $format = '%d'; |
| 242 | $where_format = '%d'; |
| 243 | |
| 244 | $wpdb->update( $table, $data, $where, $format, $where_format ); |
| 245 | } |
| 246 | |
| 247 | /** |
| 248 | * Allow hooking into the term splitting process for taxonomy by related term. |
| 249 | * |
| 250 | * @since 2.8.0 |
| 251 | * |
| 252 | * @param int $field_id The field ID for the relationship. |
| 253 | * @param int $term_id The current term ID being split. |
| 254 | * @param int $new_term_id The new term ID. |
| 255 | * @param string $task The task being done. |
| 256 | */ |
| 257 | do_action( 'pods_term_splitting_update_related_term', $field_id, $this->term_id, $this->new_term_id, $task ); |
| 258 | |
| 259 | $this->append_progress( $task ); |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | /** |
| 264 | * Called for all fields related to the target taxonomy that are in a post_type |
| 265 | * |
| 266 | * @param string $pod_name |
| 267 | * @param string $field_name |
| 268 | */ |
| 269 | private function update_postmeta( $pod_name, $field_name ) { |
| 270 | /** @var wpdb $wpdb */ |
| 271 | global $wpdb; |
| 272 | |
| 273 | // Fix up the unserialized data |
| 274 | $task = "update_postmeta_{$pod_name}_{$field_name}_unserialized"; |
| 275 | if ( ! $this->have_done( $task ) ) { |
| 276 | $wpdb->query( $wpdb->prepare( " |
| 277 | UPDATE |
| 278 | {$wpdb->postmeta} AS meta |
| 279 | LEFT JOIN {$wpdb->posts} AS t |
| 280 | ON meta.post_id = t.ID |
| 281 | SET |
| 282 | meta_value = %s |
| 283 | WHERE |
| 284 | meta_key = %s |
| 285 | AND meta_value = %s |
| 286 | AND t.post_type = %s |
| 287 | ", $this->new_term_id, $field_name, $this->term_id, $pod_name ) ); |
| 288 | |
| 289 | $this->append_progress( $task ); |
| 290 | }//end if |
| 291 | |
| 292 | // Fix up the serialized data |
| 293 | $task = "update_postmeta_{$pod_name}_{$field_name}_serialized"; |
| 294 | if ( ! $this->have_done( $task ) ) { |
| 295 | $meta_key = sprintf( '_pods_%s', $field_name ); |
| 296 | $target_serialized = sprintf( ';i:%s;', $this->term_id ); |
| 297 | $replace_serialized = sprintf( ';i:%s;', $this->new_term_id ); |
| 298 | |
| 299 | $wpdb->query( $wpdb->prepare( " |
| 300 | UPDATE |
| 301 | {$wpdb->postmeta} AS meta |
| 302 | LEFT JOIN {$wpdb->posts} AS t |
| 303 | ON meta.post_id = t.ID |
| 304 | SET |
| 305 | meta.meta_value = REPLACE( meta.meta_value, %s, %s ) |
| 306 | WHERE |
| 307 | meta.meta_key = %s |
| 308 | AND t.post_type = %s |
| 309 | AND meta_value LIKE %s |
| 310 | ", $target_serialized, $replace_serialized, $meta_key, $pod_name, '%' . pods_sanitize_like( $target_serialized ) . '%' ) ); |
| 311 | |
| 312 | $this->append_progress( $task ); |
| 313 | }//end if |
| 314 | |
| 315 | } |
| 316 | |
| 317 | /** |
| 318 | * Called for all fields related to the target taxonomy that are in a comment Pod |
| 319 | * |
| 320 | * @param string $field_name |
| 321 | */ |
| 322 | private function update_commentmeta( $field_name ) { |
| 323 | /** @var wpdb $wpdb */ |
| 324 | global $wpdb; |
| 325 | |
| 326 | // Fix up the unserialized data |
| 327 | $task = "update_commentmeta_{$field_name}_unserialized"; |
| 328 | if ( ! $this->have_done( $task ) ) { |
| 329 | $table = $wpdb->commentmeta; |
| 330 | $data = [ 'meta_value' => $this->new_term_id ]; |
| 331 | $where = [ |
| 332 | 'meta_key' => $field_name, |
| 333 | 'meta_value' => $this->term_id, |
| 334 | ]; |
| 335 | $format = '%s'; |
| 336 | $where_format = [ '%s', '%s' ]; |
| 337 | $wpdb->update( $table, $data, $where, $format, $where_format ); |
| 338 | |
| 339 | $this->append_progress( $task ); |
| 340 | } |
| 341 | |
| 342 | // Fix up the serialized data |
| 343 | $task = "update_commentmeta_{$field_name}_serialized"; |
| 344 | if ( ! $this->have_done( $task ) ) { |
| 345 | $meta_key = sprintf( '_pods_%s', $field_name ); |
| 346 | $target_serialized = sprintf( ';i:%s;', $this->term_id ); |
| 347 | $replace_serialized = sprintf( ';i:%s;', $this->new_term_id ); |
| 348 | |
| 349 | $wpdb->query( $wpdb->prepare( " |
| 350 | UPDATE |
| 351 | {$wpdb->commentmeta} |
| 352 | SET |
| 353 | meta_value = REPLACE( meta_value, %s, %s ) |
| 354 | WHERE |
| 355 | meta_key = %s |
| 356 | AND meta_value LIKE %s |
| 357 | ", $target_serialized, $replace_serialized, $meta_key, '%' . pods_sanitize_like( $target_serialized ) . '%' ) ); |
| 358 | |
| 359 | $this->append_progress( $task ); |
| 360 | }//end if |
| 361 | |
| 362 | } |
| 363 | |
| 364 | /** |
| 365 | * Called for all fields related to the target taxonomy that are in a user Pod |
| 366 | * |
| 367 | * @param string $field_name |
| 368 | */ |
| 369 | private function update_usermeta( $field_name ) { |
| 370 | /** @var wpdb $wpdb */ |
| 371 | global $wpdb; |
| 372 | |
| 373 | // Fix up the unserialized data |
| 374 | $task = "update_usermeta_{$field_name}_unserialized"; |
| 375 | if ( ! $this->have_done( $task ) ) { |
| 376 | $table = $wpdb->usermeta; |
| 377 | $data = [ 'meta_value' => $this->new_term_id ]; |
| 378 | $where = [ |
| 379 | 'meta_key' => $field_name, |
| 380 | 'meta_value' => $this->term_id, |
| 381 | ]; |
| 382 | $format = '%s'; |
| 383 | $where_format = [ '%s', '%s' ]; |
| 384 | $wpdb->update( $table, $data, $where, $format, $where_format ); |
| 385 | |
| 386 | $this->append_progress( $task ); |
| 387 | } |
| 388 | |
| 389 | // Fix up the serialized data |
| 390 | $task = "update_usermeta_{$field_name}_serialized"; |
| 391 | if ( ! $this->have_done( $task ) ) { |
| 392 | $meta_key = sprintf( '_pods_%s', $field_name ); |
| 393 | $target_serialized = sprintf( ';i:%s;', $this->term_id ); |
| 394 | $replace_serialized = sprintf( ';i:%s;', $this->new_term_id ); |
| 395 | |
| 396 | $wpdb->query( $wpdb->prepare( " |
| 397 | UPDATE |
| 398 | {$wpdb->usermeta} |
| 399 | SET |
| 400 | meta_value = REPLACE( meta_value, %s, %s ) |
| 401 | WHERE |
| 402 | meta_key = %s |
| 403 | AND meta_value LIKE %s |
| 404 | ", $target_serialized, $replace_serialized, $meta_key, '%' . pods_sanitize_like( $target_serialized ) . '%' ) ); |
| 405 | |
| 406 | $this->append_progress( $task ); |
| 407 | }//end if |
| 408 | |
| 409 | } |
| 410 | |
| 411 | /** |
| 412 | * Called for all fields related to the target taxonomy that are in a user Pod |
| 413 | * |
| 414 | * @param string $pod_name |
| 415 | * @param string $field_name |
| 416 | */ |
| 417 | private function update_setting_meta( $pod_name, $field_name ) { |
| 418 | /** @var wpdb $wpdb */ |
| 419 | global $wpdb; |
| 420 | |
| 421 | $option_name = "{$pod_name}_{$field_name}"; |
| 422 | |
| 423 | // Fix up the unserialized data |
| 424 | $task = "update_setting_meta_{$pod_name}_{$field_name}_unserialized"; |
| 425 | if ( ! $this->have_done( $task ) ) { |
| 426 | // UPDATE {$wpdb->options} SET option_value = '{$new_term_id}' WHERE option_name = '{$pod_name}_{$field_name}' AND option_value = '{$term_id}' |
| 427 | $table = $wpdb->options; |
| 428 | $data = [ 'option_value' => $this->new_term_id ]; |
| 429 | $where = [ |
| 430 | 'option_name' => $option_name, |
| 431 | 'option_value' => $this->term_id, |
| 432 | ]; |
| 433 | $format = '%s'; |
| 434 | $where_format = [ '%s', '%s' ]; |
| 435 | $wpdb->update( $table, $data, $where, $format, $where_format ); |
| 436 | |
| 437 | $this->append_progress( $task ); |
| 438 | } |
| 439 | |
| 440 | // Fix up the serialized data |
| 441 | $task = "update_setting_meta_{$pod_name}_{$field_name}_serialized"; |
| 442 | if ( ! $this->have_done( $task ) ) { |
| 443 | $target_serialized = sprintf( ';i:%s;', $this->term_id ); |
| 444 | $replace_serialized = sprintf( ';i:%s;', $this->new_term_id ); |
| 445 | |
| 446 | $wpdb->query( $wpdb->prepare( " |
| 447 | UPDATE |
| 448 | {$wpdb->options} |
| 449 | SET |
| 450 | option_value = REPLACE( option_value, %s, %s ) |
| 451 | WHERE |
| 452 | option_name = %s |
| 453 | AND option_value LIKE %s |
| 454 | ", $target_serialized, $replace_serialized, $option_name, '%' . pods_sanitize_like( $target_serialized ) . '%' ) ); |
| 455 | |
| 456 | $this->append_progress( $task ); |
| 457 | }//end if |
| 458 | |
| 459 | } |
| 460 | |
| 461 | /** |
| 462 | * @param string $task_name |
| 463 | * |
| 464 | * @return bool |
| 465 | */ |
| 466 | private function have_done( $task_name ) { |
| 467 | return in_array( $task_name, $this->previous_progress, true ); |
| 468 | } |
| 469 | |
| 470 | /** |
| 471 | * @return array |
| 472 | */ |
| 473 | private function get_progress() { |
| 474 | return get_option( $this->progress_option_name, [] ); |
| 475 | } |
| 476 | |
| 477 | /** |
| 478 | * @param $data |
| 479 | */ |
| 480 | private function append_progress( $data ) { |
| 481 | // Get the current progress array |
| 482 | $current_progress = $this->get_progress(); |
| 483 | if ( ! is_array( $current_progress ) ) { |
| 484 | $current_progress = []; |
| 485 | } |
| 486 | |
| 487 | // Tack on the new data |
| 488 | $updated_progress = array_merge( $current_progress, [ $data ] ); |
| 489 | |
| 490 | // Note: we don't want autoload set and you cannot specify autoload via update_option |
| 491 | if ( ! empty( $current_progress ) && is_array( $current_progress ) ) { |
| 492 | update_option( $this->progress_option_name, $updated_progress ); |
| 493 | } else { |
| 494 | add_option( $this->progress_option_name, $updated_progress, '', false ); |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | /** |
| 499 | * |
| 500 | */ |
| 501 | private function delete_progress() { |
| 502 | delete_option( $this->progress_option_name ); |
| 503 | } |
| 504 | |
| 505 | } |
| 506 |