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

Yoast / wordpress-seo / dd6e866a9e6d253114633104d9e3858d807178ba

19 Jun 2024 10:03AM UTC coverage: 48.628% (-4.3%) from 52.936%
dd6e866a9e6d253114633104d9e3858d807178ba

push

github

web-flow
Merge pull request #21431 from Yoast/21429-update-copy-in-the-introduction-and-consent-modals

Updates the copy for the introduction and consent modals

7441 of 13454 branches covered (55.31%)

Branch coverage included in aggregate %.

0 of 3 new or added lines in 2 files covered. (0.0%)

3718 existing lines in 107 files now uncovered.

25100 of 53464 relevant lines covered (46.95%)

62392.47 hits per line

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

0.0
/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
                // Since 3.7.
107
                $upsell_notice = new WPSEO_Product_Upsell_Notice();
×
108
                $upsell_notice->set_upgrade_notice();
×
109

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

117
                $this->finish_up( $version );
×
118
        }
119

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

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

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

161
                // Just flush rewrites, always, to at least make them work after an upgrade.
UNCOV
162
                add_action( 'shutdown', 'flush_rewrite_rules' );
×
163

164
                // Flush the sitemap cache.
UNCOV
165
                WPSEO_Sitemaps_Cache::clear();
×
166

167
                // Make sure all our options always exist - issue #1245.
UNCOV
168
                WPSEO_Options::ensure_options_exist();
×
169
        }
170

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

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

196
                $wpseo = $this->get_option_from_database( 'wpseo' );
×
197
                $this->save_option_setting( $wpseo, 'pinterestverify' );
×
198

199
                // Re-save option to trigger sanitization.
200
                $this->cleanup_option_data( 'wpseo' );
×
201
        }
202

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

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

224
                        update_option( 'wpseo_taxonomy_meta', $taxonomies );
×
225
                }
226
        }
227

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

237
                $this->cleanup_option_data( 'wpseo' );
×
238
        }
239

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

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

260
                if ( ! empty( $wp_query->posts ) ) {
×
261
                        $options = get_option( 'wpseo_xml' );
×
262

263
                        $excluded_posts = [];
×
264
                        if ( $options['excluded-posts'] !== '' ) {
×
265
                                $excluded_posts = explode( ',', $options['excluded-posts'] );
×
266
                        }
267

268
                        foreach ( $wp_query->posts as $post ) {
×
269
                                if ( ! in_array( (string) $post->ID, $excluded_posts, true ) ) {
×
270
                                        $excluded_posts[] = $post->ID;
×
271
                                }
272
                        }
273

274
                        // Updates the meta value.
275
                        $options['excluded-posts'] = implode( ',', $excluded_posts );
×
276

277
                        // Update the option.
278
                        update_option( 'wpseo_xml', $options );
×
279
                }
280

281
                // Remove the meta fields.
282
                delete_post_meta_by_key( '_yoast_wpseo_sitemap-include' );
×
283
        }
284

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

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

305
        /**
306
         * Performs upgrade functions to Yoast SEO 3.6.
307
         *
308
         * @return void
309
         */
UNCOV
310
        protected function upgrade_36() {
×
UNCOV
311
                global $wpdb;
×
312

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

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

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

342
                $this->save_option_setting( $wpseo_titles, 'content-analysis-active', 'content_analysis_active' );
×
343
                $this->save_option_setting( $wpseo_titles, 'keyword-analysis-active', 'keyword_analysis_active' );
×
344

345
                // Remove irrelevant content from the option.
346
                $this->cleanup_option_data( 'wpseo_titles' );
×
347
        }
348

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

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

368
        /**
369
         * Removes the 'wpseo-dismiss-about' notice for every user that still has it.
370
         *
371
         * @return void
372
         */
UNCOV
373
        protected function upgrade_49() {
×
UNCOV
374
                global $wpdb;
×
375

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

UNCOV
384
                $meta_key = $wpdb->get_blog_prefix() . Yoast_Notification_Center::STORAGE_KEY;
×
385

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

UNCOV
400
                if ( empty( $usermetas ) ) {
×
401
                        return;
×
402
                }
403

UNCOV
404
                foreach ( $usermetas as $usermeta ) {
×
UNCOV
405
                        $notifications = maybe_unserialize( $usermeta['meta_value'] );
×
406

UNCOV
407
                        foreach ( $notifications as $notification_key => $notification ) {
×
UNCOV
408
                                if ( ! empty( $notification['options']['id'] ) && $notification['options']['id'] === 'wpseo-dismiss-about' ) {
×
UNCOV
409
                                        unset( $notifications[ $notification_key ] );
×
410
                                }
411
                        }
412

UNCOV
413
                        update_user_option( $usermeta['user_id'], Yoast_Notification_Center::STORAGE_KEY, array_values( $notifications ) );
×
414
                }
415
        }
416

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

431
                return $notifications;
×
432
        }
433

434
        /**
435
         * Adds the yoast_seo_links table to the database.
436
         *
437
         * @return void
438
         */
UNCOV
439
        protected function upgrade_50() {
×
UNCOV
440
                global $wpdb;
×
441

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

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

464
                // Register capabilities.
465
                do_action( 'wpseo_register_capabilities' );
×
466
                WPSEO_Capability_Manager_Factory::get()->add();
×
467
        }
468

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

478
        /**
479
         * Perform the 7.0 upgrade, moves settings around, deletes several options.
480
         *
481
         * @return void
482
         */
483
        private function upgrade_70() {
×
484

485
                $wpseo_permalinks    = $this->get_option_from_database( 'wpseo_permalinks' );
×
486
                $wpseo_xml           = $this->get_option_from_database( 'wpseo_xml' );
×
487
                $wpseo_rss           = $this->get_option_from_database( 'wpseo_rss' );
×
488
                $wpseo               = $this->get_option_from_database( 'wpseo' );
×
489
                $wpseo_internallinks = $this->get_option_from_database( 'wpseo_internallinks' );
×
490

491
                // Move some permalink settings, then delete the option.
492
                $this->save_option_setting( $wpseo_permalinks, 'redirectattachment', 'disable-attachment' );
×
493
                $this->save_option_setting( $wpseo_permalinks, 'stripcategorybase' );
×
494

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

498
                // Move the RSS settings to the search appearance settings, then delete the RSS option.
499
                $this->save_option_setting( $wpseo_rss, 'rssbefore' );
×
500
                $this->save_option_setting( $wpseo_rss, 'rssafter' );
×
501

502
                $this->save_option_setting( $wpseo, 'company_logo' );
×
503
                $this->save_option_setting( $wpseo, 'company_name' );
×
504
                $this->save_option_setting( $wpseo, 'company_or_person' );
×
505
                $this->save_option_setting( $wpseo, 'person_name' );
×
506

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

510
                // All the breadcrumbs settings have moved to the search appearance settings.
511
                foreach ( array_keys( $wpseo_internallinks ) as $key ) {
×
512
                        $this->save_option_setting( $wpseo_internallinks, $key );
×
513
                }
514

515
                // Convert hidden metabox options to display metabox options.
516
                $title_options = get_option( 'wpseo_titles' );
×
517

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

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

532
                // Cleanup removed options.
533
                delete_option( 'wpseo_xml' );
×
534
                delete_option( 'wpseo_permalinks' );
×
535
                delete_option( 'wpseo_rss' );
×
536
                delete_option( 'wpseo_internallinks' );
×
537

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

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

549
        /**
550
         * Perform the 7.1 upgrade.
551
         *
552
         * @return void
553
         */
554
        private function upgrade_71() {
×
555
                $this->cleanup_option_data( 'wpseo_social' );
×
556

557
                // Move the breadcrumbs setting and invert it.
558
                $title_options = $this->get_option_from_database( 'wpseo_titles' );
×
559

560
                if ( array_key_exists( 'breadcrumbs-blog-remove', $title_options ) ) {
×
561
                        WPSEO_Options::set( 'breadcrumbs-display-blog-page', ! $title_options['breadcrumbs-blog-remove'] );
×
562

563
                        $this->cleanup_option_data( 'wpseo_titles' );
×
564
                }
565
        }
566

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

579
                // Remove the previous Whip dismissed message, as this is a new one regarding PHP 5.2.
580
                delete_option( 'whip_dismiss_timestamp' );
×
581
        }
582

583
        /**
584
         * Performs the 7.4 upgrade.
585
         *
586
         * @return void
587
         */
UNCOV
588
        protected function upgrade_74() {
×
UNCOV
589
                $this->remove_sitemap_validators();
×
590
        }
591

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

605
                // Only when attachments are not no-indexed.
606
                if ( WPSEO_Options::get( 'noindex-attachment' ) === true ) {
×
607
                        return;
×
608
                }
609

610
                // Set purging relevancy.
611
                WPSEO_Options::set( 'is-media-purge-relevant', true );
×
612
        }
613

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

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

635
        /**
636
         * Performs the 9.0 upgrade.
637
         *
638
         * @return void
639
         */
UNCOV
640
        protected function upgrade_90() {
×
UNCOV
641
                global $wpdb;
×
642

643
                // Invalidate all sitemap cache transients.
UNCOV
644
                WPSEO_Sitemaps_Cache_Validator::cleanup_database();
×
645

646
                // Removes all scheduled tasks for hitting the sitemap index.
UNCOV
647
                wp_clear_scheduled_hook( 'wpseo_hit_sitemap_index' );
×
648

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

660
        /**
661
         * Performs the 10.0 upgrade.
662
         *
663
         * @return void
664
         */
665
        private function upgrade_100() {
×
666
                // Removes recalibration notifications.
667
                $this->clean_all_notifications();
×
668

669
                // Removes recalibration options.
670
                WPSEO_Options::clean_up( 'wpseo' );
×
671
                delete_option( 'wpseo_recalibration_beta_mailinglist_subscription' );
×
672
        }
673

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

683
                if ( ! in_array( $company_or_person, [ 'company', 'person' ], true ) ) {
×
684
                        WPSEO_Options::set( 'company_or_person', 'company' );
×
685
                }
686
        }
687

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

705
                $center = Yoast_Notification_Center::get();
×
706
                foreach ( $plugins as $plugin ) {
×
707
                        $center->remove_notification_by_id( 'wpseo-outdated-yoast-seo-plugin-' . $plugin );
×
708
                }
709
        }
710

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

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

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

744
                // Removes the WordPress update notification, because it is no longer necessary when WordPress 5.3 is released.
UNCOV
745
                $center = Yoast_Notification_Center::get();
×
UNCOV
746
                $center->remove_notification_by_id( 'wpseo-dismiss-wordpress-upgrade' );
×
747
        }
748

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

758
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-page_comments-notice' );
×
759
                Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-wordpress-upgrade' );
×
760
        }
761

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

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

780
                // Changes onpage_indexability to ryte_indexability.
781
                $wpseo_option = get_option( 'wpseo' );
×
782
                if ( isset( $wpseo_option['onpage_indexability'] ) && ! isset( $wpseo_option['ryte_indexability'] ) ) {
×
783
                        $wpseo_option['ryte_indexability'] = $wpseo_option['onpage_indexability'];
×
784
                        unset( $wpseo_option['onpage_indexability'] );
×
785
                        update_option( 'wpseo', $wpseo_option );
×
786
                }
787

788
                if ( wp_next_scheduled( 'wpseo_ryte_fetch' ) ) {
×
789
                        wp_clear_scheduled_hook( 'wpseo_ryte_fetch' );
×
790
                }
791

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

800
        /**
801
         * Perform the 14.0.3 upgrade.
802
         *
803
         * @return void
804
         */
805
        private function upgrade_1403() {
×
806
                WPSEO_Options::set( 'ignore_indexation_warning', false );
×
807
        }
808

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

822
                $this->reset_permalinks_of_attachments_for_141();
×
823
        }
824

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

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

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

856
        /**
857
         * Performs the 15.1 upgrade.
858
         *
859
         * @return void
860
         */
861
        private function upgrade_151() {
×
862
                $this->set_home_url_for_151();
×
863
                $this->move_indexables_indexation_reason_for_151();
×
864

865
                add_action( 'init', [ $this, 'set_permalink_structure_option_for_151' ] );
×
866
                add_action( 'init', [ $this, 'store_custom_taxonomy_slugs_for_151' ] );
×
867
        }
868

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

878
                // Rename a couple of options.
879
                $indexation_started_value = WPSEO_Options::get( 'indexation_started' );
×
880
                WPSEO_Options::set( 'indexing_started', $indexation_started_value );
×
881

882
                $indexables_indexing_completed_value = WPSEO_Options::get( 'indexables_indexation_completed' );
×
883
                WPSEO_Options::set( 'indexables_indexing_completed', $indexables_indexing_completed_value );
×
884
        }
885

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

895
                if ( isset( $wpseo_social_option['fbadminapp'] ) ) {
×
896
                        unset( $wpseo_social_option['fbadminapp'] );
×
897
                        update_option( 'wpseo_social', $wpseo_social_option );
×
898
                }
899
        }
900

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

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

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

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

940
                // Run after the WPSEO_Options::enrich_defaults method which has priority 99.
941
                add_action( 'init', [ $this, 'reset_og_settings_to_default_values' ], 100 );
×
942
        }
943

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

953
                if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
×
954
                        wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
×
955
                }
956
        }
957

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

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

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

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

998
        /**
999
         * Performs the 18.9 upgrade routine.
1000
         *
1001
         * @return void
1002
         */
1003
        private function upgrade_189() {
×
1004
                // Make old users not get the Installation Success page after upgrading.
1005
                WPSEO_Options::set( 'should_redirect_after_install_free', false );
×
1006
                // 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.
1007
                // If they did, they wouldn't have this hardcoded value in that option, but rather (roughly) the timestamp of the moment they saw it.
1008
                WPSEO_Options::set( 'activation_redirect_timestamp_free', 1652258756 );
×
1009

1010
                // Transfer the Social URLs.
1011
                $other   = [];
×
1012
                $other[] = WPSEO_Options::get( 'instagram_url' );
×
1013
                $other[] = WPSEO_Options::get( 'linkedin_url' );
×
1014
                $other[] = WPSEO_Options::get( 'myspace_url' );
×
1015
                $other[] = WPSEO_Options::get( 'pinterest_url' );
×
1016
                $other[] = WPSEO_Options::get( 'youtube_url' );
×
1017
                $other[] = WPSEO_Options::get( 'wikipedia_url' );
×
1018

1019
                WPSEO_Options::set( 'other_social_urls', array_values( array_unique( array_filter( $other ) ) ) );
×
1020

1021
                // Transfer the progress of the old Configuration Workout.
1022
                $workout_data      = WPSEO_Options::get( 'workouts_data' );
×
1023
                $old_conf_progress = ( $workout_data['configuration']['finishedSteps'] ?? [] );
×
1024

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

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

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

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

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

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

1094
                        $attachment_cleanup_helper->remove_attachment_indexables( true );
×
1095
                        $attachment_cleanup_helper->clean_attachment_links_from_target_indexable_ids( true );
×
1096
                }
1097

1098
                $this->clean_unindexed_indexable_rows_with_no_object_id();
×
1099

1100
                if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
×
1101
                        // 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.
1102
                        wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
×
1103
                }
1104
        }
1105

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

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

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

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

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

1161
                if ( empty( $home_url ) ) {
×
1162
                        WPSEO_Options::set( 'home_url', get_home_url() );
×
1163
                }
1164
        }
1165

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

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

1188
        /**
1189
         * Cleans up the private taxonomies from the indexables table for the upgrade routine to 14.1.
1190
         *
1191
         * @return void
1192
         */
UNCOV
1193
        public function clean_up_private_taxonomies_for_141() {
×
UNCOV
1194
                global $wpdb;
×
1195

1196
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
UNCOV
1197
                $show_errors       = $wpdb->show_errors;
×
UNCOV
1198
                $wpdb->show_errors = false;
×
1199

1200
                // Clean up indexables of private taxonomies.
UNCOV
1201
                $private_taxonomies = get_taxonomies( [ 'public' => false ], 'names' );
×
1202

UNCOV
1203
                if ( empty( $private_taxonomies ) ) {
×
1204
                        return;
×
1205
                }
1206

UNCOV
1207
                $replacements = array_merge( [ Model::get_table_name( 'Indexable' ), 'object_type', 'object_sub_type' ], $private_taxonomies );
×
1208

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

UNCOV
1222
                $wpdb->show_errors = $show_errors;
×
1223
        }
1224

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

1233
                // If migrations haven't been completed succesfully the following may give false errors. So suppress them.
1234
                $show_errors       = $wpdb->show_errors;
×
1235
                $wpdb->show_errors = false;
×
1236

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

1247
                $wpdb->show_errors = $show_errors;
×
1248
        }
1249

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

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

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

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

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

1301
                if ( $deleted ) {
×
1302
                        wp_cache_set( 'last_changed', microtime(), 'posts' );
×
1303
                }
1304
        }
1305

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

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

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

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

UNCOV
1348
                if ( ! empty( $results ) ) {
×
UNCOV
1349
                        return maybe_unserialize( $results[0]['option_value'] );
×
1350
                }
1351

UNCOV
1352
                return [];
×
1353
        }
1354

1355
        /**
1356
         * Cleans the option to make sure only relevant settings are there.
1357
         *
1358
         * @param string $option_name Option name save.
1359
         *
1360
         * @return void
1361
         */
UNCOV
1362
        protected function cleanup_option_data( $option_name ) {
×
UNCOV
1363
                $data = get_option( $option_name, [] );
×
UNCOV
1364
                if ( ! is_array( $data ) || $data === [] ) {
×
UNCOV
1365
                        return;
×
1366
                }
1367

1368
                /*
1369
                 * Clean up the option by re-saving it.
1370
                 *
1371
                 * The option framework will remove any settings that are not configured
1372
                 * for this option, removing any migrated settings.
1373
                 */
UNCOV
1374
                update_option( $option_name, $data );
×
1375
        }
1376

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

UNCOV
1391
                if ( isset( $source_data[ $source_setting ] ) ) {
×
UNCOV
1392
                        WPSEO_Options::set( $target_setting, $source_data[ $source_setting ] );
×
1393
                }
1394
        }
1395

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

1406
                if ( $shop_page_id === -1 ) {
×
1407
                        return;
×
1408
                }
1409

1410
                $title = WPSEO_Meta::get_value( 'title', $shop_page_id );
×
1411

1412
                if ( empty( $title ) ) {
×
1413
                        $option_title = WPSEO_Options::get( 'title-ptarchive-product' );
×
1414

1415
                        WPSEO_Meta::set_value(
×
1416
                                'title',
×
1417
                                $option_title,
×
1418
                                $shop_page_id
×
UNCOV
1419
                        );
×
1420

1421
                        WPSEO_Options::set( 'title-ptarchive-product', '' );
×
1422
                }
1423

1424
                $meta_description = WPSEO_Meta::get_value( 'metadesc', $shop_page_id );
×
1425

1426
                if ( empty( $meta_description ) ) {
×
1427
                        $option_metadesc = WPSEO_Options::get( 'metadesc-ptarchive-product' );
×
1428

1429
                        WPSEO_Meta::set_value(
×
1430
                                'metadesc',
×
1431
                                $option_metadesc,
×
1432
                                $shop_page_id
×
UNCOV
1433
                        );
×
1434

1435
                        WPSEO_Options::set( 'metadesc-ptarchive-product', '' );
×
1436
                }
1437

1438
                $bc_title = WPSEO_Meta::get_value( 'bctitle', $shop_page_id );
×
1439

1440
                if ( empty( $bc_title ) ) {
×
1441
                        $option_bctitle = WPSEO_Options::get( 'bctitle-ptarchive-product' );
×
1442

1443
                        WPSEO_Meta::set_value(
×
1444
                                'bctitle',
×
1445
                                $option_bctitle,
×
1446
                                $shop_page_id
×
UNCOV
1447
                        );
×
1448

1449
                        WPSEO_Options::set( 'bctitle-ptarchive-product', '' );
×
1450
                }
1451

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

1454
                if ( $noindex === '0' ) {
×
1455
                        $option_noindex = WPSEO_Options::get( 'noindex-ptarchive-product' );
×
1456

1457
                        WPSEO_Meta::set_value(
×
1458
                                'meta-robots-noindex',
×
1459
                                $option_noindex,
×
1460
                                $shop_page_id
×
UNCOV
1461
                        );
×
1462

1463
                        WPSEO_Options::set( 'noindex-ptarchive-product', false );
×
1464
                }
1465
        }
1466

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

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

1484
                $custom_taxonomies = [];
×
1485

1486
                foreach ( $taxonomies as $taxonomy ) {
×
1487
                        $slug = $this->taxonomy_helper->get_taxonomy_slug( $taxonomy );
×
1488

1489
                        $custom_taxonomies[ $taxonomy ] = $slug;
×
1490
                }
1491

1492
                WPSEO_Options::set( 'custom_taxonomy_slugs', $custom_taxonomies );
×
1493
        }
1494

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

1504
                $copied_options = [];
×
1505
                // Reset to the correct default value.
1506
                $copied_options['open_graph_frontpage_title'] = '%%sitename%%';
×
1507

UNCOV
1508
                $options = [
×
1509
                        'og_frontpage_title'    => 'open_graph_frontpage_title',
×
UNCOV
1510
                        'og_frontpage_desc'     => 'open_graph_frontpage_desc',
×
UNCOV
1511
                        'og_frontpage_image'    => 'open_graph_frontpage_image',
×
UNCOV
1512
                        'og_frontpage_image_id' => 'open_graph_frontpage_image_id',
×
UNCOV
1513
                ];
×
1514

1515
                foreach ( $options as $social_option => $titles_option ) {
×
1516
                        if ( ! empty( $wpseo_social[ $social_option ] ) ) {
×
1517
                                $copied_options[ $titles_option ] = $wpseo_social[ $social_option ];
×
1518
                        }
1519
                }
1520

1521
                $wpseo_titles = array_merge( $wpseo_titles, $copied_options );
×
1522

1523
                update_option( 'wpseo_titles', $wpseo_titles );
×
1524
        }
1525

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

1535
                $updated_options['social-title-author-wpseo']  = '%%name%%';
×
1536
                $updated_options['social-title-archive-wpseo'] = '%%date%%';
×
1537

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

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

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

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

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

1561
                if ( $taxonomy_objects ) {
×
1562
                        foreach ( $taxonomy_objects as $tax ) {
×
1563
                                if ( isset( $wpseo_titles[ 'social-title-tax-' . $tax->name ] ) ) {
×
1564
                                        $updated_options[ 'social-title-tax-' . $tax->name ] = $term_archive_default;
×
1565
                                }
1566
                        }
1567
                }
1568

1569
                $wpseo_titles = array_merge( $wpseo_titles, $updated_options );
×
1570

1571
                update_option( 'wpseo_titles', $wpseo_titles );
×
1572
        }
1573

1574
        /**
1575
         * Removes all indexables for posts that are not publicly viewable.
1576
         * This method should be called after init, because post_types can still be registered.
1577
         *
1578
         * @return void
1579
         */
UNCOV
1580
        public function remove_indexable_rows_for_non_public_post_types() {
×
UNCOV
1581
                global $wpdb;
×
1582

1583
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
UNCOV
1584
                $show_errors       = $wpdb->show_errors;
×
UNCOV
1585
                $wpdb->show_errors = false;
×
1586

UNCOV
1587
                $indexable_table = Model::get_table_name( 'Indexable' );
×
1588

UNCOV
1589
                $included_post_types = YoastSEO()->helpers->post_type->get_indexable_post_types();
×
1590

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

UNCOV
1617
                $wpdb->show_errors = $show_errors;
×
1618
        }
1619

1620
        /**
1621
         * Removes all indexables for terms that are not publicly viewable.
1622
         * This method should be called after init, because taxonomies can still be registered.
1623
         *
1624
         * @return void
1625
         */
UNCOV
1626
        public function remove_indexable_rows_for_non_public_taxonomies() {
×
UNCOV
1627
                global $wpdb;
×
1628

1629
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
UNCOV
1630
                $show_errors       = $wpdb->show_errors;
×
UNCOV
1631
                $wpdb->show_errors = false;
×
1632

UNCOV
1633
                $indexable_table = Model::get_table_name( 'Indexable' );
×
1634

UNCOV
1635
                $included_taxonomies = YoastSEO()->helpers->taxonomy->get_indexable_taxonomies();
×
1636

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

UNCOV
1663
                $wpdb->show_errors = $show_errors;
×
1664
        }
1665

1666
        /**
1667
         * De-duplicates indexables that have more than one "unindexed" rows for the same object. Keeps the newest indexable.
1668
         *
1669
         * @return void
1670
         */
UNCOV
1671
        protected function deduplicate_unindexed_indexable_rows() {
×
UNCOV
1672
                global $wpdb;
×
1673

1674
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
UNCOV
1675
                $show_errors       = $wpdb->show_errors;
×
UNCOV
1676
                $wpdb->show_errors = false;
×
1677

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

UNCOV
1702
                if ( empty( $duplicates ) ) {
×
1703
                        $wpdb->show_errors = $show_errors;
×
1704

1705
                        return;
×
1706
                }
1707

1708
                // Users, terms and posts may share the same object_id. So delete them in separate, more performant, queries.
UNCOV
1709
                $delete_queries = [
×
UNCOV
1710
                        $this->get_indexable_deduplication_query_for_type( 'post', $duplicates, $wpdb ),
×
UNCOV
1711
                        $this->get_indexable_deduplication_query_for_type( 'term', $duplicates, $wpdb ),
×
UNCOV
1712
                        $this->get_indexable_deduplication_query_for_type( 'user', $duplicates, $wpdb ),
×
UNCOV
1713
                ];
×
1714

UNCOV
1715
                foreach ( $delete_queries as $delete_query ) {
×
UNCOV
1716
                        if ( ! empty( $delete_query ) ) {
×
1717
                                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1718
                                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1719
                                // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
UNCOV
1720
                                $wpdb->query( $delete_query );
×
1721
                                // phpcs:enable
1722
                        }
1723
                }
1724

UNCOV
1725
                $wpdb->show_errors = $show_errors;
×
1726
        }
1727

1728
        /**
1729
         * Cleans up "unindexed" indexable rows when appropriate, aka when there's no object ID even though it should.
1730
         *
1731
         * @return void
1732
         */
UNCOV
1733
        protected function clean_unindexed_indexable_rows_with_no_object_id() {
×
UNCOV
1734
                global $wpdb;
×
1735

1736
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
UNCOV
1737
                $show_errors       = $wpdb->show_errors;
×
UNCOV
1738
                $wpdb->show_errors = false;
×
1739

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

UNCOV
1752
                $wpdb->show_errors = $show_errors;
×
1753
        }
1754

1755
        /**
1756
         * Removes all user indexable rows when the author archive is disabled.
1757
         *
1758
         * @return void
1759
         */
UNCOV
1760
        protected function remove_indexable_rows_for_disabled_authors_archive() {
×
UNCOV
1761
                global $wpdb;
×
1762

UNCOV
1763
                if ( ! YoastSEO()->helpers->author_archive->are_disabled() ) {
×
1764
                        return;
×
1765
                }
1766

1767
                // If migrations haven't been completed successfully the following may give false errors. So suppress them.
UNCOV
1768
                $show_errors       = $wpdb->show_errors;
×
UNCOV
1769
                $wpdb->show_errors = false;
×
1770

1771
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1772
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
UNCOV
1773
                $wpdb->query(
×
UNCOV
1774
                        $wpdb->prepare(
×
UNCOV
1775
                                "DELETE FROM %i WHERE %i = 'user'",
×
UNCOV
1776
                                [ Model::get_table_name( 'Indexable' ), 'object_type' ]
×
UNCOV
1777
                        )
×
UNCOV
1778
                );
×
1779

UNCOV
1780
                $wpdb->show_errors = $show_errors;
×
1781
        }
1782

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

UNCOV
1800
                if ( empty( $filtered_duplicates ) ) {
×
1801
                        return '';
×
1802
                }
1803

UNCOV
1804
                $object_ids           = wp_list_pluck( $filtered_duplicates, 'object_id' );
×
UNCOV
1805
                $newest_indexable_ids = wp_list_pluck( $filtered_duplicates, 'newest_id' );
×
1806

UNCOV
1807
                $replacements   = array_merge( [ Model::get_table_name( 'Indexable' ), 'object_id' ], array_values( $object_ids ), array_values( $newest_indexable_ids ) );
×
UNCOV
1808
                $replacements[] = $object_type;
×
1809

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

1823
        /**
1824
         * Removes the settings' introduction modal data for users.
1825
         *
1826
         * @return void
1827
         */
1828
        public function delete_user_introduction_meta() {
×
1829
                delete_metadata( 'user', 0, '_yoast_settings_introduction', '', true );
×
1830
        }
1831
}
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