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

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