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

Yoast / wordpress-seo / 47c8459b21c4da54cd1b47acc2cd092c512000e8

02 Jul 2024 08:48AM UTC coverage: 55.363% (+1.3%) from 54.108%
47c8459b21c4da54cd1b47acc2cd092c512000e8

push

github

enricobattocchi
Merge branch 'refs/heads/feature/plugin-fixes' into trunk

7399 of 13400 branches covered (55.22%)

Branch coverage included in aggregate %.

294 of 357 new or added lines in 32 files covered. (82.35%)

175 existing lines in 3 files now uncovered.

29524 of 53293 relevant lines covered (55.4%)

63223.34 hits per line

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

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

3
namespace Yoast\WP\SEO\Integrations;
4

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

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

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

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

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

28
        /**
29
         * The cleanup repository.
30
         *
31
         * @var Indexable_Cleanup_Repository
32
         */
33
        private $cleanup_repository;
34

35
        /**
36
         * The constructor.
37
         *
38
         * @param Indexable_Cleanup_Repository $cleanup_repository The cleanup repository.
39
         */
40
        public function __construct( Indexable_Cleanup_Repository $cleanup_repository ) {
41
                $this->cleanup_repository = $cleanup_repository;
42
        }
43

44
        /**
45
         * Initializes the integration.
46
         *
47
         * This is the place to register hooks and filters.
48
         *
49
         * @return void
50
         */
51
        public function register_hooks() {
52
                \add_action( self::START_HOOK, [ $this, 'run_cleanup' ] );
UNCOV
53
                \add_action( self::CRON_HOOK, [ $this, 'run_cleanup_cron' ] );
×
NEW
54
                \add_action( 'wpseo_deactivate', [ $this, 'reset_cleanup' ] );
×
55
        }
56

57
        /**
58
         * Returns the conditionals based on which this loadable should be active.
59
         *
60
         * @return array The array of conditionals.
61
         */
62
        public static function get_conditionals() {
63
                return [];
64
        }
2✔
65

2✔
66
        /**
2✔
67
         * Starts the indexables cleanup.
2✔
68
         *
1✔
69
         * @return void
70
         */
71
        public function run_cleanup() {
72
                $this->reset_cleanup();
73

74
                $cleanups = $this->get_cleanup_tasks();
75
                $limit    = $this->get_limit();
2✔
76

2✔
77
                foreach ( $cleanups as $name => $action ) {
78
                        $items_cleaned = $action( $limit );
79

80
                        if ( $items_cleaned === false ) {
81
                                return;
82
                        }
83

84
                        if ( $items_cleaned < $limit ) {
6✔
85
                                continue;
6✔
86
                        }
87

6✔
88
                        // There are more items to delete for the current cleanup job, start a cronjob at the specified job.
NEW
89
                        $this->start_cron_job( $name );
×
90

91
                        return;
92
                }
6✔
93
        }
6✔
94

95
        /**
6✔
96
         * Returns an array of cleanup tasks.
6✔
97
         *
98
         * @return Closure[] The cleanup tasks.
6✔
99
         */
2✔
100
        public function get_cleanup_tasks() {
101
                return \array_merge(
102
                        [
4✔
103
                                'clean_indexables_with_object_type_and_object_sub_type_shop_order' => function ( $limit ) {
2✔
104
                                        return $this->cleanup_repository->clean_indexables_with_object_type_and_object_sub_type( 'post', 'shop_order', $limit );
105
                                },
106
                                'clean_indexables_by_post_status_auto-draft' => function ( $limit ) {
107
                                        return $this->cleanup_repository->clean_indexables_with_post_status( 'auto-draft', $limit );
2✔
108
                                },
109
                                'clean_indexables_for_non_publicly_viewable_post' => function ( $limit ) {
2✔
110
                                        return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_post( $limit );
111
                                },
1✔
112
                                'clean_indexables_for_non_publicly_viewable_taxonomies' => function ( $limit ) {
113
                                        return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_taxonomies( $limit );
114
                                },
115
                                'clean_indexables_for_non_publicly_viewable_post_type_archive_pages' => function ( $limit ) {
116
                                        return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_post_type_archive_pages( $limit );
117
                                },
118
                                'clean_indexables_for_authors_archive_disabled' => function ( $limit ) {
16✔
119
                                        return $this->cleanup_repository->clean_indexables_for_authors_archive_disabled( $limit );
16✔
120
                                },
8✔
121
                                'clean_indexables_for_authors_without_archive' => function ( $limit ) {
8✔
122
                                        return $this->cleanup_repository->clean_indexables_for_authors_without_archive( $limit );
8✔
123
                                },
16✔
124
                                'update_indexables_author_to_reassigned' => function ( $limit ) {
8✔
125
                                        return $this->cleanup_repository->update_indexables_author_to_reassigned( $limit );
8✔
126
                                },
16✔
127
                                'clean_orphaned_user_indexables_without_wp_user' => function ( $limit ) {
8✔
128
                                        return $this->cleanup_repository->clean_indexables_for_orphaned_users( $limit );
2✔
129
                                },
16✔
130
                                'clean_orphaned_user_indexables_without_wp_post' => function ( $limit ) {
8✔
131
                                        return $this->cleanup_repository->clean_indexables_for_object_type_and_source_table( 'posts', 'ID', 'post', $limit );
2✔
132
                                },
16✔
133
                                'clean_orphaned_user_indexables_without_wp_term' => function ( $limit ) {
8✔
134
                                        return $this->cleanup_repository->clean_indexables_for_object_type_and_source_table( 'terms', 'term_id', 'term', $limit );
2✔
135
                                },
16✔
136
                        ],
8✔
137
                        $this->get_additional_indexable_cleanups(),
2✔
138
                        [
16✔
139
                                /* These should always be the last ones to be called. */
8✔
140
                                'clean_orphaned_content_indexable_hierarchy' => function ( $limit ) {
2✔
141
                                        return $this->cleanup_repository->cleanup_orphaned_from_table( 'Indexable_Hierarchy', 'indexable_id', $limit );
16✔
142
                                },
8✔
143
                                'clean_orphaned_content_seo_links_indexable_id' => function ( $limit ) {
2✔
144
                                        return $this->cleanup_repository->cleanup_orphaned_from_table( 'SEO_Links', 'indexable_id', $limit );
16✔
145
                                },
8✔
146
                                'clean_orphaned_content_seo_links_target_indexable_id' => function ( $limit ) {
2✔
147
                                        return $this->cleanup_repository->cleanup_orphaned_from_table( 'SEO_Links', 'target_indexable_id', $limit );
16✔
148
                                },
8✔
149

2✔
150
                        ],
16✔
151
                        $this->get_additional_misc_cleanups()
8✔
152
                );
2✔
153
        }
16✔
154

8✔
155
        /**
16✔
156
         * Gets additional tasks from the 'wpseo_cleanup_tasks' filter.
8✔
157
         *
158
         * @return Closure[] Associative array of indexable cleanup functions.
8✔
159
         */
2✔
160
        private function get_additional_indexable_cleanups() {
16✔
161

8✔
162
                /**
2✔
163
                 * Filter: Adds the possibility to add additional indexable cleanup functions.
16✔
164
                 *
8✔
165
                 * @param array $additional_tasks Associative array with unique keys. Value should be a cleanup function that receives a limit.
4✔
166
                 */
16✔
167
                $additional_tasks = \apply_filters( 'wpseo_cleanup_tasks', [] );
168

8✔
169
                return $this->validate_additional_tasks( $additional_tasks );
16✔
170
        }
8✔
171

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

179
                /**
180
                 * Filter: Adds the possibility to add additional non-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_misc_cleanup_tasks', [] );
185

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

189
        /**
190
         * Validates the additional tasks.
191
         *
192
         * @param Closure[] $additional_tasks The additional tasks to validate.
193
         *
194
         * @return Closure[] The validated additional tasks.
195
         */
196
        private function validate_additional_tasks( $additional_tasks ) {
197
                if ( ! \is_array( $additional_tasks ) ) {
198
                        return [];
199
                }
200

201
                foreach ( $additional_tasks as $key => $value ) {
202
                        if ( \is_int( $key ) ) {
×
203
                                return [];
204
                        }
205
                        if ( ( ! \is_object( $value ) ) || ! ( $value instanceof Closure ) ) {
206
                                return [];
207
                        }
208
                }
209

210
                return $additional_tasks;
211
        }
212

213
        /**
214
         * Gets the deletion limit for cleanups.
215
         *
216
         * @return int The limit for the amount of entities to be cleaned.
217
         */
218
        private function get_limit() {
219
                /**
220
                 * Filter: Adds the possibility to limit the number of items that are deleted from the database on cleanup.
221
                 *
222
                 * @param int $limit Maximum number of indexables to be cleaned up per query.
223
                 */
224
                $limit = \apply_filters( 'wpseo_cron_query_limit_size', 1000 );
×
225

226
                if ( ! \is_int( $limit ) ) {
227
                        $limit = 1000;
228
                }
229

230
                return \abs( $limit );
231
        }
232

233
        /**
234
         * Resets and stops the cleanup integration.
235
         *
236
         * @return void
16✔
237
         */
238
        public function reset_cleanup() {
239
                \delete_option( self::CURRENT_TASK_OPTION );
240
                \wp_unschedule_hook( self::CRON_HOOK );
241
        }
242

16✔
243
        /**
244
         * Starts the cleanup cron job.
16✔
245
         *
2✔
246
         * @param string $task_name     The task name of the next cleanup task to run.
247
         * @param int    $schedule_time The time in seconds to wait before running the first cron job. Default is 1 hour.
248
         *
16✔
249
         * @return void
250
         */
251
        public function start_cron_job( $task_name, $schedule_time = 3600 ) {
252
                \update_option( self::CURRENT_TASK_OPTION, $task_name );
253
                \wp_schedule_event(
254
                        ( \time() + $schedule_time ),
255
                        'hourly',
256
                        self::CRON_HOOK
6✔
257
                );
6✔
258
        }
6✔
259

3✔
260
        /**
261
         * The callback that is called for the cleanup cron job.
262
         *
263
         * @return void
264
         */
265
        public function run_cleanup_cron() {
266
                $current_task_name = \get_option( self::CURRENT_TASK_OPTION );
267

268
                if ( $current_task_name === false ) {
269
                        $this->reset_cleanup();
2✔
270

2✔
271
                        return;
2✔
272
                }
2✔
273

2✔
274
                $limit = $this->get_limit();
2✔
275
                $tasks = $this->get_cleanup_tasks();
1✔
276

1✔
277
                // The task may have been added by a filter that has been removed, in that case just start over.
278
                if ( ! isset( $tasks[ $current_task_name ] ) ) {
279
                        $current_task_name = \key( $tasks );
280
                }
281

282
                $current_task = \current( $tasks );
283
                while ( $current_task !== false ) {
14✔
284
                        // Skip the tasks that have already been done.
14✔
285
                        if ( \key( $tasks ) !== $current_task_name ) {
2✔
286
                                $current_task = \next( $tasks );
287
                                continue;
2✔
288
                        }
289

290
                        // Call the cleanup callback function that accompanies the current task.
12✔
291
                        $items_cleaned = $current_task( $limit );
292

12✔
293
                        if ( $items_cleaned === false ) {
2✔
294
                                $this->reset_cleanup();
295

2✔
296
                                return;
297
                        }
298

10✔
299
                        if ( $items_cleaned === 0 ) {
10✔
300
                                // Check if we are finished with all tasks.
301
                                if ( \next( $tasks ) === false ) {
302
                                        $this->reset_cleanup();
10✔
303

304
                                        return;
305
                                }
306

10✔
307
                                // Continue with the next task next time the cron job is run.
10✔
308
                                \update_option( self::CURRENT_TASK_OPTION, \key( $tasks ) );
309

10✔
310
                                return;
8✔
311
                        }
8✔
312

313
                        // There were items deleted for the current task, continue with the same task next cron call.
314
                        return;
315
                }
10✔
316
        }
317
}
10✔
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