PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.1
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.1
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / inc / class-upgrade.php

class-upgrade.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.1, at inc/class-upgrade.php

1,259 lines 37.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Internal
6 */
7
8 use Yoast\WP\Lib\Model;
9
10 /**
11 * This code handles the option upgrades.
12 */
13 class WPSEO_Upgrade {
14
15 /**
16 * The taxonomy helper.
17 *
18 * @var \Yoast\WP\SEO\Helpers\Taxonomy_Helper
19 */
20 private $taxonomy_helper;
21
22 /**
23 * Class constructor.
24 */
25 public function __construct() {
26 $this->taxonomy_helper = YoastSEO()->helpers->taxonomy;
27
28 $version = WPSEO_Options::get( 'version' );
29
30 WPSEO_Options::maybe_set_multisite_defaults( false );
31
32 $routines = [
33 '1.5.0' => 'upgrade_15',
34 '2.0' => 'upgrade_20',
35 '2.1' => 'upgrade_21',
36 '2.2' => 'upgrade_22',
37 '2.3' => 'upgrade_23',
38 '3.0' => 'upgrade_30',
39 '3.3' => 'upgrade_33',
40 '3.6' => 'upgrade_36',
41 '4.0' => 'upgrade_40',
42 '4.4' => 'upgrade_44',
43 '4.7' => 'upgrade_47',
44 '4.9' => 'upgrade_49',
45 '5.0' => 'upgrade_50',
46 '5.5' => 'upgrade_55',
47 '6.3' => 'upgrade_63',
48 '7.0-RC0' => 'upgrade_70',
49 '7.1-RC0' => 'upgrade_71',
50 '7.3-RC0' => 'upgrade_73',
51 '7.4-RC0' => 'upgrade_74',
52 '7.5.3' => 'upgrade_753',
53 '7.7-RC0' => 'upgrade_77',
54 '7.7.2-RC0' => 'upgrade_772',
55 '9.0-RC0' => 'upgrade_90',
56 '10.0-RC0' => 'upgrade_100',
57 '11.1-RC0' => 'upgrade_111',
58 // Reset notifications because we removed the AMP Glue plugin notification.
59 '12.1-RC0' => 'clean_all_notifications',
60 '12.3-RC0' => 'upgrade_123',
61 '12.4-RC0' => 'upgrade_124',
62 '12.8-RC0' => 'upgrade_128',
63 '13.2-RC0' => 'upgrade_132',
64 '14.0.3-RC0' => 'upgrade_1403',
65 '14.1-RC0' => 'upgrade_141',
66 '14.2-RC0' => 'upgrade_142',
67 '14.5-RC0' => 'upgrade_145',
68 '14.9-RC0' => 'upgrade_149',
69 '15.1-RC0' => 'upgrade_151',
70 '15.3-RC0' => 'upgrade_153',
71 '15.5-RC0' => 'upgrade_155',
72 '15.7-RC0' => 'upgrade_157',
73 '15.9.1-RC0' => 'upgrade_1591',
74 '16.2-RC0' => 'upgrade_162',
75 '16.5-RC0' => 'upgrade_165',
76 '17.2-RC0' => 'upgrade_172',
77 '17.7.1-RC0' => 'upgrade_1771',
78 '17.9-RC0' => 'upgrade_179',
79 ];
80
81 array_walk( $routines, [ $this, 'run_upgrade_routine' ], $version );
82
83 if ( version_compare( $version, '12.5-RC0', '<' ) ) {
84 /*
85 * We have to run this by hook, because otherwise:
86 * - the theme support check isn't available.
87 * - the notification center notifications are not filled yet.
88 */
89 add_action( 'init', [ $this, 'upgrade_125' ] );
90 }
91
92 // Since 3.7.
93 $upsell_notice = new WPSEO_Product_Upsell_Notice();
94 $upsell_notice->set_upgrade_notice();
95
96 /**
97 * Filter: 'wpseo_run_upgrade' - Runs the upgrade hook which are dependent on Yoast SEO.
98 *
99 * @api string - The current version of Yoast SEO
100 */
101 do_action( 'wpseo_run_upgrade', $version );
102
103 $this->finish_up( $version );
104 }
105
106 /**
107 * Runs the upgrade routine.
108 *
109 * @param string $routine The method to call.
110 * @param string $version The new version.
111 * @param string $current_version The current set version.
112 *
113 * @return void
114 */
115 protected function run_upgrade_routine( $routine, $version, $current_version ) {
116 if ( version_compare( $current_version, $version, '<' ) ) {
117 $this->$routine( $current_version );
118 }
119 }
120
121 /**
122 * Adds a new upgrade history entry.
123 *
124 * @param string $current_version The old version from which we are upgrading.
125 * @param string $new_version The version we are upgrading to.
126 */
127 protected function add_upgrade_history( $current_version, $new_version ) {
128 $upgrade_history = new WPSEO_Upgrade_History();
129 $upgrade_history->add( $current_version, $new_version, array_keys( WPSEO_Options::$options ) );
130 }
131
132 /**
133 * Runs the needed cleanup after an update, setting the DB version to latest version, flushing caches etc.
134 *
135 * @param string|null $previous_version The previous version.
136 *
137 * @return void
138 */
139 protected function finish_up( $previous_version = null ) {
140 if ( $previous_version ) {
141 WPSEO_Options::set( 'previous_version', $previous_version );
142 }
143 WPSEO_Options::set( 'version', WPSEO_VERSION );
144
145 // Just flush rewrites, always, to at least make them work after an upgrade.
146 add_action( 'shutdown', 'flush_rewrite_rules' );
147
148 // Flush the sitemap cache.
149 WPSEO_Sitemaps_Cache::clear();
150
151 // Make sure all our options always exist - issue #1245.
152 WPSEO_Options::ensure_options_exist();
153 }
154
155 /**
156 * Run the Yoast SEO 1.5 upgrade routine.
157 *
158 * @param string $version Current plugin version.
159 */
160 private function upgrade_15( $version ) {
161 // Clean up options and meta.
162 WPSEO_Options::clean_up( null, $version );
163 WPSEO_Meta::clean_up();
164 }
165
166 /**
167 * Moves options that moved position in WPSEO 2.0.
168 */
169 private function upgrade_20() {
170 /**
171 * Clean up stray wpseo_ms options from the options table, option should only exist in the sitemeta table.
172 * This could have been caused in many version of Yoast SEO, so deleting it for everything below 2.0.
173 */
174 delete_option( 'wpseo_ms' );
175
176 $wpseo = $this->get_option_from_database( 'wpseo' );
177 $this->save_option_setting( $wpseo, 'pinterestverify' );
178
179 // Re-save option to trigger sanitization.
180 $this->cleanup_option_data( 'wpseo' );
181 }
182
183 /**
184 * Detects if taxonomy terms were split and updates the corresponding taxonomy meta's accordingly.
185 */
186 private function upgrade_21() {
187 $taxonomies = get_option( 'wpseo_taxonomy_meta', [] );
188
189 if ( ! empty( $taxonomies ) ) {
190 foreach ( $taxonomies as $taxonomy => $tax_metas ) {
191 foreach ( $tax_metas as $term_id => $tax_meta ) {
192 if ( function_exists( 'wp_get_split_term' ) ) {
193 $new_term_id = wp_get_split_term( $term_id, $taxonomy );
194 if ( $new_term_id !== false ) {
195 $taxonomies[ $taxonomy ][ $new_term_id ] = $taxonomies[ $taxonomy ][ $term_id ];
196 unset( $taxonomies[ $taxonomy ][ $term_id ] );
197 }
198 }
199 }
200 }
201
202 update_option( 'wpseo_taxonomy_meta', $taxonomies );
203 }
204 }
205
206 /**
207 * Performs upgrade functions to Yoast SEO 2.2.
208 */
209 private function upgrade_22() {
210 // Unschedule our tracking.
211 wp_clear_scheduled_hook( 'yoast_tracking' );
212
213 $this->cleanup_option_data( 'wpseo' );
214 }
215
216 /**
217 * Schedules upgrade function to Yoast SEO 2.3.
218 */
219 private function upgrade_23() {
220 add_action( 'wp', [ $this, 'upgrade_23_query' ], 90 );
221 add_action( 'admin_head', [ $this, 'upgrade_23_query' ], 90 );
222 }
223
224 /**
225 * Performs upgrade query to Yoast SEO 2.3.
226 */
227 public function upgrade_23_query() {
228 $wp_query = new WP_Query( 'post_type=any&meta_key=_yoast_wpseo_sitemap-include&meta_value=never&order=ASC' );
229
230 if ( ! empty( $wp_query->posts ) ) {
231 $options = get_option( 'wpseo_xml' );
232
233 $excluded_posts = [];
234 if ( $options['excluded-posts'] !== '' ) {
235 $excluded_posts = explode( ',', $options['excluded-posts'] );
236 }
237
238 foreach ( $wp_query->posts as $post ) {
239 if ( ! in_array( (string) $post->ID, $excluded_posts, true ) ) {
240 $excluded_posts[] = $post->ID;
241 }
242 }
243
244 // Updates the meta value.
245 $options['excluded-posts'] = implode( ',', $excluded_posts );
246
247 // Update the option.
248 update_option( 'wpseo_xml', $options );
249 }
250
251 // Remove the meta fields.
252 delete_post_meta_by_key( '_yoast_wpseo_sitemap-include' );
253 }
254
255 /**
256 * Performs upgrade functions to Yoast SEO 3.0.
257 */
258 private function upgrade_30() {
259 // Remove the meta fields for sitemap prio.
260 delete_post_meta_by_key( '_yoast_wpseo_sitemap-prio' );
261 }
262
263 /**
264 * Performs upgrade functions to Yoast SEO 3.3.
265 */
266 private function upgrade_33() {
267 // Notification dismissals have been moved to User Meta instead of global option.
268 delete_option( Yoast_Notification_Center::STORAGE_KEY );
269 }
270
271 /**
272 * Performs upgrade functions to Yoast SEO 3.6.
273 */
274 private function upgrade_36() {
275 global $wpdb;
276
277 // Between 3.2 and 3.4 the sitemap options were saved with autoloading enabled.
278 $wpdb->query( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE "wpseo_sitemap_%" AND autoload = "yes"' );
279 }
280
281 /**
282 * Removes the about notice when its still in the database.
283 */
284 private function upgrade_40() {
285 $center = Yoast_Notification_Center::get();
286 $center->remove_notification_by_id( 'wpseo-dismiss-about' );
287 }
288
289 /**
290 * Moves the content-analysis-active and keyword-analysis-acive options from wpseo-titles to wpseo.
291 */
292 private function upgrade_44() {
293 $wpseo_titles = $this->get_option_from_database( 'wpseo_titles' );
294
295 $this->save_option_setting( $wpseo_titles, 'content-analysis-active', 'content_analysis_active' );
296 $this->save_option_setting( $wpseo_titles, 'keyword-analysis-active', 'keyword_analysis_active' );
297
298 // Remove irrelevant content from the option.
299 $this->cleanup_option_data( 'wpseo_titles' );
300 }
301
302 /**
303 * Renames the meta name for the cornerstone content. It was a public meta field and it has to be private.
304 */
305 private function upgrade_47() {
306 global $wpdb;
307
308 // The meta key has to be private, so prefix it.
309 $wpdb->query(
310 $wpdb->prepare(
311 'UPDATE ' . $wpdb->postmeta . ' SET meta_key = %s WHERE meta_key = "yst_is_cornerstone"',
312 WPSEO_Cornerstone_Filter::META_NAME
313 )
314 );
315 }
316
317 /**
318 * Removes the 'wpseo-dismiss-about' notice for every user that still has it.
319 */
320 private function upgrade_49() {
321 global $wpdb;
322
323 /*
324 * Using a filter to remove the notification for the current logged in user. The notification center is
325 * initializing the notifications before the upgrade routine has been executedd and is saving the stored
326 * notifications on shutdown. This causes the returning notification. By adding this filter the shutdown
327 * routine on the notification center will remove the notification.
328 */
329 add_filter( 'yoast_notifications_before_storage', [ $this, 'remove_about_notice' ] );
330
331 $meta_key = $wpdb->get_blog_prefix() . Yoast_Notification_Center::STORAGE_KEY;
332
333 $usermetas = $wpdb->get_results(
334 $wpdb->prepare(
335 '
336 SELECT user_id, meta_value
337 FROM ' . $wpdb->usermeta . '
338 WHERE meta_key = %s AND meta_value LIKE %s
339 ',
340 $meta_key,
341 '%wpseo-dismiss-about%'
342 ),
343 ARRAY_A
344 );
345
346 if ( empty( $usermetas ) ) {
347 return;
348 }
349
350 foreach ( $usermetas as $usermeta ) {
351 $notifications = maybe_unserialize( $usermeta['meta_value'] );
352
353 foreach ( $notifications as $notification_key => $notification ) {
354 if ( ! empty( $notification['options']['id'] ) && $notification['options']['id'] === 'wpseo-dismiss-about' ) {
355 unset( $notifications[ $notification_key ] );
356 }
357 }
358
359 update_user_option( $usermeta['user_id'], Yoast_Notification_Center::STORAGE_KEY, array_values( $notifications ) );
360 }
361 }
362
363 /**
364 * Removes the wpseo-dismiss-about notice from a list of notifications.
365 *
366 * @param Yoast_Notification[] $notifications The notifications to filter.
367 *
368 * @return Yoast_Notification[] The filtered list of notifications. Excluding the wpseo-dismiss-about notification.
369 */
370 public function remove_about_notice( $notifications ) {
371 foreach ( $notifications as $notification_key => $notification ) {
372 if ( $notification->get_id() === 'wpseo-dismiss-about' ) {
373 unset( $notifications[ $notification_key ] );
374 }
375 }
376
377 return $notifications;
378 }
379
380 /**
381 * Adds the yoast_seo_links table to the database.
382 */
383 private function upgrade_50() {
384 global $wpdb;
385
386 // Deletes the post meta value, which might created in the RC.
387 $wpdb->query( 'DELETE FROM ' . $wpdb->postmeta . ' WHERE meta_key = "_yst_content_links_processed"' );
388 }
389
390 /**
391 * Register new capabilities and roles.
392 */
393 private function upgrade_55() {
394 // Register roles.
395 do_action( 'wpseo_register_roles' );
396 WPSEO_Role_Manager_Factory::get()->add();
397
398 // Register capabilities.
399 do_action( 'wpseo_register_capabilities' );
400 WPSEO_Capability_Manager_Factory::get()->add();
401 }
402
403 /**
404 * Removes some no longer used options for noindexing subpages and for meta keywords and its associated templates.
405 *
406 * @return void
407 */
408 private function upgrade_63() {
409 $this->cleanup_option_data( 'wpseo_titles' );
410 }
411
412 /**
413 * Perform the 7.0 upgrade, moves settings around, deletes several options.
414 *
415 * @return void
416 */
417 private function upgrade_70() {
418
419 $wpseo_permalinks = $this->get_option_from_database( 'wpseo_permalinks' );
420 $wpseo_xml = $this->get_option_from_database( 'wpseo_xml' );
421 $wpseo_rss = $this->get_option_from_database( 'wpseo_rss' );
422 $wpseo = $this->get_option_from_database( 'wpseo' );
423 $wpseo_internallinks = $this->get_option_from_database( 'wpseo_internallinks' );
424
425 // Move some permalink settings, then delete the option.
426 $this->save_option_setting( $wpseo_permalinks, 'redirectattachment', 'disable-attachment' );
427 $this->save_option_setting( $wpseo_permalinks, 'stripcategorybase' );
428
429 // Move one XML sitemap setting, then delete the option.
430 $this->save_option_setting( $wpseo_xml, 'enablexmlsitemap', 'enable_xml_sitemap' );
431
432
433 // Move the RSS settings to the search appearance settings, then delete the RSS option.
434 $this->save_option_setting( $wpseo_rss, 'rssbefore' );
435 $this->save_option_setting( $wpseo_rss, 'rssafter' );
436
437 $this->save_option_setting( $wpseo, 'company_logo' );
438 $this->save_option_setting( $wpseo, 'company_name' );
439 $this->save_option_setting( $wpseo, 'company_or_person' );
440 $this->save_option_setting( $wpseo, 'person_name' );
441
442 // Remove the website name and altername name as we no longer need them.
443 $this->cleanup_option_data( 'wpseo' );
444
445 // All the breadcrumbs settings have moved to the search appearance settings.
446 foreach ( array_keys( $wpseo_internallinks ) as $key ) {
447 $this->save_option_setting( $wpseo_internallinks, $key );
448 }
449
450 // Convert hidden metabox options to display metabox options.
451 $title_options = get_option( 'wpseo_titles' );
452
453 foreach ( $title_options as $key => $value ) {
454 if ( strpos( $key, 'hideeditbox-tax-' ) === 0 ) {
455 $taxonomy = substr( $key, strlen( 'hideeditbox-tax-' ) );
456 WPSEO_Options::set( 'display-metabox-tax-' . $taxonomy, ! $value );
457 continue;
458 }
459
460 if ( strpos( $key, 'hideeditbox-' ) === 0 ) {
461 $post_type = substr( $key, strlen( 'hideeditbox-' ) );
462 WPSEO_Options::set( 'display-metabox-pt-' . $post_type, ! $value );
463 continue;
464 }
465 }
466
467 // Cleanup removed options.
468 delete_option( 'wpseo_xml' );
469 delete_option( 'wpseo_permalinks' );
470 delete_option( 'wpseo_rss' );
471 delete_option( 'wpseo_internallinks' );
472
473 // Remove possibly present plugin conflict notice for plugin that was removed from the list of conflicting plugins.
474 $yoast_plugin_conflict = WPSEO_Plugin_Conflict::get_instance();
475 $yoast_plugin_conflict->clear_error( 'header-footer/plugin.php' );
476
477 // Moves the user meta for excluding from the XML sitemap to a noindex.
478 global $wpdb;
479 $wpdb->query( "UPDATE $wpdb->usermeta SET meta_key = 'wpseo_noindex_author' WHERE meta_key = 'wpseo_excludeauthorsitemap'" );
480 }
481
482 /**
483 * Perform the 7.1 upgrade.
484 *
485 * @return void
486 */
487 private function upgrade_71() {
488 $this->cleanup_option_data( 'wpseo_social' );
489
490 // Move the breadcrumbs setting and invert it.
491 $title_options = $this->get_option_from_database( 'wpseo_titles' );
492
493 if ( array_key_exists( 'breadcrumbs-blog-remove', $title_options ) ) {
494 WPSEO_Options::set( 'breadcrumbs-display-blog-page', ! $title_options['breadcrumbs-blog-remove'] );
495
496 $this->cleanup_option_data( 'wpseo_titles' );
497 }
498 }
499
500 /**
501 * Perform the 7.3 upgrade.
502 *
503 * @return void
504 */
505 private function upgrade_73() {
506 global $wpdb;
507 // We've moved the cornerstone checkbox to our proper namespace.
508 $wpdb->query( "UPDATE $wpdb->postmeta SET meta_key = '_yoast_wpseo_is_cornerstone' WHERE meta_key = '_yst_is_cornerstone'" );
509
510 // Remove the previous Whip dismissed message, as this is a new one regarding PHP 5.2.
511 delete_option( 'whip_dismiss_timestamp' );
512 }
513
514 /**
515 * Performs the 7.4 upgrade.
516 *
517 * @return void
518 */
519 private function upgrade_74() {
520 $this->remove_sitemap_validators();
521 }
522
523 /**
524 * Performs the 7.5.3 upgrade.
525 *
526 * When upgrading purging media is potentially relevant.
527 *
528 * @return void
529 */
530 private function upgrade_753() {
531 // Only when attachments are not disabled.
532 if ( WPSEO_Options::get( 'disable-attachment' ) === true ) {
533 return;
534 }
535
536 // Only when attachments are not no-indexed.
537 if ( WPSEO_Options::get( 'noindex-attachment' ) === true ) {
538 return;
539 }
540
541 // Set purging relevancy.
542 WPSEO_Options::set( 'is-media-purge-relevant', true );
543 }
544
545 /**
546 * Performs the 7.7 upgrade.
547 *
548 * @return void
549 */
550 private function upgrade_77() {
551 // Remove all OpenGraph content image cache.
552 $this->delete_post_meta( '_yoast_wpseo_post_image_cache' );
553 }
554
555 /**
556 * Performs the 7.7.2 upgrade.
557 *
558 * @return void
559 */
560 private function upgrade_772() {
561 if ( YoastSEO()->helpers->woocommerce->is_active() ) {
562 $this->migrate_woocommerce_archive_setting_to_shop_page();
563 }
564 }
565
566 /**
567 * Performs the 9.0 upgrade.
568 *
569 * @return void
570 */
571 private function upgrade_90() {
572 global $wpdb;
573
574 // Invalidate all sitemap cache transients.
575 WPSEO_Sitemaps_Cache_Validator::cleanup_database();
576
577 // Removes all scheduled tasks for hitting the sitemap index.
578 wp_clear_scheduled_hook( 'wpseo_hit_sitemap_index' );
579
580 $wpdb->query( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE "wpseo_sitemap_%"' );
581 }
582
583 /**
584 * Performs the 10.0 upgrade.
585 *
586 * @return void
587 */
588 private function upgrade_100() {
589 // Removes recalibration notifications.
590 $this->clean_all_notifications();
591
592 // Removes recalibration options.
593 WPSEO_Options::clean_up( 'wpseo' );
594 delete_option( 'wpseo_recalibration_beta_mailinglist_subscription' );
595 }
596
597 /**
598 * Performs the 11.1 upgrade.
599 *
600 * @return void
601 */
602 private function upgrade_111() {
603 // Set company_or_person to company when it's an invalid value.
604 $company_or_person = WPSEO_Options::get( 'company_or_person', '' );
605
606 if ( ! in_array( $company_or_person, [ 'company', 'person' ], true ) ) {
607 WPSEO_Options::set( 'company_or_person', 'company' );
608 }
609 }
610
611 /**
612 * Performs the 12.3 upgrade.
613 *
614 * Removes the about notice when its still in the database.
615 */
616 private function upgrade_123() {
617 $plugins = [
618 'yoast-seo-premium',
619 'video-seo-for-wordpress-seo-by-yoast',
620 'yoast-news-seo',
621 'local-seo-for-yoast-seo',
622 'yoast-woocommerce-seo',
623 'yoast-acf-analysis',
624 ];
625
626 $center = Yoast_Notification_Center::get();
627 foreach ( $plugins as $plugin ) {
628 $center->remove_notification_by_id( 'wpseo-outdated-yoast-seo-plugin-' . $plugin );
629 }
630 }
631
632 /**
633 * Performs the 12.4 upgrade.
634 *
635 * Removes the Google plus defaults from the database.
636 */
637 private function upgrade_124() {
638 $this->cleanup_option_data( 'wpseo_social' );
639 }
640
641 /**
642 * Performs the 12.5 upgrade.
643 */
644 public function upgrade_125() {
645 // Disables the force rewrite title when the theme supports it through WordPress.
646 if ( WPSEO_Options::get( 'forcerewritetitle', false ) && current_theme_supports( 'title-tag' ) ) {
647 WPSEO_Options::set( 'forcerewritetitle', false );
648 }
649
650 global $wpdb;
651 $wpdb->query( "DELETE FROM $wpdb->usermeta WHERE meta_key = 'wp_yoast_promo_hide_premium_upsell_admin_block'" );
652
653 // Removes the WordPress update notification, because it is no longer necessary when WordPress 5.3 is released.
654 $center = Yoast_Notification_Center::get();
655 $center->remove_notification_by_id( 'wpseo-dismiss-wordpress-upgrade' );
656 }
657
658 /**
659 * Performs the 12.8 upgrade.
660 */
661 private function upgrade_128() {
662 // Re-save wpseo to make sure bf_banner_2019_dismissed key is gone.
663 $this->cleanup_option_data( 'wpseo' );
664
665 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-page_comments-notice' );
666 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-wordpress-upgrade' );
667 }
668
669 /**
670 * Performs the 13.2 upgrade.
671 */
672 private function upgrade_132() {
673 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-tagline-notice' );
674 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-permalink-notice' );
675 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-onpageorg' );
676
677 // Transfers the onpage option value to the ryte option.
678 $ryte_option = get_option( 'wpseo_ryte' );
679 $onpage_option = get_option( 'wpseo_onpage' );
680 if ( ! $ryte_option && $onpage_option ) {
681 update_option( 'wpseo_ryte', $onpage_option );
682 delete_option( 'wpseo_onpage' );
683 }
684
685 // Changes onpage_indexability to ryte_indexability.
686 $wpseo_option = get_option( 'wpseo' );
687 if ( isset( $wpseo_option['onpage_indexability'] ) && ! isset( $wpseo_option['ryte_indexability'] ) ) {
688 $wpseo_option['ryte_indexability'] = $wpseo_option['onpage_indexability'];
689 unset( $wpseo_option['onpage_indexability'] );
690 update_option( 'wpseo', $wpseo_option );
691 }
692
693 if ( wp_next_scheduled( 'wpseo_ryte_fetch' ) ) {
694 wp_clear_scheduled_hook( 'wpseo_ryte_fetch' );
695 }
696
697 /*
698 * Re-register capabilities to add the new `view_site_health_checks`
699 * capability to the SEO Manager role.
700 */
701 do_action( 'wpseo_register_capabilities' );
702 WPSEO_Capability_Manager_Factory::get()->add();
703 }
704
705 /**
706 * Perform the 14.0.3 upgrade.
707 */
708 private function upgrade_1403() {
709 WPSEO_Options::set( 'ignore_indexation_warning', false );
710 }
711
712 /**
713 * Performs the 14.1 upgrade.
714 */
715 private function upgrade_141() {
716 /*
717 * These notifications are retrieved from storage on the `init` hook with
718 * priority 1. We need to remove them after they're retrieved.
719 */
720 add_action( 'init', [ $this, 'remove_notifications_for_141' ] );
721 add_action( 'init', [ $this, 'clean_up_private_taxonomies_for_141' ] );
722
723 $this->reset_permalinks_of_attachments_for_141();
724 }
725
726 /**
727 * Performs the 14.2 upgrade.
728 *
729 * Removes the yoast-acf-analysis notice when it's still in the database.
730 */
731 private function upgrade_142() {
732 add_action( 'init', [ $this, 'remove_acf_notification_for_142' ] );
733 }
734
735 /**
736 * Performs the 14.5 upgrade.
737 */
738 private function upgrade_145() {
739 add_action( 'init', [ $this, 'set_indexation_completed_option_for_145' ] );
740 }
741
742 /**
743 * Performs the 14.9 upgrade.
744 */
745 private function upgrade_149() {
746 $version = get_option( 'wpseo_license_server_version', 2 );
747 WPSEO_Options::set( 'license_server_version', $version );
748 delete_option( 'wpseo_license_server_version' );
749 }
750
751 /**
752 * Performs the 15.1 upgrade.
753 *
754 * @return void
755 */
756 private function upgrade_151() {
757 $this->set_home_url_for_151();
758 $this->move_indexables_indexation_reason_for_151();
759
760 add_action( 'init', [ $this, 'set_permalink_structure_option_for_151' ] );
761 add_action( 'init', [ $this, 'store_custom_taxonomy_slugs_for_151' ] );
762 }
763
764 /**
765 * Performs the 15.3 upgrade.
766 *
767 * @return void
768 */
769 private function upgrade_153() {
770 WPSEO_Options::set( 'category_base_url', get_option( 'category_base' ) );
771 WPSEO_Options::set( 'tag_base_url', get_option( 'tag_base' ) );
772
773 // Rename a couple of options.
774 $indexation_started_value = WPSEO_Options::get( 'indexation_started' );
775 WPSEO_Options::set( 'indexing_started', $indexation_started_value );
776
777 $indexables_indexing_completed_value = WPSEO_Options::get( 'indexables_indexation_completed' );
778 WPSEO_Options::set( 'indexables_indexing_completed', $indexables_indexing_completed_value );
779 }
780
781 /**
782 * Performs the 15.5 upgrade.
783 *
784 * @return void
785 */
786 private function upgrade_155() {
787 // Unset the fbadminapp value in the wpseo_social option.
788 $wpseo_social_option = get_option( 'wpseo_social' );
789
790 if ( isset( $wpseo_social_option['fbadminapp'] ) ) {
791 unset( $wpseo_social_option['fbadminapp'] );
792 update_option( 'wpseo_social', $wpseo_social_option );
793 }
794 }
795
796 /**
797 * Performs the 15.7 upgrade.
798 *
799 * @return void
800 */
801 private function upgrade_157() {
802 add_action( 'init', [ $this, 'remove_plugin_updated_notification_for_157' ] );
803 }
804
805 /**
806 * Performs the 15.9.1 upgrade routine.
807 */
808 private function upgrade_1591() {
809 $enabled_auto_updates = \get_option( 'auto_update_plugins' );
810 $addon_update_watcher = YoastSEO()->classes->get( \Yoast\WP\SEO\Integrations\Watchers\Addon_Update_Watcher::class );
811 $addon_update_watcher->toggle_auto_updates_for_add_ons( 'auto_update_plugins', [], $enabled_auto_updates );
812 }
813
814 /**
815 * Performs the 16.2 upgrade routine.
816 */
817 private function upgrade_162() {
818 $enabled_auto_updates = \get_site_option( 'auto_update_plugins' );
819 $addon_update_watcher = YoastSEO()->classes->get( \Yoast\WP\SEO\Integrations\Watchers\Addon_Update_Watcher::class );
820 $addon_update_watcher->toggle_auto_updates_for_add_ons( 'auto_update_plugins', $enabled_auto_updates, [] );
821 }
822
823 /**
824 * Performs the 16.5 upgrade.
825 *
826 * @return void
827 */
828 private function upgrade_165() {
829 add_action( 'init', [ $this, 'copy_og_settings_from_social_to_titles' ], 99 );
830
831 // Run after the WPSEO_Options::enrich_defaults method which has priority 99.
832 add_action( 'init', [ $this, 'reset_og_settings_to_default_values' ], 100 );
833 }
834
835 /**
836 * Performs the 17.2 upgrade. Cleans out any unnecessary indexables. See $cleanup_integration->get_cleanup_tasks() to see what will be cleaned out.
837 *
838 * @return void
839 */
840 private function upgrade_172() {
841 \wp_unschedule_hook( 'wpseo_cleanup_orphaned_indexables' );
842 \wp_unschedule_hook( 'wpseo_cleanup_indexables' );
843
844 if ( ! \wp_next_scheduled( \Yoast\WP\SEO\Integrations\Cleanup_Integration::START_HOOK ) ) {
845 \wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), \Yoast\WP\SEO\Integrations\Cleanup_Integration::START_HOOK );
846 }
847 }
848
849 /**
850 * Performs the 17.7.1 upgrade routine.
851 */
852 private function upgrade_1771() {
853 $enabled_auto_updates = \get_site_option( 'auto_update_plugins' );
854 $addon_update_watcher = YoastSEO()->classes->get( \Yoast\WP\SEO\Integrations\Watchers\Addon_Update_Watcher::class );
855 $addon_update_watcher->toggle_auto_updates_for_add_ons( 'auto_update_plugins', $enabled_auto_updates, [] );
856 }
857
858 /**
859 * Performs the 17.9 upgrade routine.
860 */
861 private function upgrade_179() {
862 WPSEO_Options::set( 'wincher_integration_active', true );
863 }
864
865 /**
866 * Sets the home_url option for the 15.1 upgrade routine.
867 *
868 * @return void
869 */
870 protected function set_home_url_for_151() {
871 $home_url = WPSEO_Options::get( 'home_url' );
872
873 if ( empty( $home_url ) ) {
874 WPSEO_Options::set( 'home_url', get_home_url() );
875 }
876 }
877
878 /**
879 * Moves the `indexables_indexation_reason` option to the
880 * renamed `indexing_reason` option.
881 *
882 * @return void
883 */
884 protected function move_indexables_indexation_reason_for_151() {
885 $reason = WPSEO_Options::get( 'indexables_indexation_reason', '' );
886 WPSEO_Options::set( 'indexing_reason', $reason );
887 }
888
889 /**
890 * Checks if the indexable indexation is completed.
891 * If so, sets the `indexables_indexation_completed` option to `true`,
892 * else to `false`.
893 */
894 public function set_indexation_completed_option_for_145() {
895 WPSEO_Options::set( 'indexables_indexation_completed', YoastSEO()->helpers->indexing->get_limited_filtered_unindexed_count( 1 ) === 0 );
896 }
897
898 /**
899 * Cleans up the private taxonomies from the indexables table for the upgrade routine to 14.1.
900 */
901 public function clean_up_private_taxonomies_for_141() {
902 global $wpdb;
903
904 // If migrations haven't been completed successfully the following may give false errors. So suppress them.
905 $show_errors = $wpdb->show_errors;
906 $wpdb->show_errors = false;
907
908 // Clean up indexables of private taxonomies.
909 $private_taxonomies = \get_taxonomies( [ 'public' => false ], 'names' );
910
911 if ( empty( $private_taxonomies ) ) {
912 return;
913 }
914
915 $indexable_table = Model::get_table_name( 'Indexable' );
916
917 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
918 $query = $wpdb->prepare(
919 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: Too hard to fix.
920 "DELETE FROM $indexable_table
921 WHERE object_type = 'term'
922 AND object_sub_type IN ("
923 . \implode( ', ', \array_fill( 0, \count( $private_taxonomies ), '%s' ) )
924 . ')',
925 $private_taxonomies
926 );
927 $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
928
929 $wpdb->show_errors = $show_errors;
930 }
931
932 /**
933 * Resets the permalinks of attachments to `null` in the indexable table for the upgrade routine to 14.1.
934 */
935 private function reset_permalinks_of_attachments_for_141() {
936 global $wpdb;
937
938 // If migrations haven't been completed succesfully the following may give false errors. So suppress them.
939 $show_errors = $wpdb->show_errors;
940 $wpdb->show_errors = false;
941
942 // Reset the permalinks of the attachments in the indexable table.
943 $indexable_table = Model::get_table_name( 'Indexable' );
944 $query = "UPDATE $indexable_table SET permalink = NULL WHERE object_type = 'post' AND object_sub_type = 'attachment'";
945 $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Reason: There is no user input.
946
947 $wpdb->show_errors = $show_errors;
948 }
949
950 /**
951 * Removes notifications from the Notification center for the 14.1 upgrade.
952 *
953 * @return void
954 */
955 public function remove_notifications_for_141() {
956 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-recalculate' );
957 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-blog-public-notice' );
958 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-links-table-not-accessible' );
959 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-post-type-archive-notification' );
960 }
961
962 /**
963 * Removes the wpseo-suggested-plugin-yoast-acf-analysis notification from the Notification center for the 14.2 upgrade.
964 *
965 * @return void
966 */
967 public function remove_acf_notification_for_142() {
968 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-suggested-plugin-yoast-acf-analysis' );
969 }
970
971 /**
972 * Removes the wpseo-plugin-updated notification from the Notification center for the 15.7 upgrade.
973 *
974 * @return void
975 */
976 public function remove_plugin_updated_notification_for_157() {
977 Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-plugin-updated' );
978 }
979
980 /**
981 * Removes all notifications saved in the database under 'wp_yoast_notifications'.
982 *
983 * @return void
984 */
985 private function clean_all_notifications() {
986 global $wpdb;
987 delete_metadata( 'user', 0, $wpdb->get_blog_prefix() . Yoast_Notification_Center::STORAGE_KEY, '', true );
988 }
989
990 /**
991 * Removes the post meta fields for a given meta key.
992 *
993 * @param string $meta_key The meta key.
994 *
995 * @return void
996 */
997 private function delete_post_meta( $meta_key ) {
998 global $wpdb;
999 $deleted = $wpdb->delete( $wpdb->postmeta, [ 'meta_key' => $meta_key ], [ '%s' ] );
1000
1001 if ( $deleted ) {
1002 wp_cache_set( 'last_changed', microtime(), 'posts' );
1003 }
1004 }
1005
1006 /**
1007 * Removes all sitemap validators.
1008 *
1009 * This should be executed on every upgrade routine until we have removed the sitemap caching in the database.
1010 *
1011 * @return void
1012 */
1013 private function remove_sitemap_validators() {
1014 global $wpdb;
1015
1016 // Remove all sitemap validators.
1017 $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'wpseo_sitemap%validator%'" );
1018 }
1019
1020 /**
1021 * Retrieves the option value directly from the database.
1022 *
1023 * @param string $option_name Option to retrieve.
1024 *
1025 * @return array|mixed The content of the option if exists, otherwise an empty array.
1026 */
1027 protected function get_option_from_database( $option_name ) {
1028 global $wpdb;
1029
1030 // Load option directly from the database, to avoid filtering and sanitization.
1031 $sql = $wpdb->prepare( 'SELECT option_value FROM ' . $wpdb->options . ' WHERE option_name = %s', $option_name );
1032 $results = $wpdb->get_results( $sql, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is is already prepared.
1033 if ( ! empty( $results ) ) {
1034 return maybe_unserialize( $results[0]['option_value'] );
1035 }
1036
1037 return [];
1038 }
1039
1040 /**
1041 * Cleans the option to make sure only relevant settings are there.
1042 *
1043 * @param string $option_name Option name save.
1044 *
1045 * @return void
1046 */
1047 protected function cleanup_option_data( $option_name ) {
1048 $data = get_option( $option_name, [] );
1049 if ( ! is_array( $data ) || $data === [] ) {
1050 return;
1051 }
1052
1053 /*
1054 * Clean up the option by re-saving it.
1055 *
1056 * The option framework will remove any settings that are not configured
1057 * for this option, removing any migrated settings.
1058 */
1059 update_option( $option_name, $data );
1060 }
1061
1062 /**
1063 * Saves an option setting to where it should be stored.
1064 *
1065 * @param array $source_data The option containing the value to be migrated.
1066 * @param string $source_setting Name of the key in the "from" option.
1067 * @param string|null $target_setting Name of the key in the "to" option.
1068 *
1069 * @return void
1070 */
1071 protected function save_option_setting( $source_data, $source_setting, $target_setting = null ) {
1072 if ( $target_setting === null ) {
1073 $target_setting = $source_setting;
1074 }
1075
1076 if ( isset( $source_data[ $source_setting ] ) ) {
1077 WPSEO_Options::set( $target_setting, $source_data[ $source_setting ] );
1078 }
1079 }
1080
1081 /**
1082 * Migrates WooCommerce archive settings to the WooCommerce Shop page meta-data settings.
1083 *
1084 * If no Shop page is defined, nothing will be migrated.
1085 *
1086 * @return void
1087 */
1088 private function migrate_woocommerce_archive_setting_to_shop_page() {
1089 $shop_page_id = wc_get_page_id( 'shop' );
1090
1091 if ( $shop_page_id === -1 ) {
1092 return;
1093 }
1094
1095 $title = WPSEO_Meta::get_value( 'title', $shop_page_id );
1096
1097 if ( empty( $title ) ) {
1098 $option_title = WPSEO_Options::get( 'title-ptarchive-product' );
1099
1100 WPSEO_Meta::set_value(
1101 'title',
1102 $option_title,
1103 $shop_page_id
1104 );
1105
1106 WPSEO_Options::set( 'title-ptarchive-product', '' );
1107 }
1108
1109 $meta_description = WPSEO_Meta::get_value( 'metadesc', $shop_page_id );
1110
1111 if ( empty( $meta_description ) ) {
1112 $option_metadesc = WPSEO_Options::get( 'metadesc-ptarchive-product' );
1113
1114 WPSEO_Meta::set_value(
1115 'metadesc',
1116 $option_metadesc,
1117 $shop_page_id
1118 );
1119
1120 WPSEO_Options::set( 'metadesc-ptarchive-product', '' );
1121 }
1122
1123 $bc_title = WPSEO_Meta::get_value( 'bctitle', $shop_page_id );
1124
1125 if ( empty( $bc_title ) ) {
1126 $option_bctitle = WPSEO_Options::get( 'bctitle-ptarchive-product' );
1127
1128 WPSEO_Meta::set_value(
1129 'bctitle',
1130 $option_bctitle,
1131 $shop_page_id
1132 );
1133
1134 WPSEO_Options::set( 'bctitle-ptarchive-product', '' );
1135 }
1136
1137 $noindex = WPSEO_Meta::get_value( 'meta-robots-noindex', $shop_page_id );
1138
1139 if ( $noindex === '0' ) {
1140 $option_noindex = WPSEO_Options::get( 'noindex-ptarchive-product' );
1141
1142 WPSEO_Meta::set_value(
1143 'meta-robots-noindex',
1144 $option_noindex,
1145 $shop_page_id
1146 );
1147
1148 WPSEO_Options::set( 'noindex-ptarchive-product', false );
1149 }
1150 }
1151
1152 /**
1153 * Stores the initial `permalink_structure` option.
1154 *
1155 * @return void
1156 */
1157 public function set_permalink_structure_option_for_151() {
1158 WPSEO_Options::set( 'permalink_structure', get_option( 'permalink_structure' ) );
1159 }
1160
1161 /**
1162 * Stores the initial slugs of custom taxonomies.
1163 *
1164 * @return void
1165 */
1166 public function store_custom_taxonomy_slugs_for_151() {
1167 $taxonomies = $this->taxonomy_helper->get_custom_taxonomies();
1168
1169 $custom_taxonomies = [];
1170
1171 foreach ( $taxonomies as $taxonomy ) {
1172 $slug = $this->taxonomy_helper->get_taxonomy_slug( $taxonomy );
1173
1174 $custom_taxonomies[ $taxonomy ] = $slug;
1175 }
1176
1177 WPSEO_Options::set( 'custom_taxonomy_slugs', $custom_taxonomies );
1178 }
1179
1180 /**
1181 * Copies the frontpage social settings to the titles options.
1182 *
1183 * @return void
1184 */
1185 public function copy_og_settings_from_social_to_titles() {
1186 $wpseo_social = get_option( 'wpseo_social' );
1187 $wpseo_titles = get_option( 'wpseo_titles' );
1188
1189 $copied_options = [];
1190 // Reset to the correct default value.
1191 $copied_options['open_graph_frontpage_title'] = '%%sitename%%';
1192
1193 $options = [
1194 'og_frontpage_title' => 'open_graph_frontpage_title',
1195 'og_frontpage_desc' => 'open_graph_frontpage_desc',
1196 'og_frontpage_image' => 'open_graph_frontpage_image',
1197 'og_frontpage_image_id' => 'open_graph_frontpage_image_id',
1198 ];
1199
1200 foreach ( $options as $social_option => $titles_option ) {
1201 if ( ! empty( $wpseo_social[ $social_option ] ) ) {
1202 $copied_options[ $titles_option ] = $wpseo_social[ $social_option ];
1203 }
1204 }
1205
1206 $wpseo_titles = array_merge( $wpseo_titles, $copied_options );
1207
1208 update_option( 'wpseo_titles', $wpseo_titles );
1209 }
1210
1211 /**
1212 * Reset the social options with the correct default values.
1213 *
1214 * @return void
1215 */
1216 public function reset_og_settings_to_default_values() {
1217 $wpseo_titles = get_option( 'wpseo_titles' );
1218 $updated_options = [];
1219
1220 $updated_options['social-title-author-wpseo'] = '%%name%%';
1221 $updated_options['social-title-archive-wpseo'] = '%%date%%';
1222
1223 /* translators: %s expands to the name of a post type (plural). */
1224 $post_type_archive_default = sprintf( __( '%s Archive', 'wordpress-seo' ), '%%pt_plural%%' );
1225
1226 /* translators: %s expands to the variable used for term title. */
1227 $term_archive_default = sprintf( __( '%s Archives', 'wordpress-seo' ), '%%term_title%%' );
1228
1229 $post_type_objects = get_post_types( [ 'public' => true ], 'objects' );
1230
1231 if ( $post_type_objects ) {
1232 foreach ( $post_type_objects as $pt ) {
1233 // Post types.
1234 if ( isset( $wpseo_titles[ 'social-title-' . $pt->name ] ) ) {
1235 $updated_options[ 'social-title-' . $pt->name ] = '%%title%%';
1236 }
1237 // Post type archives.
1238 if ( isset( $wpseo_titles[ 'social-title-ptarchive-' . $pt->name ] ) ) {
1239 $updated_options[ 'social-title-ptarchive-' . $pt->name ] = $post_type_archive_default;
1240 }
1241 }
1242 }
1243
1244 $taxonomy_objects = get_taxonomies( [ 'public' => true ], 'object' );
1245
1246 if ( $taxonomy_objects ) {
1247 foreach ( $taxonomy_objects as $tax ) {
1248 if ( isset( $wpseo_titles[ 'social-title-tax-' . $tax->name ] ) ) {
1249 $updated_options[ 'social-title-tax-' . $tax->name ] = $term_archive_default;
1250 }
1251 }
1252 }
1253
1254 $wpseo_titles = array_merge( $wpseo_titles, $updated_options );
1255
1256 update_option( 'wpseo_titles', $wpseo_titles );
1257 }
1258 }
1259