PluginProbe ʕ •ᴥ•ʔ
Admin Columns / 4.3.2
Admin Columns v4.3.2
7.0.19 2.3.5 2.4 2.4.1 2.4.10 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.6.1 2.5.6.2 2.5.6.3 2.5.6.4 3.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.7 3.1 3.1.1 3.1.10 3.1.2 3.1.3 3.1.5 3.2.3 3.2.7 3.3.1 3.4.1 3.4.6 3.4.8 4.0.1 4.0.3 4.1.6 4.2.2 4.2.5 4.3 4.3.2 4.4.1 4.4.4 4.4.5 4.5.5 4.6.1 4.7.18 4.7.19 4.7.20 4.7.7 7.0.13 7.0.14 7.0.16 trunk 1.0 1.1 1.1.3 1.2 1.2.1 1.3 1.3.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.5.1 1.4.6 1.4.6.1 1.4.6.2 1.4.6.3 1.4.6.4 1.4.7 1.4.8 1.4.9 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2 2.2.1 2.2.1.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.5.1 2.2.6 2.2.6.1 2.2.6.2 2.2.6.3 2.2.6.4 2.2.7 2.2.8 2.2.8.1 2.2.9 2.3.1 2.3.2 2.3.3
codepress-admin-columns / classes / Plugin / Update / V4000.php
codepress-admin-columns / classes / Plugin / Update Last commit date
V3005.php 5 years ago V3007.php 5 years ago V3201.php 5 years ago V4000.php 5 years ago
V4000.php
732 lines
1 <?php
2
3 namespace AC\Plugin\Update;
4
5 use AC\ListScreenRepository\Database;
6 use AC\Plugin\Update;
7 use AC\Storage\ListScreenOrder;
8 use DateTime;
9
10 class V4000 extends Update {
11
12 const LAYOUT_PREFIX = 'cpac_layouts';
13 const COLUMNS_PREFIX = 'cpac_options_';
14
15 const PROGRESS_KEY = 'ac_update_progress';
16 const REPLACEMENT_IDS_KEY = 'ac_update_replacement_ids';
17
18 /** @var int */
19 private $next_step;
20
21 public function __construct( $stored_version ) {
22
23 // because `get_option` could be cached we only fetch the next step from the DB on initialisation.
24 $this->next_step = $this->get_next_step();
25
26 parent::__construct( $stored_version );
27 }
28
29 protected function set_version() {
30 $this->version = '4.0.0';
31 }
32
33 public function apply_update() {
34 // just in case we need a bit of extra time to execute our upgrade script
35 if ( ini_get( 'max_execution_time' ) < 120 ) {
36 @set_time_limit( 120 );
37 }
38
39 global $wpdb;
40
41 // Apply update in chunks to minimize the impact of a timeout.
42 switch ( $this->next_step ) {
43 case 1 :
44 // 1. migrate segments to site specific user preference. Previously this was stored globally.
45 $this->migrate_segments_preferences();
46
47 // go to next step
48 $this->update_next_step( 2 )
49 ->apply_update();
50
51 break;
52 case 2 :
53
54 // 2. migrate column settings to new DB table
55 $replaced_list_ids = $this->migrate_list_screen_settings();
56
57 // $replaced_list_ids contains a list of empty id's that are replaced with unique id's
58 $this->update_replacement_ids( $replaced_list_ids );
59
60 // go to next step
61 $this->update_next_step( 3 )
62 ->apply_update();
63
64 break;
65 case 3 :
66
67 // 3. User Preference "Segments": ac_preferences_search_segments
68 $this->update_user_preferences_segments( $this->get_replacement_ids() );
69
70 // go to next step
71 $this->update_next_step( 4 )
72 ->apply_update();
73
74 break;
75 case 4 :
76 $replaced_list_ids = $this->get_replacement_ids();
77
78 // 4. User Preference "Horizontal Scrolling": ac_preferences_show_overflow_table
79 $this->update_user_preference_by_key( $wpdb->get_blog_prefix() . 'ac_preferences_show_overflow_table', $replaced_list_ids );
80
81 // 5. User Preference "Sort": ac_preferences_sorted_by
82 $this->update_user_preference_by_key( $wpdb->get_blog_prefix() . 'ac_preferences_sorted_by', $replaced_list_ids );
83
84 // go to next step
85 $this->update_next_step( 5 )
86 ->apply_update();
87
88 break;
89 case 5 :
90 $this->migrate_invalid_network_settings();
91
92 // go to next step
93 $this->update_next_step( 6 )
94 ->apply_update();
95
96 break;
97 case 6 :
98 $replaced_list_ids = $this->get_replacement_ids();
99
100 // 6. User Preference "Table selection": wp_ac_preferences_layout_table
101 $this->migrate_user_preferences_table_selection( $replaced_list_ids );
102
103 // 7. Migrate layout order from `usermeta` to the `option table`
104 $this->migrate_list_screen_order( $replaced_list_ids );
105
106 // clear steps and replacement id's
107 $this->flush_temp_data();
108
109 break;
110 }
111 }
112
113 private function update_replacement_ids( array $ids ) {
114 update_option( self::REPLACEMENT_IDS_KEY, $ids, false );
115 }
116
117 private function get_replacement_ids() {
118 return (array) get_option( self::REPLACEMENT_IDS_KEY, [] );
119 }
120
121 /**
122 * @return int
123 */
124 private function get_next_step() {
125 return (int) get_option( self::PROGRESS_KEY, 1 );
126 }
127
128 private function update_next_step( $step ) {
129 $this->next_step = (int) $step;
130
131 update_option( self::PROGRESS_KEY, $this->next_step, false );
132
133 return $this;
134 }
135
136 private function flush_temp_data() {
137 delete_option( self::PROGRESS_KEY );
138 delete_option( self::REPLACEMENT_IDS_KEY );
139 }
140
141 // Segments were stored globally, ignoring individual sites on a multisite network. Segments are now stored per site.
142 private function migrate_segments_preferences() {
143 global $wpdb;
144
145 $prefix = 'ac_preferences_search_segments_';
146
147 $sql = "
148 SELECT *
149 FROM $wpdb->usermeta
150 WHERE meta_key LIKE '{$prefix}%'
151 ORDER BY `umeta_id` DESC
152 ";
153
154 $results = $wpdb->get_results( $sql );
155
156 foreach ( $results as $row ) {
157
158 $data = maybe_unserialize( $row->meta_value );
159
160 if ( $data ) {
161 update_user_option( $row->user_id, $row->meta_key, $data );
162 }
163 }
164 }
165
166 private function update_user_preferences_segments( array $list_ids ) {
167 global $wpdb;
168
169 $prefix = $wpdb->get_blog_prefix() . 'ac_preferences_search_segments_';
170
171 foreach ( $list_ids as $list_key => $ids ) {
172 foreach ( $ids as $deprecated_id => $list_id ) {
173
174 $old_meta_key = $prefix . ( $deprecated_id ?: $list_key );
175
176 // Segments were stored globally, ignoring individual sites on a multisite network. Segments are now stored per site.
177 $new_meta_key = $prefix . $list_id;
178
179 $sql = $wpdb->prepare( "SELECT user_id, meta_value FROM $wpdb->usermeta WHERE meta_key = %s", $old_meta_key );
180
181 $results = $wpdb->get_results( $sql );
182
183 foreach ( $results as $row ) {
184 $wpdb->insert(
185 $wpdb->usermeta,
186 [
187 'user_id' => $row->user_id,
188 'meta_key' => $new_meta_key,
189 'meta_value' => $row->meta_value,
190 ],
191 [
192 '%d',
193 '%s',
194 '%s',
195 ]
196 );
197 }
198 }
199 }
200 }
201
202 /**
203 * @param array $list_ids
204 *
205 * @return array
206 */
207 private function map_to_storage_keys( array $list_ids ) {
208 $map = [];
209
210 foreach ( $list_ids as $list_key => $ids ) {
211 foreach ( $ids as $deprecated_id => $list_id ) {
212 $old_meta_key = $list_key . $deprecated_id;
213 $new_meta_key = $list_key . $list_id;
214
215 $map[ $old_meta_key ] = $new_meta_key;
216 }
217 }
218
219 return $map;
220 }
221
222 private function update_user_preference_by_key( $meta_key, array $list_ids ) {
223 global $wpdb;
224
225 $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->usermeta} WHERE meta_key = %s", $meta_key ) );
226
227 $storage_keys = $this->map_to_storage_keys( $list_ids );
228
229 foreach ( $results as $row ) {
230 $data = maybe_unserialize( $row->meta_value );
231
232 if ( empty( $data ) ) {
233 continue;
234 }
235
236 $orginal_data = $data;
237
238 foreach ( $storage_keys as $old_key => $new_key ) {
239
240 // Replace old key with new key
241 if ( isset( $data[ $old_key ] ) ) {
242 $data[ $new_key ] = $data[ $old_key ];
243
244 unset( $data[ $new_key ] );
245 }
246 }
247
248 // no update needed
249 if ( $data === $orginal_data ) {
250 continue;
251 }
252
253 $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->usermeta} SET meta_value = %s WHERE umeta_id = %d", serialize( $data ), $row->umeta_id ) );
254 }
255 }
256
257 private function migrate_user_preferences_table_selection( array $replaced_list_ids ) {
258 global $wpdb;
259
260 $meta_key = $wpdb->get_blog_prefix() . 'ac_preferences_layout_table';
261 $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->usermeta} WHERE meta_key = %s", $meta_key ) );
262
263 foreach ( $results as $row ) {
264 $data = maybe_unserialize( $row->meta_value );
265
266 if ( empty( $data ) || ! is_array( $data ) ) {
267 continue;
268 }
269
270 $orginal_data = $data;
271
272 foreach ( $replaced_list_ids as $list_key => $ids ) {
273
274 if ( ! array_key_exists( $list_key, $data ) ) {
275 continue;
276 }
277
278 $old_id = $data[ $list_key ];
279
280 if ( array_key_exists( $old_id, $ids ) ) {
281
282 // use replaced ID
283 $data[ $list_key ] = $ids[ $old_id ];
284 }
285 }
286
287 // no update needed
288 if ( $data === $orginal_data ) {
289 continue;
290 }
291
292 $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->usermeta} SET meta_value = %s WHERE umeta_id = %d", serialize( $data ), $row->umeta_id ) );
293 }
294 }
295
296 /**
297 * Migrate the order of the list screens from the `usermeta` to the `options` table
298 */
299 private function migrate_list_screen_order( array $replaced_list_ids ) {
300 global $wpdb;
301
302 $meta_key = $wpdb->get_blog_prefix() . 'ac_preferences_layout_order';
303
304 $sql = $wpdb->prepare( "SELECT meta_value FROM {$wpdb->prefix}usermeta WHERE meta_key = %s AND meta_value != '' LIMIT 1", $meta_key );
305
306 $result = $wpdb->get_var( $sql );
307
308 if ( ! $result ) {
309 return;
310 }
311
312 $list_screens = maybe_unserialize( $result );
313
314 if ( ! $list_screens || ! is_array( $list_screens ) ) {
315 return;
316 }
317
318 $order = new ListScreenOrder();
319
320 foreach ( $list_screens as $list_screen_key => $ids ) {
321
322 if ( $ids && is_array( $ids ) ) {
323
324 // try and replace deprecated id's
325 foreach ( $ids as $k => $id ) {
326 $ids[ $k ] = $this->maybe_replace_id( $replaced_list_ids, $list_screen_key, $id );
327 }
328
329 $order->set( $list_screen_key, $ids );
330 }
331 }
332 }
333
334 /**
335 * Since Network list screens have their own preference, we have to remove any network list screens from the preferences
336 */
337 private function migrate_invalid_network_settings() {
338 global $wpdb;
339
340 $results = $wpdb->get_results( $wpdb->prepare( "
341 SELECT *
342 FROM {$wpdb->usermeta}
343 WHERE meta_key = %s
344 ",
345 $wpdb->get_blog_prefix() . 'ac_preferences_settings' )
346 );
347
348 foreach ( $results as $row ) {
349 $data = maybe_unserialize( $row->meta_value );
350
351 if ( empty( $data ) || ! is_array( $data ) ) {
352 continue;
353 }
354
355 if ( isset( $data['list_screen'] ) && in_array( $data['list_screen'], [ 'wp-ms_sites', 'wp-ms_users' ], true ) ) {
356 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE umeta_id = %d", $row->umeta_id ) );
357 }
358 }
359 }
360
361 private function maybe_replace_id( array $replaced_list_ids, $list_key, $id ) {
362 if ( ! array_key_exists( $list_key, $replaced_list_ids ) ) {
363 return $id;
364 }
365
366 $_ids = $replaced_list_ids[ $list_key ];
367
368 if ( ! $_ids || ! is_array( $_ids ) ) {
369 return $id;
370 }
371
372 foreach ( $_ids as $old_id => $new_id ) {
373 if ( $old_id === $id ) {
374 return $new_id;
375 }
376 }
377
378 return $id;
379 }
380
381 /**
382 * @return object[]
383 */
384 private function get_layouts_data() {
385 global $wpdb;
386
387 $prefix = self::LAYOUT_PREFIX;
388
389 $sql = "
390 SELECT option_name, option_value
391 FROM {$wpdb->options}
392 WHERE option_name LIKE '{$prefix}%'
393 ORDER BY `option_id` DESC
394 ";
395
396 $results = $wpdb->get_results( $sql );
397
398 $layouts = [];
399
400 foreach ( $results as $row ) {
401 $data = maybe_unserialize( $row->option_value );
402
403 if ( is_object( $data ) ) {
404
405 $list_id = $data->id;
406
407 $list_key = $this->remove_prefix( self::LAYOUT_PREFIX, $row->option_name );
408 $list_key = $this->remove_suffix( $list_id, $list_key );
409
410 if ( ! $list_key ) {
411 continue;
412 }
413
414 $layout_data = [
415 'id' => $list_id,
416 'key' => $list_key,
417 'name' => '',
418 'users' => [],
419 'roles' => [],
420 ];
421
422 if ( ! empty( $data->name ) ) {
423 $layout_data['name'] = $data->name;
424 }
425 if ( ! empty( $data->users ) && is_array( $data->users ) ) {
426 $layout_data['users'] = array_map( 'intval', $data->users );
427 }
428 if ( ! empty( $data->roles ) && is_array( $data->roles ) ) {
429 $layout_data['roles'] = array_map( 'strval', $data->roles );
430 }
431
432 $layouts[] = $layout_data;
433 }
434 }
435
436 return $layouts;
437 }
438
439 private function get_columns_data() {
440 global $wpdb;
441
442 $prefix = self::COLUMNS_PREFIX;
443
444 $sql = "
445 SELECT option_name, option_value
446 FROM {$wpdb->options}
447 WHERE option_name LIKE '{$prefix}%'
448 ORDER BY `option_id` DESC
449 ";
450
451 $results = $wpdb->get_results( $sql );
452
453 $columns = [];
454
455 foreach ( $results as $row ) {
456 if ( $this->has_suffix( '__default', $row->option_name ) ) {
457 continue;
458 }
459
460 $storage_key = $this->remove_prefix( self::COLUMNS_PREFIX, $row->option_name );
461
462 $column_data = maybe_unserialize( $row->option_value );
463
464 $columns[ $storage_key ] = $column_data && is_array( $column_data ) ? $column_data : [];
465 }
466
467 return $columns;
468 }
469
470 /**
471 * @return array List of replaced id's
472 */
473 private function migrate_list_screen_settings() {
474 $migrate = [];
475
476 /** @var array $replaced_list_ids array( $list_key => array( $deprecated_list_id => $new_list_id ) ) */
477 $replaced_list_ids = [];
478
479 // 1. clear DB table
480 $this->clear_table();
481
482 // 2. Fetch data
483 $layouts_data = $this->get_layouts_data();
484 $columns_data = $this->get_columns_data();
485
486 // 3. Process Pro settings
487 foreach ( $layouts_data as $layout_data ) {
488
489 $list_key = $layout_data['key'];
490
491 $storage_key = $list_key . $layout_data['id'];
492
493 $columns = [];
494
495 // Check for column settings
496 if ( isset( $columns_data[ $storage_key ] ) ) {
497 $columns = $columns_data[ $storage_key ];
498
499 // Remove used column settings from stack
500 unset( $columns_data[ $storage_key ] );
501 }
502
503 $settings = [];
504 if ( $layout_data['users'] ) {
505 $settings['users'] = $layout_data['users'];
506 }
507 if ( $layout_data['roles'] ) {
508 $settings['roles'] = $layout_data['roles'];
509 }
510
511 $list_id = $layout_data['id'];
512
513 if ( ! $list_id ) {
514 $list_id = uniqid();
515
516 // add to list of id's that have been replaced
517 $replaced_list_ids[ $list_key ][''] = $list_id;
518 }
519
520 $list_data = [
521 'id' => $list_id,
522 'key' => $list_key,
523 'title' => $layout_data['name'],
524 'columns' => $columns,
525 'settings' => $settings,
526 ];
527
528 $migrate[] = $list_data;
529 }
530
531 // 4. Process Free column settings
532 foreach ( $columns_data as $list_key => $columns ) {
533 if ( empty( $columns ) ) {
534 continue;
535 }
536
537 // Skip columns that contain a list ID
538 if ( $this->contains_list_id( $list_key ) ) {
539 continue;
540 }
541
542 $list_id = uniqid();
543
544 $list_data = [
545 'id' => $list_id,
546 'key' => $list_key,
547 'title' => __( 'Original', 'codepress-admin-columns' ),
548 'settings' => [],
549 'columns' => $columns,
550 ];
551
552 $migrate[] = $list_data;
553
554 // add to list of id's that have been replaced
555 $replaced_list_ids[ $list_key ][''] = $list_id;
556 }
557
558 // 5. Make sure all ID's are unique.
559 // A duplicate `id` is possible when a user manually exported their list screen settings and
560 // changed the list screen `key` (e.g. from post to page), without changing the `id`, and imported these settings.
561 $migrate = $this->unique_ids( $migrate );
562
563 // 6. Insert into DB
564 foreach ( $migrate as $list_data ) {
565 $this->insert( $list_data );
566 }
567
568 return $replaced_list_ids;
569 }
570
571 /**
572 * @param array $migrate
573 *
574 * @return array
575 */
576 private function unique_ids( array $migrate ) {
577 $ids = [];
578
579 foreach ( $migrate as $k => $list_data ) {
580
581 if ( in_array( $list_data['id'], $ids, true ) ) {
582 // Truly eliminates a duplicate $unique_id, because `uniqid()` is based on the current time in microseconds
583 usleep( 1000 );
584
585 $migrate[ $k ]['id'] = uniqid();
586 }
587
588 $ids[] = $list_data['id'];
589 }
590
591 return $migrate;
592 }
593
594 private function clear_table() {
595 global $wpdb;
596
597 $table = $wpdb->prefix . Database::TABLE;
598
599 $exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ) === $table;
600
601 if ( $exists ) {
602 $wpdb->query( "TRUNCATE TABLE " . $table );
603 }
604 }
605
606 private function insert( array $data ) {
607 global $wpdb;
608
609 $date = new DateTime();
610
611 $wpdb->insert(
612 $wpdb->prefix . Database::TABLE,
613 [
614 'title' => $data['title'],
615 'list_id' => $data['id'],
616 'list_key' => $data['key'],
617 'columns' => serialize( $data['columns'] ),
618 'settings' => serialize( $data['settings'] ),
619 'date_modified' => $date->format( 'Y-m-d H:i:s' ),
620 'date_created' => $date->format( 'Y-m-d H:i:s' ),
621 ],
622 [
623 '%s',
624 '%s',
625 '%s',
626 '%s',
627 '%s',
628 '%s',
629 '%s',
630 ]
631 );
632 }
633
634 /**
635 * @param string $storage_key
636 *
637 * @return string|false ID
638 */
639 private function contains_list_id( $storage_key ) {
640 $prefixes = [
641 'wp-users',
642 'wp-media',
643 'wp-comments',
644 'wp-ms_sites',
645 'wp-ms_users',
646 ];
647
648 foreach ( $prefixes as $prefix ) {
649 if ( $this->has_prefix( $prefix, $storage_key ) ) {
650 $layout_id = $this->remove_prefix( $prefix, $storage_key );
651 $layout_id = substr( $layout_id, -13 );
652
653 return $this->is_layout_id( $layout_id );
654 }
655 }
656
657 // Is it a taxonomy?
658 if ( $this->has_prefix( 'wp-taxonomy_', $storage_key ) ) {
659 $taxonomy = $this->remove_prefix( 'wp-taxonomy_', $storage_key );
660
661 if ( taxonomy_exists( $taxonomy ) ) {
662 return false;
663 }
664
665 $layout_id = substr( $taxonomy, -13 );
666
667 return $this->is_layout_id( $layout_id );
668 }
669
670 // is it a post?
671 if ( post_type_exists( $storage_key ) ) {
672 return false;
673 }
674
675 $layout_id = substr( $storage_key, -13 );
676
677 return $this->is_layout_id( $layout_id );
678 }
679
680 /**
681 * @param string $id
682 *
683 * @return string|false
684 */
685 private function is_layout_id( $id ) {
686 return $id && is_string( $id ) && ( strlen( $id ) === 13 ) && $this->is_hex( $id ) ? $id : false;
687 }
688
689 /**
690 * @param string $string
691 *
692 * @return bool
693 */
694 private function is_hex( $string ) {
695 return '' == trim( substr( $string, -13 ), '0..9A..Fa..f' );
696 }
697
698 private function remove_prefix( $prefix, $string ) {
699 return (string) preg_replace( '/^' . preg_quote( $prefix, '/' ) . '/', '', $string );
700 }
701
702 /**
703 * @param string $string
704 * @param string $end
705 *
706 * @return string
707 */
708 private function remove_suffix( $suffix, $string ) {
709 return (string) preg_replace( '/' . preg_quote( $suffix, '/' ) . '$/', '', $string );
710 }
711
712 /**
713 * @param string $prefix
714 * @param string $string
715 *
716 * @return bool
717 */
718 private function has_prefix( $prefix, $string ) {
719 return substr( $string, 0, strlen( $prefix ) ) === $prefix;
720 }
721
722 /**
723 * @param string $suffix
724 * @param string $string
725 *
726 * @return bool
727 */
728 private function has_suffix( $suffix, $string ) {
729 return substr( $string, strlen( $suffix ) * -1 ) === $suffix;
730 }
731
732 }