• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 59517635615d055f783a2be92e52a1e2637df1be

17 Feb 2025 11:08AM UTC coverage: 53.377% (-1.3%) from 54.636%
59517635615d055f783a2be92e52a1e2637df1be

Pull #22048

github

web-flow
Merge e41dbb150 into 711656c23
Pull Request #22048: Update Dashboard page description

7808 of 13867 branches covered (56.31%)

Branch coverage included in aggregate %.

4 of 5 new or added lines in 2 files covered. (80.0%)

1554 existing lines in 42 files now uncovered.

30279 of 57488 relevant lines covered (52.67%)

40022.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

28.93
/inc/class-upgrade.php
1
<?php
2
/**
3
 * WPSEO plugin file.
4
 *
5
 * @package WPSEO\Internal
6
 */
7

8
use Yoast\WP\Lib\Model;
9
use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
10
use Yoast\WP\SEO\Integrations\Cleanup_Integration;
11
use Yoast\WP\SEO\Integrations\Watchers\Addon_Update_Watcher;
12

13
/**
14
 * This code handles the option upgrades.
15
 */
16
class WPSEO_Upgrade {
17

18
        /**
19
         * The taxonomy helper.
20
         *
21
         * @var Taxonomy_Helper
22
         */
23
        private $taxonomy_helper;
24

25
        /**
26
         * Class constructor.
27
         */
28
        public function __construct() {
×
29
                $this->taxonomy_helper = YoastSEO()->helpers->taxonomy;
×
30

31
                $version = WPSEO_Options::get( 'version' );
×
32

33
                WPSEO_Options::maybe_set_multisite_defaults( false );
×
34

UNCOV
35
                $routines = [
×
36
                        '1.5.0'      => 'upgrade_15',
×
UNCOV
37
                        '2.0'        => 'upgrade_20',
×
UNCOV
38
                        '2.1'        => 'upgrade_21',
×
UNCOV
39
                        '2.2'        => 'upgrade_22',
×
UNCOV
40
                        '2.3'        => 'upgrade_23',
×
UNCOV
41
                        '3.0'        => 'upgrade_30',
×
UNCOV
42
                        '3.3'        => 'upgrade_33',
×
UNCOV
43
                        '3.6'        => 'upgrade_36',
×
UNCOV
44
                        '4.0'        => 'upgrade_40',
×
UNCOV
45
                        '4.4'        => 'upgrade_44',
×
UNCOV
46
                        '4.7'        => 'upgrade_47',
×
UNCOV
47
                        '4.9'        => 'upgrade_49',
×
UNCOV
48
                        '5.0'        => 'upgrade_50',
×
UNCOV
49
                        '5.5'        => 'upgrade_55',
×
UNCOV
50
                        '6.3'        => 'upgrade_63',
×
UNCOV
51
                        '7.0-RC0'    => 'upgrade_70',
×
UNCOV
52
                        '7.1-RC0'    => 'upgrade_71',
×
UNCOV
53
                        '7.3-RC0'    => 'upgrade_73',
×
UNCOV
54
                        '7.4-RC0'    => 'upgrade_74',
×
UNCOV
55
                        '7.5.3'      => 'upgrade_753',
×
UNCOV
56
                        '7.7-RC0'    => 'upgrade_77',
×
UNCOV
57
                        '7.7.2-RC0'  => 'upgrade_772',
×
UNCOV
58
                        '9.0-RC0'    => 'upgrade_90',
×
UNCOV
59
                        '10.0-RC0'   => 'upgrade_100',
×
UNCOV
60
                        '11.1-RC0'   => 'upgrade_111',
×
61
                        // Reset notifications because we removed the AMP Glue plugin notification.
UNCOV
62
                        '12.1-RC0'   => 'clean_all_notifications',
×
UNCOV
63
                        '12.3-RC0'   => 'upgrade_123',
×
UNCOV
64
                        '12.4-RC0'   => 'upgrade_124',
×
UNCOV
65
                        '12.8-RC0'   => 'upgrade_128',
×
UNCOV
66
                        '13.2-RC0'   => 'upgrade_132',
×
UNCOV
67
                        '14.0.3-RC0' => 'upgrade_1403',
×
UNCOV
68
                        '14.1-RC0'   => 'upgrade_141',
×
UNCOV
69
                        '14.2-RC0'   => 'upgrade_142',
×
UNCOV
70
                        '14.5-RC0'   => 'upgrade_145',
×
UNCOV
71
                        '14.9-RC0'   => 'upgrade_149',
×
UNCOV
72
                        '15.1-RC0'   => 'upgrade_151',
×
UNCOV
73
                        '15.3-RC0'   => 'upgrade_153',
×
UNCOV
74
                        '15.5-RC0'   => 'upgrade_155',
×
UNCOV
75
                        '15.7-RC0'   => 'upgrade_157',
×
UNCOV
76
                        '15.9.1-RC0' => 'upgrade_1591',
×
UNCOV
77
                        '16.2-RC0'   => 'upgrade_162',
×
UNCOV
78
                        '16.5-RC0'   => 'upgrade_165',
×
UNCOV
79
                        '17.2-RC0'   => 'upgrade_172',
×
UNCOV
80
                        '17.7.1-RC0' => 'upgrade_1771',
×
UNCOV
81
                        '17.9-RC0'   => 'upgrade_179',
×
UNCOV
82
                        '18.3-RC3'   => 'upgrade_183',
×
UNCOV
83
                        '18.6-RC0'   => 'upgrade_186',
×
UNCOV
84
                        '18.9-RC0'   => 'upgrade_189',
×
UNCOV
85
                        '19.1-RC0'   => 'upgrade_191',
×
UNCOV
86
                        '19.3-RC0'   => 'upgrade_193',
×
UNCOV
87
                        '19.6-RC0'   => 'upgrade_196',
×
UNCOV
88
                        '19.11-RC0'  => 'upgrade_1911',
×
UNCOV
89
                        '20.2-RC0'   => 'upgrade_202',
×
UNCOV
90
                        '20.5-RC0'   => 'upgrade_205',
×
UNCOV
91
                        '20.7-RC0'   => 'upgrade_207',
×
UNCOV
92
                        '20.8-RC0'   => 'upgrade_208',
×
UNCOV
93
                        '22.6-RC0'   => 'upgrade_226',
×
UNCOV
94
                ];
×
95

96
                array_walk( $routines, [ $this, 'run_upgrade_routine' ], $version );
×
97
                if ( version_compare( $version, '12.5-RC0', '<' ) ) {
×
98
                        /*
99
                         * We have to run this by hook, because otherwise:
100
                         * - the theme support check isn't available.
101
                         * - the notification center notifications are not filled yet.
102
                         */
103
                        add_action( 'init', [ $this, 'upgrade_125' ] );
×
104
                }
105

106
                /**
107
                 * Filter: 'wpseo_run_upgrade' - Runs the upgrade hook which are dependent on Yoast SEO.
108
                 *
109
                 * @param string $version The current version of Yoast SEO
110
                 */
111
                do_action( 'wpseo_run_upgrade', $version );
×
112

113
                $this->finish_up( $version );
×
114
        }
115

116
        /**
117
         * Runs the upgrade routine.
118
         *
119
         * @param string $routine         The method to call.
120
         * @param string $version         The new version.
121
         * @param string $current_version The current set version.
122
         *
123
         * @return void
124
         */
125
        protected function run_upgrade_routine( $routine, $version, $current_version ) {
×
126
                if ( version_compare( $current_version, $version, '<' ) ) {
×
127
                        $this->$routine( $current_version );
×
128
                }
129
        }
130

131
        /**
132
         * Adds a new upgrade history entry.
133
         *
134
         * @param string $current_version The old version from which we are upgrading.
135
         * @param string $new_version     The version we are upgrading to.
136
         *
137
         * @return void
138
         */
139
        protected function add_upgrade_history( $current_version, $new_version ) {
×
140
                $upgrade_history = new WPSEO_Upgrade_History();
×
141
                $upgrade_history->add( $current_version, $new_version, array_keys( WPSEO_Options::$options ) );
×
142
        }
143

144
        /**
145
         * Runs the needed cleanup after an update, setting the DB version to latest version, flushing caches etc.
146
         *
147
         * @param string|null $previous_version The previous version.
148
         *
149
         * @return void
150
         */
151
        protected function finish_up( $previous_version = null ) {
2✔
152
                if ( $previous_version ) {
2✔
153
                        WPSEO_Options::set( 'previous_version', $previous_version, 'wpseo' );
×
154
                }
155
                WPSEO_Options::set( 'version', WPSEO_VERSION, 'wpseo' );
2✔
156

157
                // Just flush rewrites, always, to at least make them work after an upgrade.
158
                add_action( 'shutdown', 'flush_rewrite_rules' );
2✔
159

160
                // Flush the sitemap cache.
161
                WPSEO_Sitemaps_Cache::clear();
2✔
162

163
                // Make sure all our options always exist - issue #1245.
164
                WPSEO_Options::ensure_options_exist();
2✔
165
        }
166

167
        /**
168
         * Run the Yoast SEO 1.5 upgrade routine.
169
         *
170
         * @param string $version Current plugin version.
171
         *
172
         * @return void
173
         */
174
        private function upgrade_15( $version ) {
×
175
                // Clean up options and meta.
176
                WPSEO_Options::clean_up( null, $version );
×
177
                WPSEO_Meta::clean_up();
×
178
        }
179

180
        /**
181
         * Moves options that moved position in WPSEO 2.0.
182
         *
183
         * @return void
184
         */
185
        private function upgrade_20() {
×
186
                /**
187
                 * Clean up stray wpseo_ms options from the options table, option should only exist in the sitemeta table.
188
                 * This could have been caused in many version of Yoast SEO, so deleting it for everything below 2.0.
189
                 */
190
                delete_option( 'wpseo_ms' );
×
191

192
                $wpseo = $this->get_option_from_database( 'wpseo' );
×
193
                $this->save_option_setting( $wpseo, 'pinterestverify' );
×
194

195
                // Re-save option to trigger sanitization.
196
                $this->cleanup_option_data( 'wpseo' );
×
197
        }
198

199
        /**
200
         * Detects if taxonomy terms were split and updates the corresponding taxonomy meta's accordingly.
201
         *
202
         * @return void
203
         */
204
        private function upgrade_21() {
×
205
                $taxonomies = get_option( 'wpseo_taxonomy_meta', [] );
×
206

207
                if ( ! empty( $taxonomies ) ) {
×
208
                        foreach ( $taxonomies as $taxonomy => $tax_metas ) {
×
209
                                foreach ( $tax_metas as $term_id => $tax_meta ) {
×
210
                                        if ( function_exists( 'wp_get_split_term' ) ) {
×
211
                                                $new_term_id = wp_get_split_term( $term_id, $taxonomy );
×
212
                                                if ( $new_term_id !== false ) {
×
213
                                                        $taxonomies[ $taxonomy ][ $new_term_id ] = $taxonomies[ $taxonomy ][ $term_id ];
×
214
                                                        unset( $taxonomies[ $taxonomy ][ $term_id ] );
×
215
                                                }
216
                                        }
217
                                }
218
                        }
219

220
                        update_option( 'wpseo_taxonomy_meta', $taxonomies );
×
221
                }
222
        }
223

224
        /**
225
         * Performs upgrade functions to Yoast SEO 2.2.
226
         *
227
         * @return void
228
         */
229
        private function upgrade_22() {
×
230
                // Unschedule our tracking.
231
                wp_clear_scheduled_hook( 'yoast_tracking' );
×
232

233
                $this->cleanup_option_data( 'wpseo' );
×
234
        }
235

236
        /**
237
         * Schedules upgrade function to Yoast SEO 2.3.
238
         *
239
         * @return void
240
         */
241
        private function upgrade_23() {
×
242
                add_action( 'wp', [ $this, 'upgrade_23_query' ], 90 );
×
243
                add_action( 'admin_head', [ $this, 'upgrade_23_query' ], 90 );
×
244
        }
245

246
        /**
247
         * Performs upgrade query to Yoast SEO 2.3.
248
         *
249
         * @return void
250
         */
251
        public function upgrade_23_query() {
×
252
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: executed only during the upgrade routine.
253
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Reason: executed only during the upgrade routine.
254
                $wp_query = new WP_Query( 'post_type=any&meta_key=_yoast_wpseo_sitemap-include&meta_value=never&order=ASC' );
×
255

256
                if ( ! empty( $wp_query->posts ) ) {
×
257
                        $options = get_option( 'wpseo_xml' );
×
258

259
                        $excluded_posts = [];
×
260
                        if ( $options['excluded-posts'] !== '' ) {
×
261
                                $excluded_posts = explode( ',', $options['excluded-posts'] );
×
262
                        }
263

264
                        foreach ( $wp_query->posts as $post ) {
×
265
                                if ( ! in_array( (string) $post->ID, $excluded_posts, true ) ) {
×
266
                                        $excluded_posts[] = $post->ID;
×
267
                                }
268
                        }
269

270
                        // Updates the meta value.
271
                        $options['excluded-posts'] = implode( ',', $excluded_posts );
×
272

273
                        // Update the option.
274
                        update_option( 'wpseo_xml', $options );
×
275
                }
276

277
                // Remove the meta fields.
278
                delete_post_meta_by_key( '_yoast_wpseo_sitemap-include' );
×
279
        }
280

281
        /**
282
         * Performs upgrade functions to Yoast SEO 3.0.
283
         *
284
         * @return void
285
         */
286
        private function upgrade_30() {
×
287
                // Remove the meta fields for sitemap prio.
288
                delete_post_meta_by_key( '_yoast_wpseo_sitemap-prio' );
×
289
        }
290

291
        /**
292
         * Performs upgrade functions to Yoast SEO 3.3.
293
         *
294
         * @return void
295
         */
296
        private function upgrade_33() {
×
297
                // Notification dismissals have been moved to User Meta instead of global option.
298
                delete_option( Yoast_Notification_Center::STORAGE_KEY );
×
299
        }
300

301
        /**
302
         * Performs upgrade functions to Yoast SEO 3.6.
303
         *
304
         * @return void
305
         */
306
        protected function upgrade_36() {
2✔
307
                global $wpdb;
2✔
308

309
                // Between 3.2 and 3.4 the sitemap options were saved with autoloading enabled.
310
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
311
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
312
                $wpdb->query(
2✔
313
                        $wpdb->prepare(
2✔
314
                                'DELETE FROM %i WHERE %i LIKE %s AND autoload IN ("on", "yes")',
2✔
315
                                [ $wpdb->options, 'option_name', 'wpseo_sitemap_%' ]
2✔
316
                        )
2✔
317
                );
2✔
318
        }
319

320
        /**
321
         * Removes the about notice when its still in the database.
322
         *
323
         * @return void
324
         */
325
        private function upgrade_40() {
×
326
                $center = Yoast_Notification_Center::get();
×
327
                $center->remove_notification_by_id( 'wpseo-dismiss-about' );
×
328
        }
329

330
        /**
331
         * Moves the content-analysis-active and keyword-analysis-acive options from wpseo-titles to wpseo.
332
         *
333
         * @return void
334
         */
335
        private function upgrade_44() {
×
336
                $wpseo_titles = $this->get_option_from_database( 'wpseo_titles' );
×
337

338
                $this->save_option_setting( $wpseo_titles, 'content-analysis-active', 'content_analysis_active' );
×
339
                $this->save_option_setting( $wpseo_titles, 'keyword-analysis-active', 'keyword_analysis_active' );
×
340

341
                // Remove irrelevant content from the option.
342
                $this->cleanup_option_data( 'wpseo_titles' );
×
343
        }
344

345
        /**
346
         * Renames the meta name for the cornerstone content. It was a public meta field and it has to be private.
347
         *
348
         * @return void
349
         */
350
        private function upgrade_47() {
×
351
                global $wpdb;
×
352

353
                // The meta key has to be private, so prefix it.
354
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
355
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
356
                $wpdb->query(
×
357
                        $wpdb->prepare(
×
358
                                'UPDATE ' . $wpdb->postmeta . ' SET meta_key = %s WHERE meta_key = "yst_is_cornerstone"',
×
359
                                WPSEO_Cornerstone_Filter::META_NAME
×
UNCOV
360
                        )
×
UNCOV
361
                );
×
362
        }
363

364
        /**
365
         * Removes the 'wpseo-dismiss-about' notice for every user that still has it.
366
         *
367
         * @return void
368
         */
369
        protected function upgrade_49() {
2✔
370
                global $wpdb;
2✔
371

372
                /*
373
                 * Using a filter to remove the notification for the current logged in user. The notification center is
374
                 * initializing the notifications before the upgrade routine has been executedd and is saving the stored
375
                 * notifications on shutdown. This causes the returning notification. By adding this filter the shutdown
376
                 * routine on the notification center will remove the notification.
377
                 */
378
                add_filter( 'yoast_notifications_before_storage', [ $this, 'remove_about_notice' ] );
2✔
379

380
                $meta_key = $wpdb->get_blog_prefix() . Yoast_Notification_Center::STORAGE_KEY;
2✔
381

382
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
383
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
384
                $usermetas = $wpdb->get_results(
2✔
385
                        $wpdb->prepare(
2✔
386
                                '
2✔
387
                                SELECT %i, %i
388
                                FROM %i
389
                                WHERE %i = %s AND %i LIKE %s
390
                                ',
2✔
391
                                [ 'user_id', 'meta_value', $wpdb->usermeta, 'meta_key', $meta_key, 'meta_value', '%wpseo-dismiss-about%' ]
2✔
392
                        ),
2✔
393
                        ARRAY_A
2✔
394
                );
2✔
395

396
                if ( empty( $usermetas ) ) {
2✔
397
                        return;
×
398
                }
399

400
                foreach ( $usermetas as $usermeta ) {
2✔
401
                        $notifications = maybe_unserialize( $usermeta['meta_value'] );
2✔
402

403
                        foreach ( $notifications as $notification_key => $notification ) {
2✔
404
                                if ( ! empty( $notification['options']['id'] ) && $notification['options']['id'] === 'wpseo-dismiss-about' ) {
2✔
405
                                        unset( $notifications[ $notification_key ] );
2✔
406
                                }
407
                        }
408

409
                        update_user_option( $usermeta['user_id'], Yoast_Notification_Center::STORAGE_KEY, array_values( $notifications ) );
2✔
410
                }
411
        }
412

413
        /**
414
         * Removes the wpseo-dismiss-about notice from a list of notifications.
415
         *
416
         * @param Yoast_Notification[] $notifications The notifications to filter.
417
         *
418
         * @return Yoast_Notification[] The filtered list of notifications. Excluding the wpseo-dismiss-about notification.
419
         */
420
        public function remove_about_notice( $notifications ) {
×
421
                foreach ( $notifications as $notification_key => $notification ) {
×
422
                        if ( $notification->get_id() === 'wpseo-dismiss-about' ) {
×
423
                                unset( $notifications[ $notification_key ] );
×
424
                        }
425
                }
426

427
                return $notifications;
×
428
        }
429

430
        /**
431
         * Adds the yoast_seo_links table to the database.
432
         *
433
         * @return void
434
         */
435
        protected function upgrade_50() {
2✔
436
                global $wpdb;
2✔
437

438
                // Deletes the post meta value, which might created in the RC.
439
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
440
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
441
                $wpdb->query(
2✔
442
                        $wpdb->prepare(
2✔
443
                                "DELETE FROM %i
2✔
444
                                WHERE %i = '_yst_content_links_processed'",
2✔
445
                                [ $wpdb->postmeta, 'meta_key' ]
2✔
446
                        )
2✔
447
                );
2✔
448
        }
449

450
        /**
451
         * Register new capabilities and roles.
452
         *
453
         * @return void
454
         */
455
        private function upgrade_55() {
×
456
                // Register roles.
457
                do_action( 'wpseo_register_roles' );
×
458
                WPSEO_Role_Manager_Factory::get()->add();
×
459

460
                // Register capabilities.
461
                do_action( 'wpseo_register_capabilities' );
×
462
                WPSEO_Capability_Manager_Factory::get()->add();
×
463
        }
464

465
        /**
466
         * Removes some no longer used options for noindexing subpages and for meta keywords and its associated templates.
467
         *
468
         * @return void
469
         */
470
        private function upgrade_63() {
×
471
                $this->cleanup_option_data( 'wpseo_titles' );
×
472
        }
473

474
        /**
475
         * Perform the 7.0 upgrade, moves settings around, deletes several options.
476
         *
477
         * @return void
478
         */
479
        private function upgrade_70() {
×
480

481
                $wpseo_permalinks    = $this->get_option_from_database( 'wpseo_permalinks' );
×
482
                $wpseo_xml           = $this->get_option_from_database( 'wpseo_xml' );
×
483
                $wpseo_rss           = $this->get_option_from_database( 'wpseo_rss' );
×
484
                $wpseo               = $this->get_option_from_database( 'wpseo' );
×
485
                $wpseo_internallinks = $this->get_option_from_database( 'wpseo_internallinks' );
×
486

487
                // Move some permalink settings, then delete the option.
488
                $this->save_option_setting( $wpseo_permalinks, 'redirectattachment', 'disable-attachment' );
×
489
                $this->save_option_setting( $wpseo_permalinks, 'stripcategorybase' );
×
490

491
                // Move one XML sitemap setting, then delete the option.
492
                $this->save_option_setting( $wpseo_xml, 'enablexmlsitemap', 'enable_xml_sitemap' );
×
493

494
                // Move the RSS settings to the search appearance settings, then delete the RSS option.
495
                $this->save_option_setting( $wpseo_rss, 'rssbefore' );
×
496
                $this->save_option_setting( $wpseo_rss, 'rssafter' );
×
497

498
                $this->save_option_setting( $wpseo, 'company_logo' );
×
499
                $this->save_option_setting( $wpseo, 'company_name' );
×
500
                $this->save_option_setting( $wpseo, 'company_or_person' );
×
501
                $this->save_option_setting( $wpseo, 'person_name' );
×
502

503
                // Remove the website name and altername name as we no longer need them.
504
                $this->cleanup_option_data( 'wpseo' );
×
505

506
                // All the breadcrumbs settings have moved to the search appearance settings.
507
                foreach ( array_keys( $wpseo_internallinks ) as $key ) {
×
508
                        $this->save_option_setting( $wpseo_internallinks, $key );
×
509
                }
510

511
                // Convert hidden metabox options to display metabox options.
512
                $title_options = get_option( 'wpseo_titles' );
×
513

514
                foreach ( $title_options as $key => $value ) {
×
515
                        if ( strpos( $key, 'hideeditbox-tax-' ) === 0 ) {
×
516
                                $taxonomy = substr( $key, strlen( 'hideeditbox-tax-' ) );
×
517
                                WPSEO_Options::set( 'display-metabox-tax-' . $taxonomy, ! $value );
×
518
                                continue;
×
519
                        }
520

521
                        if ( strpos( $key, 'hideeditbox-' ) === 0 ) {
×
522
                                $post_type = substr( $key, strlen( 'hideeditbox-' ) );
×
523
                                WPSEO_Options::set( 'display-metabox-pt-' . $post_type, ! $value );
×
524
                                continue;
×
525
                        }
526
                }
527

528
                // Cleanup removed options.
529
                delete_option( 'wpseo_xml' );
×
530
                delete_option( 'wpseo_permalinks' );
×
531
                delete_option( 'wpseo_rss' );
×
532
                delete_option( 'wpseo_internallinks' );
×
533

534
                // Remove possibly present plugin conflict notice for plugin that was removed from the list of conflicting plugins.
535
                $yoast_plugin_conflict = WPSEO_Plugin_Conflict::get_instance();
×
536
                $yoast_plugin_conflict->clear_error( 'header-footer/plugin.php' );
×
537

538
                // Moves the user meta for excluding from the XML sitemap to a noindex.
539
                global $wpdb;
×
540
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
541
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
542
                $wpdb->query( "UPDATE $wpdb->usermeta SET meta_key = 'wpseo_noindex_author' WHERE meta_key = 'wpseo_excludeauthorsitemap'" );
×
543
        }
544

545
        /**
546
         * Perform the 7.1 upgrade.
547
         *
548
         * @return void
549
         */
550
        private function upgrade_71() {
×
551
                $this->cleanup_option_data( 'wpseo_social' );
×
552

553
                // Move the breadcrumbs setting and invert it.
554
                $title_options = $this->get_option_from_database( 'wpseo_titles' );
×
555

556
                if ( array_key_exists( 'breadcrumbs-blog-remove', $title_options ) ) {
×
557
                        WPSEO_Options::set( 'breadcrumbs-display-blog-page', ! $title_options['breadcrumbs-blog-remove'] );
×
558

559
                        $this->cleanup_option_data( 'wpseo_titles' );
×
560
                }
561
        }
562

563
        /**
564
         * Perform the 7.3 upgrade.
565
         *
566
         * @return void
567
         */
568
        private function upgrade_73() {
×
569
                global $wpdb;
×
570
                // We've moved the cornerstone checkbox to our proper namespace.
571
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
572
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
573
                $wpdb->query( "UPDATE $wpdb->postmeta SET meta_key = '_yoast_wpseo_is_cornerstone' WHERE meta_key = '_yst_is_cornerstone'" );
×
574

575
                // Remove the previous Whip dismissed message, as this is a new one regarding PHP 5.2.
576
                delete_option( 'whip_dismiss_timestamp' );
×
577
        }
578

579
        /**
580
         * Performs the 7.4 upgrade.
581
         *
582
         * @return void
583
         */
584
        protected function upgrade_74() {
2✔
585
                $this->remove_sitemap_validators();
2✔
586
        }
587

588
        /**
589
         * Performs the 7.5.3 upgrade.
590
         *
591
         * When upgrading purging media is potentially relevant.
592
         *
593
         * @return void
594
         */
595
        private function upgrade_753() {
×
596
                // Only when attachments are not disabled.
597
                if ( WPSEO_Options::get( 'disable-attachment' ) === true ) {
×
598
                        return;
×
599
                }
600

601
                // Only when attachments are not no-indexed.
602
                if ( WPSEO_Options::get( 'noindex-attachment' ) === true ) {
×
603
                        return;
×
604
                }
605

606
                // Set purging relevancy.
607
                WPSEO_Options::set( 'is-media-purge-relevant', true );
×
608
        }
609

610
        /**
611
         * Performs the 7.7 upgrade.
612
         *
613
         * @return void
614
         */
615
        private function upgrade_77() {
×
616
                // Remove all OpenGraph content image cache.
617
                $this->delete_post_meta( '_yoast_wpseo_post_image_cache' );
×
618
        }
619

620
        /**
621
         * Performs the 7.7.2 upgrade.
622
         *
623
         * @return void
624
         */
625
        private function upgrade_772() {
×
626
                if ( YoastSEO()->helpers->woocommerce->is_active() ) {
×
627
                        $this->migrate_woocommerce_archive_setting_to_shop_page();
×
628
                }
629
        }
630

631
        /**
632
         * Performs the 9.0 upgrade.
633
         *
634
         * @return void
635
         */
636
        protected function upgrade_90() {
2✔
637
                global $wpdb;
2✔
638

639
                // Invalidate all sitemap cache transients.
640
                WPSEO_Sitemaps_Cache_Validator::cleanup_database();
2✔
641

642
                // Removes all scheduled tasks for hitting the sitemap index.
643
                wp_clear_scheduled_hook( 'wpseo_hit_sitemap_index' );
2✔
644

645
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
646
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
647
                $wpdb->query(
2✔
648
                        $wpdb->prepare(
2✔
649
                                'DELETE FROM %i
2✔
650
                                WHERE %i LIKE %s',
2✔
651
                                [ $wpdb->options, 'option_name', 'wpseo_sitemap_%' ]
2✔
652
                        )
2✔
653
                );
2✔
654
        }
655

656
        /**
657
         * Performs the 10.0 upgrade.
658
         *
659
         * @return void
660
         */
661
        private function upgrade_100() {
×
662
                // Removes recalibration notifications.
663
                $this->clean_all_notifications();
×
664

665
                // Removes recalibration options.
666
                WPSEO_Options::clean_up( 'wpseo' );
×
667
                delete_option( 'wpseo_recalibration_beta_mailinglist_subscription' );
×
668
        }
669

670
        /**
671
         * Performs the 11.1 upgrade.
672
         *
673
         * @return void
674
         */
675
        private function upgrade_111() {
×
676
                // Set company_or_person to company when it's an invalid value.
677
                $company_or_person = WPSEO_Options::get( 'company_or_person', '' );
×
678

679
                if ( ! in_array( $company_or_person, [ 'company', 'person' ], true ) ) {
×
680
                        WPSEO_Options::set( 'company_or_person', 'company' );
×
681
                }
682
        }
683

684
        /**
685
         * Performs the 12.3 upgrade.
686
         *
687
         * Removes the about notice when its still in the database.
688
         *
689
         * @return void
690
         */
691
        private function upgrade_123() {
×
UNCOV
692
                $plugins = [
×
693
                        'yoast-seo-premium',
×
UNCOV
694
                        'video-seo-for-wordpress-seo-by-yoast',
×
UNCOV
695
                        'yoast-news-seo',
×
UNCOV
696
                        'local-seo-for-yoast-seo',
×
UNCOV
697
                        'yoast-woocommerce-seo',
×
UNCOV
698
                        'yoast-acf-analysis',
×
UNCOV
699
                ];
×
700

701
                $center = Yoast_Notification_Center::get();
×
702
                foreach ( $plugins as $plugin ) {
×
703
                        $center->remove_notification_by_id( 'wpseo-outdated-yoast-seo-plugin-' . $plugin );
×
704
                }
705
        }
706

707
        /**
708
         * Performs the 12.4 upgrade.
709
         *
710
         * Removes the Google plus defaults from the database.
711
         *
712
         * @return void
713
         */
714
        private function upgrade_124() {
×
715
                $this->cleanup_option_data( 'wpseo_social' );
×
716
        }
717

718
        /**
719
         * Performs the 12.5 upgrade.
720
         *
721
         * @return void
722
         */
723
        public function upgrade_125() {
2✔
724
                // Disables the force rewrite title when the theme supports it through WordPress.
725
                if ( WPSEO_Options::get( 'forcerewritetitle', false ) && current_theme_supports( 'title-tag' ) ) {
2✔
726
                        WPSEO_Options::set( 'forcerewritetitle', false );
×
727
                }
728

729
                global $wpdb;
2✔
730
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
731
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
732
                $wpdb->query(
2✔
733
                        $wpdb->prepare(
2✔
734
                                'DELETE FROM %i
2✔
735
                                WHERE %i = %s',
2✔
736
                                [ $wpdb->usermeta, 'meta_key', 'wp_yoast_promo_hide_premium_upsell_admin_block' ]
2✔
737
                        )
2✔
738
                );
2✔
739

740
                // Removes the WordPress update notification, because it is no longer necessary when WordPress 5.3 is released.
741
                $center = Yoast_Notification_Center::get();
2✔
742
                $center->remove_notification_by_id( 'wpseo-dismiss-wordpress-upgrade' );
2✔
743
        }
744

745
        /**
746
         * Performs the 12.8 upgrade.
747
         *
748
         * @return void
749
         */
750
        private function upgrade_128() {
×
751
                // Re-save wpseo to make sure bf_banner_2019_dismissed key is gone.
752
                $this->cleanup_option_data( 'wpseo' );
×
753

754
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-page_comments-notice' );
×
755
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-wordpress-upgrade' );
×
756
        }
757

758
        /**
759
         * Performs the 13.2 upgrade.
760
         *
761
         * @return void
762
         */
763
        private function upgrade_132() {
×
764
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-tagline-notice' );
×
765
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-permalink-notice' );
×
766
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-onpageorg' );
×
767

768
                // Transfers the onpage option value to the ryte option.
769
                $ryte_option   = get_option( 'wpseo_ryte' );
×
770
                $onpage_option = get_option( 'wpseo_onpage' );
×
771
                if ( ! $ryte_option && $onpage_option ) {
×
772
                        update_option( 'wpseo_ryte', $onpage_option );
×
773
                        delete_option( 'wpseo_onpage' );
×
774
                }
775

776
                // Changes onpage_indexability to ryte_indexability.
777
                $wpseo_option = get_option( 'wpseo' );
×
778
                if ( isset( $wpseo_option['onpage_indexability'] ) && ! isset( $wpseo_option['ryte_indexability'] ) ) {
×
779
                        $wpseo_option['ryte_indexability'] = $wpseo_option['onpage_indexability'];
×
780
                        unset( $wpseo_option['onpage_indexability'] );
×
781
                        update_option( 'wpseo', $wpseo_option );
×
782
                }
783

784
                if ( wp_next_scheduled( 'wpseo_ryte_fetch' ) ) {
×
785
                        wp_clear_scheduled_hook( 'wpseo_ryte_fetch' );
×
786
                }
787

788
                /*
789
                 * Re-register capabilities to add the new `view_site_health_checks`
790
                 * capability to the SEO Manager role.
791
                 */
792
                do_action( 'wpseo_register_capabilities' );
×
793
                WPSEO_Capability_Manager_Factory::get()->add();
×
794
        }
795

796
        /**
797
         * Perform the 14.0.3 upgrade.
798
         *
799
         * @return void
800
         */
801
        private function upgrade_1403() {
×
802
                WPSEO_Options::set( 'ignore_indexation_warning', false );
×
803
        }
804

805
        /**
806
         * Performs the 14.1 upgrade.
807
         *
808
         * @return void
809
         */
810
        private function upgrade_141() {
×
811
                /*
812
                 * These notifications are retrieved from storage on the `init` hook with
813
                 * priority 1. We need to remove them after they're retrieved.
814
                 */
815
                add_action( 'init', [ $this, 'remove_notifications_for_141' ] );
×
816
                add_action( 'init', [ $this, 'clean_up_private_taxonomies_for_141' ] );
×
817

818
                $this->reset_permalinks_of_attachments_for_141();
×
819
        }
820

821
        /**
822
         * Performs the 14.2 upgrade.
823
         *
824
         * Removes the yoast-acf-analysis notice when it's still in the database.
825
         *
826
         * @return void
827
         */
828
        private function upgrade_142() {
×
829
                add_action( 'init', [ $this, 'remove_acf_notification_for_142' ] );
×
830
        }
831

832
        /**
833
         * Performs the 14.5 upgrade.
834
         *
835
         * @return void
836
         */
837
        private function upgrade_145() {
×
838
                add_action( 'init', [ $this, 'set_indexation_completed_option_for_145' ] );
×
839
        }
840

841
        /**
842
         * Performs the 14.9 upgrade.
843
         *
844
         * @return void
845
         */
846
        private function upgrade_149() {
×
847
                $version = get_option( 'wpseo_license_server_version', 2 );
×
848
                WPSEO_Options::set( 'license_server_version', $version );
×
849
                delete_option( 'wpseo_license_server_version' );
×
850
        }
851

852
        /**
853
         * Performs the 15.1 upgrade.
854
         *
855
         * @return void
856
         */
857
        private function upgrade_151() {
×
858
                $this->set_home_url_for_151();
×
859
                $this->move_indexables_indexation_reason_for_151();
×
860

861
                add_action( 'init', [ $this, 'set_permalink_structure_option_for_151' ] );
×
862
                add_action( 'init', [ $this, 'store_custom_taxonomy_slugs_for_151' ] );
×
863
        }
864

865
        /**
866
         * Performs the 15.3 upgrade.
867
         *
868
         * @return void
869
         */
870
        private function upgrade_153() {
×
871
                WPSEO_Options::set( 'category_base_url', get_option( 'category_base' ) );
×
872
                WPSEO_Options::set( 'tag_base_url', get_option( 'tag_base' ) );
×
873

874
                // Rename a couple of options.
875
                $indexation_started_value = WPSEO_Options::get( 'indexation_started' );
×
876
                WPSEO_Options::set( 'indexing_started', $indexation_started_value );
×
877

878
                $indexables_indexing_completed_value = WPSEO_Options::get( 'indexables_indexation_completed' );
×
879
                WPSEO_Options::set( 'indexables_indexing_completed', $indexables_indexing_completed_value );
×
880
        }
881

882
        /**
883
         * Performs the 15.5 upgrade.
884
         *
885
         * @return void
886
         */
887
        private function upgrade_155() {
×
888
                // Unset the fbadminapp value in the wpseo_social option.
889
                $wpseo_social_option = get_option( 'wpseo_social' );
×
890

891
                if ( isset( $wpseo_social_option['fbadminapp'] ) ) {
×
892
                        unset( $wpseo_social_option['fbadminapp'] );
×
893
                        update_option( 'wpseo_social', $wpseo_social_option );
×
894
                }
895
        }
896

897
        /**
898
         * Performs the 15.7 upgrade.
899
         *
900
         * @return void
901
         */
902
        private function upgrade_157() {
×
903
                add_action( 'init', [ $this, 'remove_plugin_updated_notification_for_157' ] );
×
904
        }
905

906
        /**
907
         * Performs the 15.9.1 upgrade routine.
908
         *
909
         * @return void
910
         */
911
        private function upgrade_1591() {
×
912
                $enabled_auto_updates = get_option( 'auto_update_plugins' );
×
913
                $addon_update_watcher = YoastSEO()->classes->get( Addon_Update_Watcher::class );
×
914
                $addon_update_watcher->toggle_auto_updates_for_add_ons( 'auto_update_plugins', [], $enabled_auto_updates );
×
915
        }
916

917
        /**
918
         * Performs the 16.2 upgrade routine.
919
         *
920
         * @return void
921
         */
922
        private function upgrade_162() {
×
923
                $enabled_auto_updates = get_site_option( 'auto_update_plugins' );
×
924
                $addon_update_watcher = YoastSEO()->classes->get( Addon_Update_Watcher::class );
×
925
                $addon_update_watcher->toggle_auto_updates_for_add_ons( 'auto_update_plugins', $enabled_auto_updates, [] );
×
926
        }
927

928
        /**
929
         * Performs the 16.5 upgrade.
930
         *
931
         * @return void
932
         */
933
        private function upgrade_165() {
×
934
                add_action( 'init', [ $this, 'copy_og_settings_from_social_to_titles' ], 99 );
×
935

936
                // Run after the WPSEO_Options::enrich_defaults method which has priority 99.
937
                add_action( 'init', [ $this, 'reset_og_settings_to_default_values' ], 100 );
×
938
        }
939

940
        /**
941
         * Performs the 17.2 upgrade. Cleans out any unnecessary indexables. See $cleanup_integration->get_cleanup_tasks() to see what will be cleaned out.
942
         *
943
         * @return void
944
         */
945
        private function upgrade_172() {
×
946
                wp_unschedule_hook( 'wpseo_cleanup_orphaned_indexables' );
×
947
                wp_unschedule_hook( 'wpseo_cleanup_indexables' );
×
948

949
                if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
×
950
                        wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
×
951
                }
952
        }
953

954
        /**
955
         * Performs the 17.7.1 upgrade routine.
956
         *
957
         * @return void
958
         */
959
        private function upgrade_1771() {
×
960
                $enabled_auto_updates = get_site_option( 'auto_update_plugins' );
×
961
                $addon_update_watcher = YoastSEO()->classes->get( Addon_Update_Watcher::class );
×
962
                $addon_update_watcher->toggle_auto_updates_for_add_ons( 'auto_update_plugins', $enabled_auto_updates, [] );
×
963
        }
964

965
        /**
966
         * Performs the 17.9 upgrade routine.
967
         *
968
         * @return void
969
         */
970
        private function upgrade_179() {
×
971
                WPSEO_Options::set( 'wincher_integration_active', true );
×
972
        }
973

974
        /**
975
         * Performs the 18.3 upgrade routine.
976
         *
977
         * @return void
978
         */
979
        private function upgrade_183() {
×
980
                $this->delete_post_meta( 'yoast-structured-data-blocks-images-cache' );
×
981
        }
982

983
        /**
984
         * Performs the 18.6 upgrade routine.
985
         *
986
         * @return void
987
         */
988
        private function upgrade_186() {
×
989
                if ( is_multisite() ) {
×
990
                        WPSEO_Options::set( 'allow_wincher_integration_active', false );
×
991
                }
992
        }
993

994
        /**
995
         * Performs the 18.9 upgrade routine.
996
         *
997
         * @return void
998
         */
999
        private function upgrade_189() {
×
1000
                // Make old users not get the Installation Success page after upgrading.
1001
                WPSEO_Options::set( 'should_redirect_after_install_free', false );
×
1002
                // We're adding a hardcoded time here, so that in the future we can be able to identify whether the user did see the Installation Success page or not.
1003
                // If they did, they wouldn't have this hardcoded value in that option, but rather (roughly) the timestamp of the moment they saw it.
1004
                WPSEO_Options::set( 'activation_redirect_timestamp_free', 1652258756 );
×
1005

1006
                // Transfer the Social URLs.
1007
                $other   = [];
×
1008
                $other[] = WPSEO_Options::get( 'instagram_url' );
×
1009
                $other[] = WPSEO_Options::get( 'linkedin_url' );
×
1010
                $other[] = WPSEO_Options::get( 'myspace_url' );
×
1011
                $other[] = WPSEO_Options::get( 'pinterest_url' );
×
1012
                $other[] = WPSEO_Options::get( 'youtube_url' );
×
1013
                $other[] = WPSEO_Options::get( 'wikipedia_url' );
×
1014

1015
                WPSEO_Options::set( 'other_social_urls', array_values( array_unique( array_filter( $other ) ) ) );
×
1016

1017
                // Transfer the progress of the old Configuration Workout.
1018
                $workout_data      = WPSEO_Options::get( 'workouts_data' );
×
1019
                $old_conf_progress = ( $workout_data['configuration']['finishedSteps'] ?? [] );
×
1020

1021
                if ( in_array( 'optimizeSeoData', $old_conf_progress, true ) && in_array( 'siteRepresentation', $old_conf_progress, true ) ) {
×
1022
                        // If completed ‘SEO optimization’ and ‘Site representation’ step, we assume the workout was completed.
UNCOV
1023
                        $configuration_finished_steps = [
×
1024
                                'siteRepresentation',
×
UNCOV
1025
                                'socialProfiles',
×
UNCOV
1026
                                'personalPreferences',
×
UNCOV
1027
                        ];
×
1028
                        WPSEO_Options::set( 'configuration_finished_steps', $configuration_finished_steps );
×
1029
                }
1030
        }
1031

1032
        /**
1033
         * Performs the 19.1 upgrade routine.
1034
         *
1035
         * @return void
1036
         */
1037
        private function upgrade_191() {
×
1038
                if ( is_multisite() ) {
×
1039
                        WPSEO_Options::set( 'allow_remove_feed_post_comments', true );
×
1040
                }
1041
        }
1042

1043
        /**
1044
         * Performs the 19.3 upgrade routine.
1045
         *
1046
         * @return void
1047
         */
1048
        private function upgrade_193() {
×
1049
                if ( empty( get_option( 'wpseo_premium', [] ) ) ) {
×
1050
                        WPSEO_Options::set( 'enable_index_now', true );
×
1051
                        WPSEO_Options::set( 'enable_link_suggestions', true );
×
1052
                }
1053
        }
1054

1055
        /**
1056
         * Performs the 19.6 upgrade routine.
1057
         *
1058
         * @return void
1059
         */
1060
        private function upgrade_196() {
×
1061
                WPSEO_Options::set( 'ryte_indexability', false );
×
1062
                WPSEO_Options::set( 'allow_ryte_indexability', false );
×
1063
                wp_clear_scheduled_hook( 'wpseo_ryte_fetch' );
×
1064
        }
1065

1066
        /**
1067
         * Performs the 19.11 upgrade routine.
1068
         *
1069
         * @return void
1070
         */
1071
        private function upgrade_1911() {
×
1072
                add_action( 'shutdown', [ $this, 'remove_indexable_rows_for_non_public_post_types' ] );
×
1073
                add_action( 'shutdown', [ $this, 'remove_indexable_rows_for_non_public_taxonomies' ] );
×
1074
                $this->deduplicate_unindexed_indexable_rows();
×
1075
                $this->remove_indexable_rows_for_disabled_authors_archive();
×
1076
                if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
×
1077
                        wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
×
1078
                }
1079
        }
1080

1081
        /**
1082
         * Performs the 20.2 upgrade routine.
1083
         *
1084
         * @return void
1085
         */
1086
        private function upgrade_202() {
×
1087
                if ( WPSEO_Options::get( 'disable-attachment', true ) ) {
×
1088
                        $attachment_cleanup_helper = YoastSEO()->helpers->attachment_cleanup;
×
1089

1090
                        $attachment_cleanup_helper->remove_attachment_indexables( true );
×
1091
                        $attachment_cleanup_helper->clean_attachment_links_from_target_indexable_ids( true );
×
1092
                }
1093

1094
                $this->clean_unindexed_indexable_rows_with_no_object_id();
×
1095

1096
                if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
×
1097
                        // This schedules the cleanup routine cron again, since in combination of premium cleans up the prominent words table. We also want to cleanup possible orphaned hierarchies from the above cleanups.
1098
                        wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
×
1099
                }
1100
        }
1101

1102
        /**
1103
         * Performs the 20.5 upgrade routine.
1104
         *
1105
         * @return void
1106
         */
1107
        private function upgrade_205() {
×
1108
                if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
×
1109
                        wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
×
1110
                }
1111
        }
1112

1113
        /**
1114
         * Performs the 20.7 upgrade routine.
1115
         * Removes the metadata related to the settings page introduction modal for all the users.
1116
         * Also, schedules another cleanup scheduled action.
1117
         *
1118
         * @return void
1119
         */
1120
        private function upgrade_207() {
×
1121
                add_action( 'shutdown', [ $this, 'delete_user_introduction_meta' ] );
×
1122
        }
1123

1124
        /**
1125
         * Performs the 20.8 upgrade routine.
1126
         * Schedules another cleanup scheduled action.
1127
         *
1128
         * @return void
1129
         */
1130
        private function upgrade_208() {
×
1131
                if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
×
1132
                        wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
×
1133
                }
1134
        }
1135

1136
        /**
1137
         * Performs the 22.6 upgrade routine.
1138
         * Schedules another cleanup scheduled action, but starting from the last cleanup action we just added (if there aren't any running cleanups already).
1139
         *
1140
         * @return void
1141
         */
1142
        private function upgrade_226() {
×
1143
                if ( get_option( Cleanup_Integration::CURRENT_TASK_OPTION ) === false ) {
×
1144
                        $cleanup_integration = YoastSEO()->classes->get( Cleanup_Integration::class );
×
1145
                        $cleanup_integration->start_cron_job( 'clean_selected_empty_usermeta', DAY_IN_SECONDS );
×
1146
                }
1147
        }
1148

1149
        /**
1150
         * Sets the home_url option for the 15.1 upgrade routine.
1151
         *
1152
         * @return void
1153
         */
1154
        protected function set_home_url_for_151() {
×
1155
                $home_url = WPSEO_Options::get( 'home_url' );
×
1156

1157
                if ( empty( $home_url ) ) {
×
1158
                        WPSEO_Options::set( 'home_url', get_home_url() );
×
1159
                }
1160
        }
1161

1162
        /**
1163
         * Moves the `indexables_indexation_reason` option to the
1164
         * renamed `indexing_reason` option.
1165
         *
1166
         * @return void
1167
         */
1168
        protected function move_indexables_indexation_reason_for_151() {
×
1169
                $reason = WPSEO_Options::get( 'indexables_indexation_reason', '' );
×
1170
                WPSEO_Options::set( 'indexing_reason', $reason );
×
1171
        }
1172

1173
        /**
1174
         * Checks if the indexable indexation is completed.
1175
         * If so, sets the `indexables_indexation_completed` option to `true`,
1176
         * else to `false`.
1177
         *
1178
         * @return void
1179
         */
1180
        public function set_indexation_completed_option_for_145() {
×
1181
                WPSEO_Options::set( 'indexables_indexation_completed', YoastSEO()->helpers->indexing->get_limited_filtered_unindexed_count( 1 ) === 0 );
×
1182
        }
1183

1184
        /**
1185
         * Cleans up the private taxonomies from the indexables table for the upgrade routine to 14.1.
1186
         *
1187
         * @return void
1188
         */
1189
        public function clean_up_private_taxonomies_for_141() {
2✔
1190
                global $wpdb;
2✔
1191

1192
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
1193
                $show_errors       = $wpdb->show_errors;
2✔
1194
                $wpdb->show_errors = false;
2✔
1195

1196
                // Clean up indexables of private taxonomies.
1197
                $private_taxonomies = get_taxonomies( [ 'public' => false ], 'names' );
2✔
1198

1199
                if ( empty( $private_taxonomies ) ) {
2✔
1200
                        return;
×
1201
                }
1202

1203
                $replacements = array_merge( [ Model::get_table_name( 'Indexable' ), 'object_type', 'object_sub_type' ], $private_taxonomies );
2✔
1204

1205
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1206
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1207
                $wpdb->query(
2✔
1208
                        $wpdb->prepare(
2✔
1209
                                "DELETE FROM %i
2✔
1210
                                WHERE %i = 'term'
1211
                                AND %i IN ("
2✔
1212
                                        . implode( ', ', array_fill( 0, count( $private_taxonomies ), '%s' ) )
2✔
1213
                                        . ')',
2✔
1214
                                $replacements
2✔
1215
                        )
2✔
1216
                );
2✔
1217

1218
                $wpdb->show_errors = $show_errors;
2✔
1219
        }
1220

1221
        /**
1222
         * Resets the permalinks of attachments to `null` in the indexable table for the upgrade routine to 14.1.
1223
         *
1224
         * @return void
1225
         */
1226
        private function reset_permalinks_of_attachments_for_141() {
×
1227
                global $wpdb;
×
1228

1229
                // If migrations haven't been completed succesfully the following may give false errors. So suppress them.
1230
                $show_errors       = $wpdb->show_errors;
×
1231
                $wpdb->show_errors = false;
×
1232

1233
                // Reset the permalinks of the attachments in the indexable table.
1234
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1235
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1236
                $wpdb->query(
×
1237
                        $wpdb->prepare(
×
1238
                                "UPDATE %i SET %i = NULL WHERE %i = 'post' AND %i = 'attachment'",
×
1239
                                [ Model::get_table_name( 'Indexable' ), 'permalink', 'object_type', 'object_sub_type' ]
×
UNCOV
1240
                        )
×
UNCOV
1241
                );
×
1242

1243
                $wpdb->show_errors = $show_errors;
×
1244
        }
1245

1246
        /**
1247
         * Removes notifications from the Notification center for the 14.1 upgrade.
1248
         *
1249
         * @return void
1250
         */
1251
        public function remove_notifications_for_141() {
×
1252
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-recalculate' );
×
1253
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-blog-public-notice' );
×
1254
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-links-table-not-accessible' );
×
1255
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-post-type-archive-notification' );
×
1256
        }
1257

1258
        /**
1259
         * Removes the wpseo-suggested-plugin-yoast-acf-analysis notification from the Notification center for the 14.2 upgrade.
1260
         *
1261
         * @return void
1262
         */
1263
        public function remove_acf_notification_for_142() {
×
1264
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-suggested-plugin-yoast-acf-analysis' );
×
1265
        }
1266

1267
        /**
1268
         * Removes the wpseo-plugin-updated notification from the Notification center for the 15.7 upgrade.
1269
         *
1270
         * @return void
1271
         */
1272
        public function remove_plugin_updated_notification_for_157() {
×
1273
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-plugin-updated' );
×
1274
        }
1275

1276
        /**
1277
         * Removes all notifications saved in the database under 'wp_yoast_notifications'.
1278
         *
1279
         * @return void
1280
         */
1281
        private function clean_all_notifications() {
×
1282
                global $wpdb;
×
1283
                delete_metadata( 'user', 0, $wpdb->get_blog_prefix() . Yoast_Notification_Center::STORAGE_KEY, '', true );
×
1284
        }
1285

1286
        /**
1287
         * Removes the post meta fields for a given meta key.
1288
         *
1289
         * @param string $meta_key The meta key.
1290
         *
1291
         * @return void
1292
         */
1293
        private function delete_post_meta( $meta_key ) {
×
1294
                global $wpdb;
×
1295
                $deleted = $wpdb->delete( $wpdb->postmeta, [ 'meta_key' => $meta_key ], [ '%s' ] );
×
1296

1297
                if ( $deleted ) {
×
1298
                        wp_cache_set( 'last_changed', microtime(), 'posts' );
×
1299
                }
1300
        }
1301

1302
        /**
1303
         * Removes all sitemap validators.
1304
         *
1305
         * This should be executed on every upgrade routine until we have removed the sitemap caching in the database.
1306
         *
1307
         * @return void
1308
         */
1309
        private function remove_sitemap_validators() {
×
1310
                global $wpdb;
×
1311

1312
                // Remove all sitemap validators.
1313
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1314
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1315
                $wpdb->query(
×
1316
                        $wpdb->prepare(
×
1317
                                'DELETE FROM %i WHERE %i LIKE %s',
×
1318
                                [ $wpdb->options, 'option_name', 'wpseo_sitemap%validator%' ]
×
UNCOV
1319
                        )
×
UNCOV
1320
                );
×
1321
        }
1322

1323
        /**
1324
         * Retrieves the option value directly from the database.
1325
         *
1326
         * @param string $option_name Option to retrieve.
1327
         *
1328
         * @return int|string|bool|float|array<string|int|bool|float> The content of the option if exists, otherwise an empty array.
1329
         */
1330
        protected function get_option_from_database( $option_name ) {
6✔
1331
                global $wpdb;
6✔
1332

1333
                // Load option directly from the database, to avoid filtering and sanitization.
1334
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1335
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1336
                $results = $wpdb->get_results(
6✔
1337
                        $wpdb->prepare(
6✔
1338
                                'SELECT %i FROM %i WHERE %i = %s',
6✔
1339
                                [ 'option_value', $wpdb->options, 'option_name', $option_name ]
6✔
1340
                        ),
6✔
1341
                        ARRAY_A
6✔
1342
                );
6✔
1343

1344
                if ( ! empty( $results ) ) {
6✔
1345
                        return maybe_unserialize( $results[0]['option_value'] );
4✔
1346
                }
1347

1348
                return [];
2✔
1349
        }
1350

1351
        /**
1352
         * Cleans the option to make sure only relevant settings are there.
1353
         *
1354
         * @param string $option_name Option name save.
1355
         *
1356
         * @return void
1357
         */
1358
        protected function cleanup_option_data( $option_name ) {
4✔
1359
                $data = get_option( $option_name, [] );
4✔
1360
                if ( ! is_array( $data ) || $data === [] ) {
4✔
1361
                        return;
2✔
1362
                }
1363

1364
                /*
1365
                 * Clean up the option by re-saving it.
1366
                 *
1367
                 * The option framework will remove any settings that are not configured
1368
                 * for this option, removing any migrated settings.
1369
                 */
1370
                update_option( $option_name, $data );
2✔
1371
        }
1372

1373
        /**
1374
         * Saves an option setting to where it should be stored.
1375
         *
1376
         * @param int|string|bool|float|array<string|int|bool|float> $source_data    The option containing the value to be migrated.
1377
         * @param string                                             $source_setting Name of the key in the "from" option.
1378
         * @param string|null                                        $target_setting Name of the key in the "to" option.
1379
         *
1380
         * @return void
1381
         */
1382
        protected function save_option_setting( $source_data, $source_setting, $target_setting = null ) {
4✔
1383
                if ( $target_setting === null ) {
4✔
1384
                        $target_setting = $source_setting;
4✔
1385
                }
1386

1387
                if ( isset( $source_data[ $source_setting ] ) ) {
4✔
1388
                        WPSEO_Options::set( $target_setting, $source_data[ $source_setting ] );
2✔
1389
                }
1390
        }
1391

1392
        /**
1393
         * Migrates WooCommerce archive settings to the WooCommerce Shop page meta-data settings.
1394
         *
1395
         * If no Shop page is defined, nothing will be migrated.
1396
         *
1397
         * @return void
1398
         */
1399
        private function migrate_woocommerce_archive_setting_to_shop_page() {
×
1400
                $shop_page_id = wc_get_page_id( 'shop' );
×
1401

1402
                if ( $shop_page_id === -1 ) {
×
1403
                        return;
×
1404
                }
1405

1406
                $title = WPSEO_Meta::get_value( 'title', $shop_page_id );
×
1407

1408
                if ( empty( $title ) ) {
×
1409
                        $option_title = WPSEO_Options::get( 'title-ptarchive-product' );
×
1410

1411
                        WPSEO_Meta::set_value(
×
1412
                                'title',
×
1413
                                $option_title,
×
1414
                                $shop_page_id
×
UNCOV
1415
                        );
×
1416

1417
                        WPSEO_Options::set( 'title-ptarchive-product', '' );
×
1418
                }
1419

1420
                $meta_description = WPSEO_Meta::get_value( 'metadesc', $shop_page_id );
×
1421

1422
                if ( empty( $meta_description ) ) {
×
1423
                        $option_metadesc = WPSEO_Options::get( 'metadesc-ptarchive-product' );
×
1424

1425
                        WPSEO_Meta::set_value(
×
1426
                                'metadesc',
×
1427
                                $option_metadesc,
×
1428
                                $shop_page_id
×
UNCOV
1429
                        );
×
1430

1431
                        WPSEO_Options::set( 'metadesc-ptarchive-product', '' );
×
1432
                }
1433

1434
                $bc_title = WPSEO_Meta::get_value( 'bctitle', $shop_page_id );
×
1435

1436
                if ( empty( $bc_title ) ) {
×
1437
                        $option_bctitle = WPSEO_Options::get( 'bctitle-ptarchive-product' );
×
1438

1439
                        WPSEO_Meta::set_value(
×
1440
                                'bctitle',
×
1441
                                $option_bctitle,
×
1442
                                $shop_page_id
×
UNCOV
1443
                        );
×
1444

1445
                        WPSEO_Options::set( 'bctitle-ptarchive-product', '' );
×
1446
                }
1447

1448
                $noindex = WPSEO_Meta::get_value( 'meta-robots-noindex', $shop_page_id );
×
1449

1450
                if ( $noindex === '0' ) {
×
1451
                        $option_noindex = WPSEO_Options::get( 'noindex-ptarchive-product' );
×
1452

1453
                        WPSEO_Meta::set_value(
×
1454
                                'meta-robots-noindex',
×
1455
                                $option_noindex,
×
1456
                                $shop_page_id
×
UNCOV
1457
                        );
×
1458

1459
                        WPSEO_Options::set( 'noindex-ptarchive-product', false );
×
1460
                }
1461
        }
1462

1463
        /**
1464
         * Stores the initial `permalink_structure` option.
1465
         *
1466
         * @return void
1467
         */
1468
        public function set_permalink_structure_option_for_151() {
×
1469
                WPSEO_Options::set( 'permalink_structure', get_option( 'permalink_structure' ) );
×
1470
        }
1471

1472
        /**
1473
         * Stores the initial slugs of custom taxonomies.
1474
         *
1475
         * @return void
1476
         */
1477
        public function store_custom_taxonomy_slugs_for_151() {
×
1478
                $taxonomies = $this->taxonomy_helper->get_custom_taxonomies();
×
1479

1480
                $custom_taxonomies = [];
×
1481

1482
                foreach ( $taxonomies as $taxonomy ) {
×
1483
                        $slug = $this->taxonomy_helper->get_taxonomy_slug( $taxonomy );
×
1484

1485
                        $custom_taxonomies[ $taxonomy ] = $slug;
×
1486
                }
1487

1488
                WPSEO_Options::set( 'custom_taxonomy_slugs', $custom_taxonomies );
×
1489
        }
1490

1491
        /**
1492
         * Copies the frontpage social settings to the titles options.
1493
         *
1494
         * @return void
1495
         */
1496
        public function copy_og_settings_from_social_to_titles() {
×
1497
                $wpseo_social = get_option( 'wpseo_social' );
×
1498
                $wpseo_titles = get_option( 'wpseo_titles' );
×
1499

1500
                $copied_options = [];
×
1501
                // Reset to the correct default value.
1502
                $copied_options['open_graph_frontpage_title'] = '%%sitename%%';
×
1503

UNCOV
1504
                $options = [
×
1505
                        'og_frontpage_title'    => 'open_graph_frontpage_title',
×
UNCOV
1506
                        'og_frontpage_desc'     => 'open_graph_frontpage_desc',
×
UNCOV
1507
                        'og_frontpage_image'    => 'open_graph_frontpage_image',
×
UNCOV
1508
                        'og_frontpage_image_id' => 'open_graph_frontpage_image_id',
×
UNCOV
1509
                ];
×
1510

1511
                foreach ( $options as $social_option => $titles_option ) {
×
1512
                        if ( ! empty( $wpseo_social[ $social_option ] ) ) {
×
1513
                                $copied_options[ $titles_option ] = $wpseo_social[ $social_option ];
×
1514
                        }
1515
                }
1516

1517
                $wpseo_titles = array_merge( $wpseo_titles, $copied_options );
×
1518

1519
                update_option( 'wpseo_titles', $wpseo_titles );
×
1520
        }
1521

1522
        /**
1523
         * Reset the social options with the correct default values.
1524
         *
1525
         * @return void
1526
         */
1527
        public function reset_og_settings_to_default_values() {
×
1528
                $wpseo_titles    = get_option( 'wpseo_titles' );
×
1529
                $updated_options = [];
×
1530

1531
                $updated_options['social-title-author-wpseo']  = '%%name%%';
×
1532
                $updated_options['social-title-archive-wpseo'] = '%%date%%';
×
1533

1534
                /* translators: %s expands to the name of a post type (plural). */
1535
                $post_type_archive_default = sprintf( __( '%s Archive', 'wordpress-seo' ), '%%pt_plural%%' );
×
1536

1537
                /* translators: %s expands to the variable used for term title. */
1538
                $term_archive_default = sprintf( __( '%s Archives', 'wordpress-seo' ), '%%term_title%%' );
×
1539

1540
                $post_type_objects = get_post_types( [ 'public' => true ], 'objects' );
×
1541

1542
                if ( $post_type_objects ) {
×
1543
                        foreach ( $post_type_objects as $pt ) {
×
1544
                                // Post types.
1545
                                if ( isset( $wpseo_titles[ 'social-title-' . $pt->name ] ) ) {
×
1546
                                        $updated_options[ 'social-title-' . $pt->name ] = '%%title%%';
×
1547
                                }
1548
                                // Post type archives.
1549
                                if ( isset( $wpseo_titles[ 'social-title-ptarchive-' . $pt->name ] ) ) {
×
1550
                                        $updated_options[ 'social-title-ptarchive-' . $pt->name ] = $post_type_archive_default;
×
1551
                                }
1552
                        }
1553
                }
1554

1555
                $taxonomy_objects = get_taxonomies( [ 'public' => true ], 'object' );
×
1556

1557
                if ( $taxonomy_objects ) {
×
1558
                        foreach ( $taxonomy_objects as $tax ) {
×
1559
                                if ( isset( $wpseo_titles[ 'social-title-tax-' . $tax->name ] ) ) {
×
1560
                                        $updated_options[ 'social-title-tax-' . $tax->name ] = $term_archive_default;
×
1561
                                }
1562
                        }
1563
                }
1564

1565
                $wpseo_titles = array_merge( $wpseo_titles, $updated_options );
×
1566

1567
                update_option( 'wpseo_titles', $wpseo_titles );
×
1568
        }
1569

1570
        /**
1571
         * Removes all indexables for posts that are not publicly viewable.
1572
         * This method should be called after init, because post_types can still be registered.
1573
         *
1574
         * @return void
1575
         */
1576
        public function remove_indexable_rows_for_non_public_post_types() {
4✔
1577
                global $wpdb;
4✔
1578

1579
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
1580
                $show_errors       = $wpdb->show_errors;
4✔
1581
                $wpdb->show_errors = false;
4✔
1582

1583
                $indexable_table = Model::get_table_name( 'Indexable' );
4✔
1584

1585
                $included_post_types = YoastSEO()->helpers->post_type->get_indexable_post_types();
4✔
1586

1587
                if ( empty( $included_post_types ) ) {
4✔
1588
                        // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1589
                        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1590
                        $wpdb->query(
2✔
1591
                                $wpdb->prepare(
2✔
1592
                                        "DELETE FROM %i
2✔
1593
                                        WHERE %i = 'post'
1594
                                        AND %i IS NOT NULL",
2✔
1595
                                        [ $indexable_table, 'object_type', 'object_sub_type' ]
2✔
1596
                                )
2✔
1597
                        );
2✔
1598
                }
1599
                else {
1600
                        // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1601
                        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1602
                        $wpdb->query(
2✔
1603
                                $wpdb->prepare(
2✔
1604
                                        "DELETE FROM %i
2✔
1605
                                        WHERE %i = 'post'
1606
                                        AND %i IS NOT NULL
1607
                                        AND %i NOT IN ( " . implode( ', ', array_fill( 0, count( $included_post_types ), '%s' ) ) . ' )',
2✔
1608
                                        array_merge( [ $indexable_table, 'object_type', 'object_sub_type', 'object_sub_type' ], $included_post_types )
2✔
1609
                                )
2✔
1610
                        );
2✔
1611
                }
1612

1613
                $wpdb->show_errors = $show_errors;
4✔
1614
        }
1615

1616
        /**
1617
         * Removes all indexables for terms that are not publicly viewable.
1618
         * This method should be called after init, because taxonomies can still be registered.
1619
         *
1620
         * @return void
1621
         */
1622
        public function remove_indexable_rows_for_non_public_taxonomies() {
4✔
1623
                global $wpdb;
4✔
1624

1625
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
1626
                $show_errors       = $wpdb->show_errors;
4✔
1627
                $wpdb->show_errors = false;
4✔
1628

1629
                $indexable_table = Model::get_table_name( 'Indexable' );
4✔
1630

1631
                $included_taxonomies = YoastSEO()->helpers->taxonomy->get_indexable_taxonomies();
4✔
1632

1633
                if ( empty( $included_taxonomies ) ) {
4✔
1634
                        // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1635
                        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1636
                        $wpdb->query(
×
1637
                                $wpdb->prepare(
×
1638
                                        "DELETE FROM %i
×
1639
                                        WHERE %i = 'term'
UNCOV
1640
                                        AND %i IS NOT NULL",
×
1641
                                        [ $indexable_table, 'object_type', 'object_sub_type' ]
×
UNCOV
1642
                                )
×
UNCOV
1643
                        );
×
1644
                }
1645
                else {
1646
                        // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1647
                        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1648
                        $wpdb->query(
4✔
1649
                                $wpdb->prepare(
4✔
1650
                                        "DELETE FROM %i
4✔
1651
                                        WHERE %i = 'term'
1652
                                        AND %i IS NOT NULL
1653
                                        AND %i NOT IN ( " . implode( ', ', array_fill( 0, count( $included_taxonomies ), '%s' ) ) . ' )',
4✔
1654
                                        array_merge( [ $indexable_table, 'object_type', 'object_sub_type', 'object_sub_type' ], $included_taxonomies )
4✔
1655
                                )
4✔
1656
                        );
4✔
1657
                }
1658

1659
                $wpdb->show_errors = $show_errors;
4✔
1660
        }
1661

1662
        /**
1663
         * De-duplicates indexables that have more than one "unindexed" rows for the same object. Keeps the newest indexable.
1664
         *
1665
         * @return void
1666
         */
1667
        protected function deduplicate_unindexed_indexable_rows() {
2✔
1668
                global $wpdb;
2✔
1669

1670
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
1671
                $show_errors       = $wpdb->show_errors;
2✔
1672
                $wpdb->show_errors = false;
2✔
1673

1674
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1675
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1676
                $duplicates = $wpdb->get_results(
2✔
1677
                        $wpdb->prepare(
2✔
1678
                                "
2✔
1679
                        SELECT
1680
                                MAX(id) as newest_id,
1681
                                object_id,
1682
                                object_type
1683
                        FROM
1684
                                %i
1685
                        WHERE
1686
                                post_status = 'unindexed'
1687
                                AND object_type IN ( 'term', 'post', 'user' )
1688
                        GROUP BY
1689
                                object_id,
1690
                                object_type
1691
                        HAVING
1692
                                count(*) > 1",
2✔
1693
                                [ Model::get_table_name( 'Indexable' ) ]
2✔
1694
                        ),
2✔
1695
                        ARRAY_A
2✔
1696
                );
2✔
1697

1698
                if ( empty( $duplicates ) ) {
2✔
1699
                        $wpdb->show_errors = $show_errors;
×
1700

1701
                        return;
×
1702
                }
1703

1704
                // Users, terms and posts may share the same object_id. So delete them in separate, more performant, queries.
1705
                $delete_queries = [
2✔
1706
                        $this->get_indexable_deduplication_query_for_type( 'post', $duplicates, $wpdb ),
2✔
1707
                        $this->get_indexable_deduplication_query_for_type( 'term', $duplicates, $wpdb ),
2✔
1708
                        $this->get_indexable_deduplication_query_for_type( 'user', $duplicates, $wpdb ),
2✔
1709
                ];
2✔
1710

1711
                foreach ( $delete_queries as $delete_query ) {
2✔
1712
                        if ( ! empty( $delete_query ) ) {
2✔
1713
                                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1714
                                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1715
                                // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
1716
                                $wpdb->query( $delete_query );
2✔
1717
                                // phpcs:enable
1718
                        }
1719
                }
1720

1721
                $wpdb->show_errors = $show_errors;
2✔
1722
        }
1723

1724
        /**
1725
         * Cleans up "unindexed" indexable rows when appropriate, aka when there's no object ID even though it should.
1726
         *
1727
         * @return void
1728
         */
1729
        protected function clean_unindexed_indexable_rows_with_no_object_id() {
2✔
1730
                global $wpdb;
2✔
1731

1732
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
1733
                $show_errors       = $wpdb->show_errors;
2✔
1734
                $wpdb->show_errors = false;
2✔
1735

1736
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1737
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1738
                $wpdb->query(
2✔
1739
                        $wpdb->prepare(
2✔
1740
                                "DELETE FROM %i
2✔
1741
                                WHERE %i = 'unindexed'
1742
                                AND %i NOT IN ( 'home-page', 'date-archive', 'post-type-archive', 'system-page' )
1743
                                AND %i IS NULL",
2✔
1744
                                [ Model::get_table_name( 'Indexable' ), 'post_status', 'object_type', 'object_id' ]
2✔
1745
                        )
2✔
1746
                );
2✔
1747

1748
                $wpdb->show_errors = $show_errors;
2✔
1749
        }
1750

1751
        /**
1752
         * Removes all user indexable rows when the author archive is disabled.
1753
         *
1754
         * @return void
1755
         */
1756
        protected function remove_indexable_rows_for_disabled_authors_archive() {
2✔
1757
                global $wpdb;
2✔
1758

1759
                if ( ! YoastSEO()->helpers->author_archive->are_disabled() ) {
2✔
1760
                        return;
×
1761
                }
1762

1763
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
1764
                $show_errors       = $wpdb->show_errors;
2✔
1765
                $wpdb->show_errors = false;
2✔
1766

1767
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1768
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1769
                $wpdb->query(
2✔
1770
                        $wpdb->prepare(
2✔
1771
                                "DELETE FROM %i WHERE %i = 'user'",
2✔
1772
                                [ Model::get_table_name( 'Indexable' ), 'object_type' ]
2✔
1773
                        )
2✔
1774
                );
2✔
1775

1776
                $wpdb->show_errors = $show_errors;
2✔
1777
        }
1778

1779
        /**
1780
         * Creates a query for de-duplicating indexables for a particular type.
1781
         *
1782
         * @param string                              $object_type The object type to deduplicate.
1783
         * @param string|array<array<int,int,string>> $duplicates  The result of the duplicate query.
1784
         * @param wpdb                                $wpdb        The wpdb object.
1785
         *
1786
         * @return string The query that removes all but one duplicate for each object of the object type.
1787
         */
1788
        protected function get_indexable_deduplication_query_for_type( $object_type, $duplicates, $wpdb ) {
2✔
1789
                $filtered_duplicates = array_filter(
2✔
1790
                        $duplicates,
2✔
1791
                        static function ( $duplicate ) use ( $object_type ) {
2✔
1792
                                return $duplicate['object_type'] === $object_type;
2✔
1793
                        }
2✔
1794
                );
2✔
1795

1796
                if ( empty( $filtered_duplicates ) ) {
2✔
1797
                        return '';
×
1798
                }
1799

1800
                $object_ids           = wp_list_pluck( $filtered_duplicates, 'object_id' );
2✔
1801
                $newest_indexable_ids = wp_list_pluck( $filtered_duplicates, 'newest_id' );
2✔
1802

1803
                $replacements   = array_merge( [ Model::get_table_name( 'Indexable' ), 'object_id' ], array_values( $object_ids ), array_values( $newest_indexable_ids ) );
2✔
1804
                $replacements[] = $object_type;
2✔
1805

1806
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1807
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1808
                return $wpdb->prepare(
2✔
1809
                        'DELETE FROM
2✔
1810
                                %i
1811
                        WHERE
1812
                                %i IN ( ' . implode( ', ', array_fill( 0, count( $filtered_duplicates ), '%d' ) ) . ' )
2✔
1813
                                AND id NOT IN ( ' . implode( ', ', array_fill( 0, count( $filtered_duplicates ), '%d' ) ) . ' )
2✔
1814
                                AND object_type = %s',
2✔
1815
                        $replacements
2✔
1816
                );
2✔
1817
        }
1818

1819
        /**
1820
         * Removes the settings' introduction modal data for users.
1821
         *
1822
         * @return void
1823
         */
1824
        public function delete_user_introduction_meta() {
×
1825
                delete_metadata( 'user', 0, '_yoast_settings_introduction', '', true );
×
1826
        }
1827
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc