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

Yoast / wordpress-seo / 329e7257b699570577e9617a4ea672714686da15

26 Jan 2026 07:52AM UTC coverage: 52.923% (-0.4%) from 53.35%
329e7257b699570577e9617a4ea672714686da15

Pull #22888

github

web-flow
Merge 00d428c6c into ff2f33de1
Pull Request #22888: Add integration tests for analytics adapter

8393 of 15825 branches covered (53.04%)

Branch coverage included in aggregate %.

32383 of 61223 relevant lines covered (52.89%)

47097.05 hits per line

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

84.21
/src/integrations/cleanup-integration.php
1
<?php
2

3
namespace Yoast\WP\SEO\Integrations;
4

5
use Closure;
6
use Yoast\WP\SEO\Helpers\Indexable_Helper;
7
use Yoast\WP\SEO\Repositories\Indexable_Cleanup_Repository;
8

9
/**
10
 * Adds cleanup hooks.
11
 */
12
class Cleanup_Integration implements Integration_Interface {
13

14
        /**
15
         * Identifier used to determine the current task.
16
         */
17
        public const CURRENT_TASK_OPTION = 'wpseo-cleanup-current-task';
18

19
        /**
20
         * Identifier for the cron job.
21
         */
22
        public const CRON_HOOK = 'wpseo_cleanup_cron';
23

24
        /**
25
         * Identifier for starting the cleanup.
26
         */
27
        public const START_HOOK = 'wpseo_start_cleanup_indexables';
28

29
        /**
30
         * The indexable helper.
31
         *
32
         * @var Indexable_Helper
33
         */
34
        private $indexable_helper;
35

36
        /**
37
         * The cleanup repository.
38
         *
39
         * @var Indexable_Cleanup_Repository
40
         */
41
        private $cleanup_repository;
42

43
        /**
44
         * The constructor.
45
         *
46
         * @param Indexable_Cleanup_Repository $cleanup_repository The cleanup repository.
47
         * @param Indexable_Helper             $indexable_helper   The indexable helper.
48
         */
49
        public function __construct(
×
50
                Indexable_Cleanup_Repository $cleanup_repository,
51
                Indexable_Helper $indexable_helper
52
        ) {
53
                $this->cleanup_repository = $cleanup_repository;
×
54
                $this->indexable_helper   = $indexable_helper;
×
55
        }
56

57
        /**
58
         * Initializes the integration.
59
         *
60
         * This is the place to register hooks and filters.
61
         *
62
         * @return void
63
         */
64
        public function register_hooks() {
2✔
65
                \add_action( self::START_HOOK, [ $this, 'run_cleanup' ] );
2✔
66
                \add_action( self::CRON_HOOK, [ $this, 'run_cleanup_cron' ] );
2✔
67
                \add_action( 'wpseo_deactivate', [ $this, 'reset_cleanup' ] );
2✔
68
        }
69

70
        /**
71
         * Returns the conditionals based on which this loadable should be active.
72
         *
73
         * @return array<string> The array of conditionals.
74
         */
75
        public static function get_conditionals() {
2✔
76
                return [];
2✔
77
        }
78

79
        /**
80
         * Starts the indexables cleanup.
81
         *
82
         * @return void
83
         */
84
        public function run_cleanup() {
6✔
85
                $this->reset_cleanup();
6✔
86

87
                if ( ! $this->indexable_helper->should_index_indexables() ) {
6✔
88
                        \wp_unschedule_hook( self::START_HOOK );
×
89
                        return;
×
90
                }
91

92
                $cleanups = $this->get_cleanup_tasks();
6✔
93
                $limit    = $this->get_limit();
6✔
94

95
                foreach ( $cleanups as $name => $action ) {
6✔
96
                        $items_cleaned = $action( $limit );
6✔
97

98
                        if ( $items_cleaned === false ) {
6✔
99
                                return;
2✔
100
                        }
101

102
                        if ( $items_cleaned < $limit ) {
4✔
103
                                continue;
2✔
104
                        }
105

106
                        // There are more items to delete for the current cleanup job, start a cronjob at the specified job.
107
                        $this->start_cron_job( $name );
2✔
108

109
                        return;
2✔
110
                }
111
        }
112

113
        /**
114
         * Returns an array of cleanup tasks.
115
         *
116
         * @return Closure[] The cleanup tasks.
117
         */
118
        public function get_cleanup_tasks() {
16✔
119
                return \array_merge(
16✔
120
                        [
16✔
121
                                'clean_indexables_with_object_type_and_object_sub_type_shop_order' => function ( $limit ) {
16✔
122
                                        return $this->cleanup_repository->clean_indexables_with_object_type_and_object_sub_type( 'post', 'shop_order', $limit );
8✔
123
                                },
16✔
124
                                'clean_indexables_by_post_status_auto-draft' => function ( $limit ) {
16✔
125
                                        return $this->cleanup_repository->clean_indexables_with_post_status( 'auto-draft', $limit );
8✔
126
                                },
16✔
127
                                'clean_indexables_for_non_publicly_viewable_post' => function ( $limit ) {
16✔
128
                                        return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_post( $limit );
2✔
129
                                },
16✔
130
                                'clean_indexables_for_non_publicly_viewable_taxonomies' => function ( $limit ) {
16✔
131
                                        return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_taxonomies( $limit );
2✔
132
                                },
16✔
133
                                'clean_indexables_for_non_publicly_viewable_post_type_archive_pages' => function ( $limit ) {
16✔
134
                                        return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_post_type_archive_pages( $limit );
2✔
135
                                },
16✔
136
                                'clean_indexables_for_authors_archive_disabled' => function ( $limit ) {
16✔
137
                                        return $this->cleanup_repository->clean_indexables_for_authors_archive_disabled( $limit );
2✔
138
                                },
16✔
139
                                'clean_indexables_for_authors_without_archive' => function ( $limit ) {
16✔
140
                                        return $this->cleanup_repository->clean_indexables_for_authors_without_archive( $limit );
2✔
141
                                },
16✔
142
                                'update_indexables_author_to_reassigned' => function ( $limit ) {
16✔
143
                                        return $this->cleanup_repository->update_indexables_author_to_reassigned( $limit );
2✔
144
                                },
16✔
145
                                'clean_orphaned_user_indexables_without_wp_user' => function ( $limit ) {
16✔
146
                                        return $this->cleanup_repository->clean_indexables_for_orphaned_users( $limit );
2✔
147
                                },
16✔
148
                                'clean_orphaned_user_indexables_without_wp_post' => function ( $limit ) {
16✔
149
                                        return $this->cleanup_repository->clean_indexables_for_object_type_and_source_table( 'posts', 'ID', 'post', $limit );
2✔
150
                                },
16✔
151
                                'clean_orphaned_user_indexables_without_wp_term' => function ( $limit ) {
16✔
152
                                        return $this->cleanup_repository->clean_indexables_for_object_type_and_source_table( 'terms', 'term_id', 'term', $limit );
2✔
153
                                },
16✔
154
                        ],
16✔
155
                        $this->get_additional_indexable_cleanups(),
16✔
156
                        [
16✔
157
                                /* These should always be the last ones to be called. */
158
                                'clean_orphaned_content_indexable_hierarchy' => function ( $limit ) {
16✔
159
                                        return $this->cleanup_repository->cleanup_orphaned_from_table( 'Indexable_Hierarchy', 'indexable_id', $limit );
2✔
160
                                },
16✔
161
                                'clean_orphaned_content_seo_links_indexable_id' => function ( $limit ) {
16✔
162
                                        return $this->cleanup_repository->cleanup_orphaned_from_table( 'SEO_Links', 'indexable_id', $limit );
2✔
163
                                },
16✔
164
                                'clean_orphaned_content_seo_links_target_indexable_id' => function ( $limit ) {
16✔
165
                                        return $this->cleanup_repository->cleanup_orphaned_from_table( 'SEO_Links', 'target_indexable_id', $limit );
4✔
166
                                },
16✔
167
                        ],
16✔
168
                        $this->get_additional_misc_cleanups()
16✔
169
                );
16✔
170
        }
171

172
        /**
173
         * Gets additional tasks from the 'wpseo_cleanup_tasks' filter.
174
         *
175
         * @return Closure[] Associative array of indexable cleanup functions.
176
         */
177
        private function get_additional_indexable_cleanups() {
×
178

179
                /**
180
                 * Filter: Adds the possibility to add additional indexable cleanup functions.
181
                 *
182
                 * @param array $additional_tasks Associative array with unique keys. Value should be a cleanup function that receives a limit.
183
                 */
184
                $additional_tasks = \apply_filters( 'wpseo_cleanup_tasks', [] );
×
185

186
                return $this->validate_additional_tasks( $additional_tasks );
×
187
        }
188

189
        /**
190
         * Gets additional tasks from the 'wpseo_misc_cleanup_tasks' filter.
191
         *
192
         * @return Closure[] Associative array of indexable cleanup functions.
193
         */
194
        private function get_additional_misc_cleanups() {
×
195

196
                /**
197
                 * Filter: Adds the possibility to add additional non-indexable cleanup functions.
198
                 *
199
                 * @param array $additional_tasks Associative array with unique keys. Value should be a cleanup function that receives a limit.
200
                 */
201
                $additional_tasks = \apply_filters( 'wpseo_misc_cleanup_tasks', [] );
×
202

203
                return $this->validate_additional_tasks( $additional_tasks );
×
204
        }
205

206
        /**
207
         * Validates the additional tasks.
208
         *
209
         * @param Closure[] $additional_tasks The additional tasks to validate.
210
         *
211
         * @return Closure[] The validated additional tasks.
212
         */
213
        private function validate_additional_tasks( $additional_tasks ) {
×
214
                if ( ! \is_array( $additional_tasks ) ) {
×
215
                        return [];
×
216
                }
217

218
                foreach ( $additional_tasks as $key => $value ) {
×
219
                        if ( \is_int( $key ) ) {
×
220
                                return [];
×
221
                        }
222
                        if ( ( ! \is_object( $value ) ) || ! ( $value instanceof Closure ) ) {
×
223
                                return [];
×
224
                        }
225
                }
226

227
                return $additional_tasks;
×
228
        }
229

230
        /**
231
         * Gets the deletion limit for cleanups.
232
         *
233
         * @return int The limit for the amount of entities to be cleaned.
234
         */
235
        private function get_limit() {
16✔
236
                /**
237
                 * Filter: Adds the possibility to limit the number of items that are deleted from the database on cleanup.
238
                 *
239
                 * @param int $limit Maximum number of indexables to be cleaned up per query.
240
                 */
241
                $limit = \apply_filters( 'wpseo_cron_query_limit_size', 1000 );
16✔
242

243
                if ( ! \is_int( $limit ) ) {
16✔
244
                        $limit = 1000;
2✔
245
                }
246

247
                return \abs( $limit );
16✔
248
        }
249

250
        /**
251
         * Resets and stops the cleanup integration.
252
         *
253
         * @return void
254
         */
255
        public function reset_cleanup() {
6✔
256
                \delete_option( self::CURRENT_TASK_OPTION );
6✔
257
                \wp_unschedule_hook( self::CRON_HOOK );
6✔
258
        }
259

260
        /**
261
         * Starts the cleanup cron job.
262
         *
263
         * @param string $task_name     The task name of the next cleanup task to run.
264
         * @param int    $schedule_time The time in seconds to wait before running the first cron job. Default is 1 hour.
265
         *
266
         * @return void
267
         */
268
        public function start_cron_job( $task_name, $schedule_time = 3600 ) {
2✔
269
                \update_option( self::CURRENT_TASK_OPTION, $task_name );
2✔
270
                \wp_schedule_event(
2✔
271
                        ( \time() + $schedule_time ),
2✔
272
                        'hourly',
2✔
273
                        self::CRON_HOOK
2✔
274
                );
2✔
275
        }
276

277
        /**
278
         * The callback that is called for the cleanup cron job.
279
         *
280
         * @return void
281
         */
282
        public function run_cleanup_cron() {
14✔
283
                if ( ! $this->indexable_helper->should_index_indexables() ) {
14✔
284
                        $this->reset_cleanup();
2✔
285

286
                        return;
2✔
287
                }
288

289
                $current_task_name = \get_option( self::CURRENT_TASK_OPTION );
12✔
290

291
                if ( $current_task_name === false ) {
12✔
292
                        $this->reset_cleanup();
2✔
293

294
                        return;
2✔
295
                }
296

297
                $limit = $this->get_limit();
10✔
298
                $tasks = $this->get_cleanup_tasks();
10✔
299

300
                // The task may have been added by a filter that has been removed, in that case just start over.
301
                if ( ! isset( $tasks[ $current_task_name ] ) ) {
10✔
302
                        $current_task_name = \key( $tasks );
×
303
                }
304

305
                $current_task = \current( $tasks );
10✔
306
                while ( $current_task !== false ) {
10✔
307
                        // Skip the tasks that have already been done.
308
                        if ( \key( $tasks ) !== $current_task_name ) {
10✔
309
                                $current_task = \next( $tasks );
8✔
310
                                continue;
8✔
311
                        }
312

313
                        // Call the cleanup callback function that accompanies the current task.
314
                        $items_cleaned = $current_task( $limit );
10✔
315

316
                        if ( $items_cleaned === false ) {
10✔
317
                                $this->reset_cleanup();
2✔
318

319
                                return;
2✔
320
                        }
321

322
                        if ( $items_cleaned === 0 ) {
8✔
323
                                // Check if we are finished with all tasks.
324
                                if ( \next( $tasks ) === false ) {
4✔
325
                                        $this->reset_cleanup();
2✔
326

327
                                        return;
2✔
328
                                }
329

330
                                // Continue with the next task next time the cron job is run.
331
                                \update_option( self::CURRENT_TASK_OPTION, \key( $tasks ) );
2✔
332

333
                                return;
2✔
334
                        }
335

336
                        // There were items deleted for the current task, continue with the same task next cron call.
337
                        return;
4✔
338
                }
339
        }
340
}
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

© 2026 Coveralls, Inc