PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.8
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.8
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.8, at inc/class-upgrade.php

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