PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.3
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.3
28.4 28.3 28.2 28.1 28.0 27.9 27.8 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 All 128 releases
wordpress-seo / src / integrations / cleanup-integration.php

cleanup-integration.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.3, at src/integrations/cleanup-integration.php

341 lines 9.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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