PluginProbe ʕ •ᴥ•ʔ
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 27.7
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v27.7
27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 19.1 19.10 19.11 19.12 19.13 19.14 19.2 19.3 19.4 19.5 19.5.1 19.6 19.6.1 19.7 19.7.1 19.7.2 19.8 19.9 20.0 20.1 20.10 20.11 20.12 20.13 20.2 20.2.1 20.3 20.4 20.5 20.6 20.7 20.8 20.9 21.0 21.1 21.2 21.3 21.4 21.5 21.6 21.7 21.8 21.8.1 21.9 21.9.1 22.0 22.1 22.2 22.3 22.4 22.5 22.6 22.7 22.8 22.9 23.0 23.1 23.2 23.3 23.4 23.5 23.6 23.7 23.8 23.9 24.0 24.1 24.2 24.3 24.4 24.5 24.6 24.7 24.8 24.8.1 24.9 25.0 25.1 25.2 25.3 25.3.1 25.4 25.5 25.6 25.7 25.8 25.9 26.0 26.1 26.1.1 26.2 26.3 26.4 26.5 26.6 26.7 26.8 26.9 27.0 27.1 27.1.1 27.2 27.3 27.4
wordpress-seo / src / integrations / cleanup-integration.php
wordpress-seo / src / integrations Last commit date
admin 3 weeks ago alerts 9 months ago blocks 4 weeks ago front-end 3 months ago third-party 3 months ago watchers 3 months ago abstract-exclude-post-type.php 1 year ago academy-integration.php 3 weeks ago breadcrumbs-integration.php 2 years ago cleanup-integration.php 3 months ago estimated-reading-time.php 5 years ago exclude-attachment-post-type.php 3 years ago exclude-oembed-cache-post-type.php 1 year ago feature-flag-integration.php 4 years ago front-end-integration.php 1 month ago integration-interface.php 5 years ago primary-category.php 5 years ago settings-integration.php 2 months ago support-integration.php 6 months ago uninstall-integration.php 4 years ago woocommerce-product-category-permalink-integration.php 3 months ago xmlrpc.php 4 years ago
cleanup-integration.php
341 lines
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() {
65 \add_action( self::START_HOOK, [ $this, 'run_cleanup' ] );
66 \add_action( self::CRON_HOOK, [ $this, 'run_cleanup_cron' ] );
67 \add_action( 'wpseo_deactivate', [ $this, 'reset_cleanup' ] );
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() {
76 return [];
77 }
78
79 /**
80 * Starts the indexables cleanup.
81 *
82 * @return void
83 */
84 public function run_cleanup() {
85 $this->reset_cleanup();
86
87 if ( ! $this->indexable_helper->should_index_indexables() ) {
88 \wp_unschedule_hook( self::START_HOOK );
89 return;
90 }
91
92 $cleanups = $this->get_cleanup_tasks();
93 $limit = $this->get_limit();
94
95 foreach ( $cleanups as $name => $action ) {
96 $items_cleaned = $action( $limit );
97
98 if ( $items_cleaned === false ) {
99 return;
100 }
101
102 if ( $items_cleaned < $limit ) {
103 continue;
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 );
108
109 return;
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() {
119 return \array_merge(
120 [
121 'clean_indexables_with_object_type_and_object_sub_type_shop_order' => function ( $limit ) {
122 return $this->cleanup_repository->clean_indexables_with_object_type_and_object_sub_type( 'post', 'shop_order', $limit );
123 },
124 'clean_indexables_by_post_status_auto-draft' => function ( $limit ) {
125 return $this->cleanup_repository->clean_indexables_with_post_status( 'auto-draft', $limit );
126 },
127 'clean_indexables_for_non_publicly_viewable_post' => function ( $limit ) {
128 return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_post( $limit );
129 },
130 'clean_indexables_for_non_publicly_viewable_taxonomies' => function ( $limit ) {
131 return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_taxonomies( $limit );
132 },
133 'clean_indexables_for_non_publicly_viewable_post_type_archive_pages' => function ( $limit ) {
134 return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_post_type_archive_pages( $limit );
135 },
136 'clean_indexables_for_authors_archive_disabled' => function ( $limit ) {
137 return $this->cleanup_repository->clean_indexables_for_authors_archive_disabled( $limit );
138 },
139 'clean_indexables_for_authors_without_archive' => function ( $limit ) {
140 return $this->cleanup_repository->clean_indexables_for_authors_without_archive( $limit );
141 },
142 'update_indexables_author_to_reassigned' => function ( $limit ) {
143 return $this->cleanup_repository->update_indexables_author_to_reassigned( $limit );
144 },
145 'clean_orphaned_user_indexables_without_wp_user' => function ( $limit ) {
146 return $this->cleanup_repository->clean_indexables_for_orphaned_users( $limit );
147 },
148 'clean_orphaned_user_indexables_without_wp_post' => function ( $limit ) {
149 return $this->cleanup_repository->clean_indexables_for_object_type_and_source_table( 'posts', 'ID', 'post', $limit );
150 },
151 'clean_orphaned_user_indexables_without_wp_term' => function ( $limit ) {
152 return $this->cleanup_repository->clean_indexables_for_object_type_and_source_table( 'terms', 'term_id', 'term', $limit );
153 },
154 ],
155 $this->get_additional_indexable_cleanups(),
156 [
157 /* These should always be the last ones to be called. */
158 'clean_orphaned_content_indexable_hierarchy' => function ( $limit ) {
159 return $this->cleanup_repository->cleanup_orphaned_from_table( 'Indexable_Hierarchy', 'indexable_id', $limit );
160 },
161 'clean_orphaned_content_seo_links_indexable_id' => function ( $limit ) {
162 return $this->cleanup_repository->cleanup_orphaned_from_table( 'SEO_Links', 'indexable_id', $limit );
163 },
164 'clean_orphaned_content_seo_links_target_indexable_id' => function ( $limit ) {
165 return $this->cleanup_repository->cleanup_orphaned_from_table( 'SEO_Links', 'target_indexable_id', $limit );
166 },
167 ],
168 $this->get_additional_misc_cleanups(),
169 );
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() {
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 );
242
243 if ( ! \is_int( $limit ) ) {
244 $limit = 1000;
245 }
246
247 return \abs( $limit );
248 }
249
250 /**
251 * Resets and stops the cleanup integration.
252 *
253 * @return void
254 */
255 public function reset_cleanup() {
256 \delete_option( self::CURRENT_TASK_OPTION );
257 \wp_unschedule_hook( self::CRON_HOOK );
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 ) {
269 \update_option( self::CURRENT_TASK_OPTION, $task_name );
270 \wp_schedule_event(
271 ( \time() + $schedule_time ),
272 'hourly',
273 self::CRON_HOOK,
274 );
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() {
283 if ( ! $this->indexable_helper->should_index_indexables() ) {
284 $this->reset_cleanup();
285
286 return;
287 }
288
289 $current_task_name = \get_option( self::CURRENT_TASK_OPTION );
290
291 if ( $current_task_name === false ) {
292 $this->reset_cleanup();
293
294 return;
295 }
296
297 $limit = $this->get_limit();
298 $tasks = $this->get_cleanup_tasks();
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 ] ) ) {
302 $current_task_name = \key( $tasks );
303 }
304
305 $current_task = \current( $tasks );
306 while ( $current_task !== false ) {
307 // Skip the tasks that have already been done.
308 if ( \key( $tasks ) !== $current_task_name ) {
309 $current_task = \next( $tasks );
310 continue;
311 }
312
313 // Call the cleanup callback function that accompanies the current task.
314 $items_cleaned = $current_task( $limit );
315
316 if ( $items_cleaned === false ) {
317 $this->reset_cleanup();
318
319 return;
320 }
321
322 if ( $items_cleaned === 0 ) {
323 // Check if we are finished with all tasks.
324 if ( \next( $tasks ) === false ) {
325 $this->reset_cleanup();
326
327 return;
328 }
329
330 // Continue with the next task next time the cron job is run.
331 \update_option( self::CURRENT_TASK_OPTION, \key( $tasks ) );
332
333 return;
334 }
335
336 // There were items deleted for the current task, continue with the same task next cron call.
337 return;
338 }
339 }
340 }
341