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.5 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 All 129 releases
wordpress-seo / src / integrations / admin / background-indexing-integration.php

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

340 lines 10.8 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\Admin;
4
5 use Yoast\WP\SEO\Actions\Indexing\Indexable_Indexing_Complete_Action;
6 use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface;
7 use Yoast\WP\SEO\Conditionals\Get_Request_Conditional;
8 use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
9 use Yoast\WP\SEO\Conditionals\WP_CRON_Enabled_Conditional;
10 use Yoast\WP\SEO\Conditionals\Yoast_Admin_And_Dashboard_Conditional;
11 use Yoast\WP\SEO\Helpers\Indexable_Helper;
12 use Yoast\WP\SEO\Helpers\Indexing_Helper;
13 use Yoast\WP\SEO\Integrations\Integration_Interface;
14
15 /**
16 * Class Background_Indexing_Integration.
17 *
18 * @package Yoast\WP\SEO\Integrations\Admin
19 */
20 class Background_Indexing_Integration implements Integration_Interface {
21
22 /**
23 * Represents the indexing completed action.
24 *
25 * @var Indexable_Indexing_Complete_Action
26 */
27 protected $complete_indexation_action;
28
29 /**
30 * Represents the indexing helper.
31 *
32 * @var Indexing_Helper
33 */
34 protected $indexing_helper;
35
36 /**
37 * An object that checks if we are on the Yoast admin or on the dashboard page.
38 *
39 * @var Yoast_Admin_And_Dashboard_Conditional
40 */
41 protected $yoast_admin_and_dashboard_conditional;
42
43 /**
44 * All available indexing actions.
45 *
46 * @var Indexation_Action_Interface[]
47 */
48 protected $indexing_actions;
49
50 /**
51 * An object that checks if we are handling a GET request.
52 *
53 * @var Get_Request_Conditional
54 */
55 private $get_request_conditional;
56
57 /**
58 * An object that checks if WP_CRON is enabled.
59 *
60 * @var WP_CRON_Enabled_Conditional
61 */
62 private $wp_cron_enabled_conditional;
63
64 /**
65 * The indexable helper
66 *
67 * @var Indexable_Helper
68 */
69 private $indexable_helper;
70
71 /**
72 * Shutdown_Indexing_Integration constructor.
73 *
74 * @param Indexable_Indexing_Complete_Action $complete_indexation_action The complete indexing action.
75 * @param Indexing_Helper $indexing_helper The indexing helper.
76 * @param Indexable_Helper $indexable_helper The indexable helper.
77 * @param Yoast_Admin_And_Dashboard_Conditional $yoast_admin_and_dashboard_conditional An object that checks if we are on the Yoast admin or on the dashboard page.
78 * @param Get_Request_Conditional $get_request_conditional An object that checks if we are handling a GET request.
79 * @param WP_CRON_Enabled_Conditional $wp_cron_enabled_conditional An object that checks if WP_CRON is enabled.
80 * @param Indexation_Action_Interface ...$indexing_actions A list of all available indexing actions.
81 */
82 public function __construct(
83 Indexable_Indexing_Complete_Action $complete_indexation_action,
84 Indexing_Helper $indexing_helper,
85 Indexable_Helper $indexable_helper,
86 Yoast_Admin_And_Dashboard_Conditional $yoast_admin_and_dashboard_conditional,
87 Get_Request_Conditional $get_request_conditional,
88 WP_CRON_Enabled_Conditional $wp_cron_enabled_conditional,
89 Indexation_Action_Interface ...$indexing_actions
90 ) {
91 $this->indexing_actions = $indexing_actions;
92 $this->complete_indexation_action = $complete_indexation_action;
93 $this->indexing_helper = $indexing_helper;
94 $this->indexable_helper = $indexable_helper;
95 $this->yoast_admin_and_dashboard_conditional = $yoast_admin_and_dashboard_conditional;
96 $this->get_request_conditional = $get_request_conditional;
97 $this->wp_cron_enabled_conditional = $wp_cron_enabled_conditional;
98 }
99
100 /**
101 * Returns the conditionals based on which this integration should be active.
102 *
103 * @return array The array of conditionals.
104 */
105 public static function get_conditionals() {
106 return [
107 Migrations_Conditional::class,
108 ];
109 }
110
111 /**
112 * Register hooks.
113 *
114 * @return void
115 */
116 public function register_hooks() {
117 \add_action( 'admin_init', [ $this, 'register_shutdown_indexing' ] );
118 \add_action( 'wpseo_indexable_index_batch', [ $this, 'index' ] );
119 // phpcs:ignore WordPress.WP.CronInterval -- The sniff doesn't understand values with parentheses. https://github.com/WordPress/WordPress-Coding-Standards/issues/2025
120 \add_filter( 'cron_schedules', [ $this, 'add_cron_schedule' ] );
121 \add_action( 'admin_init', [ $this, 'schedule_cron_indexing' ], 11 );
122
123 $this->add_limit_filters();
124 }
125
126 /**
127 * Adds the filters that change the indexing limits.
128 *
129 * @return void
130 */
131 public function add_limit_filters() {
132 \add_filter( 'wpseo_post_indexation_limit', [ $this, 'throttle_cron_indexing' ] );
133 \add_filter( 'wpseo_post_type_archive_indexation_limit', [ $this, 'throttle_cron_indexing' ] );
134 \add_filter( 'wpseo_term_indexation_limit', [ $this, 'throttle_cron_indexing' ] );
135 \add_filter( 'wpseo_prominent_words_indexation_limit', [ $this, 'throttle_cron_indexing' ] );
136 \add_filter( 'wpseo_link_indexing_limit', [ $this, 'throttle_cron_link_indexing' ] );
137 }
138
139 /**
140 * Enqueues the required scripts.
141 *
142 * @return void
143 */
144 public function register_shutdown_indexing() {
145 if ( $this->should_index_on_shutdown( $this->get_shutdown_limit() ) ) {
146 $this->register_shutdown_function( 'index' );
147 }
148 }
149
150 /**
151 * Run a single indexing pass of each indexing action. Intended for use as a shutdown function.
152 *
153 * @return void
154 */
155 public function index() {
156 if ( \wp_doing_cron() && ! $this->should_index_on_cron() ) {
157 $this->unschedule_cron_indexing();
158
159 return;
160 }
161
162 foreach ( $this->indexing_actions as $indexation_action ) {
163 $indexation_action->index();
164 }
165
166 if ( $this->indexing_helper->get_limited_filtered_unindexed_count_background( 1 ) === 0 ) {
167 // We set this as complete, even though prominent words might not be complete. But that's the way we always treated that.
168 $this->complete_indexation_action->complete();
169 }
170 }
171
172 /**
173 * Adds the 'Every fifteen minutes' cron schedule to WP-Cron.
174 *
175 * @param array $schedules The existing schedules.
176 *
177 * @return array The schedules containing the fifteen_minutes schedule.
178 */
179 public function add_cron_schedule( $schedules ) {
180 if ( ! \is_array( $schedules ) ) {
181 return $schedules;
182 }
183
184 $schedules['fifteen_minutes'] = [
185 'interval' => ( 15 * \MINUTE_IN_SECONDS ),
186 'display' => \esc_html__( 'Every fifteen minutes', 'wordpress-seo' ),
187 ];
188
189 return $schedules;
190 }
191
192 /**
193 * Schedule background indexing every 15 minutes if the index isn't already up to date.
194 *
195 * @return void
196 */
197 public function schedule_cron_indexing() {
198 /**
199 * Filter: 'wpseo_unindexed_count_queries_ran' - Informs whether the expensive unindexed count queries have been ran already.
200 *
201 * @internal
202 *
203 * @param bool $have_queries_ran
204 */
205 $have_queries_ran = \apply_filters( 'wpseo_unindexed_count_queries_ran', false );
206
207 if ( ( ! $this->yoast_admin_and_dashboard_conditional->is_met() || ! $this->get_request_conditional->is_met() ) && ! $have_queries_ran ) {
208 return;
209 }
210
211 if ( ! \wp_next_scheduled( 'wpseo_indexable_index_batch' ) && $this->should_index_on_cron() ) {
212 \wp_schedule_event( ( \time() + \HOUR_IN_SECONDS ), 'fifteen_minutes', 'wpseo_indexable_index_batch' );
213 }
214 }
215
216 /**
217 * Limit cron indexing to 15 indexables per batch instead of 25.
218 *
219 * @param int $indexation_limit The current limit (filter input).
220 *
221 * @return int The new batch limit.
222 */
223 public function throttle_cron_indexing( $indexation_limit ) {
224 if ( \wp_doing_cron() ) {
225 /**
226 * Filter: 'wpseo_cron_indexing_limit_size' - Adds the possibility to limit the number of items that are indexed when in cron action.
227 *
228 * @param int $limit Maximum number of indexables to be indexed per indexing action.
229 */
230 return \apply_filters( 'wpseo_cron_indexing_limit_size', 15 );
231 }
232
233 return $indexation_limit;
234 }
235
236 /**
237 * Limit cron indexing to 3 links per batch instead of 5.
238 *
239 * @param int $link_indexation_limit The current limit (filter input).
240 *
241 * @return int The new batch limit.
242 */
243 public function throttle_cron_link_indexing( $link_indexation_limit ) {
244 if ( \wp_doing_cron() ) {
245 /**
246 * Filter: 'wpseo_cron_link_indexing_limit_size' - Adds the possibility to limit the number of links that are indexed when in cron action.
247 *
248 * @param int $limit Maximum number of link indexables to be indexed per link indexing action.
249 */
250 return \apply_filters( 'wpseo_cron_link_indexing_limit_size', 3 );
251 }
252
253 return $link_indexation_limit;
254 }
255
256 /**
257 * Determine whether cron indexation should be performed.
258 *
259 * @return bool Should cron indexation be performed.
260 */
261 protected function should_index_on_cron() {
262 if ( ! $this->indexable_helper->should_index_indexables() ) {
263 return false;
264 }
265
266 // The filter supersedes everything when preventing cron indexation.
267 if ( \apply_filters( 'Yoast\WP\SEO\enable_cron_indexing', true ) !== true ) {
268 return false;
269 }
270
271 return $this->indexing_helper->get_limited_filtered_unindexed_count_background( 1 ) > 0;
272 }
273
274 /**
275 * Determine whether background indexation should be performed.
276 *
277 * @param int $shutdown_limit The shutdown limit used to determine whether indexation should be run.
278 *
279 * @return bool Should background indexation be performed.
280 */
281 protected function should_index_on_shutdown( $shutdown_limit ) {
282 if ( ! $this->yoast_admin_and_dashboard_conditional->is_met() || ! $this->get_request_conditional->is_met() ) {
283 return false;
284 }
285
286 if ( ! $this->indexable_helper->should_index_indexables() ) {
287 return false;
288 }
289
290 if ( $this->wp_cron_enabled_conditional->is_met() ) {
291 return false;
292 }
293
294 $total_unindexed = $this->indexing_helper->get_limited_filtered_unindexed_count_background( $shutdown_limit );
295 if ( $total_unindexed === 0 || $total_unindexed > $shutdown_limit ) {
296 return false;
297 }
298
299 return true;
300 }
301
302 /**
303 * Retrieves the shutdown limit. This limit is the amount of indexables that is generated in the background.
304 *
305 * @return int The shutdown limit.
306 */
307 protected function get_shutdown_limit() {
308 /**
309 * Filter 'wpseo_shutdown_indexation_limit' - Allow filtering the number of objects that can be indexed during shutdown.
310 *
311 * @param int $limit The maximum number of objects indexed.
312 */
313 return \apply_filters( 'wpseo_shutdown_indexation_limit', 25 );
314 }
315
316 /**
317 * Removes the cron indexing job from the scheduled event queue.
318 *
319 * @return void
320 */
321 protected function unschedule_cron_indexing() {
322 $scheduled = \wp_next_scheduled( 'wpseo_indexable_index_batch' );
323 if ( $scheduled ) {
324 \wp_unschedule_event( $scheduled, 'wpseo_indexable_index_batch' );
325 }
326 }
327
328 /**
329 * Registers a method to be executed on shutdown.
330 * This wrapper mostly exists for making this class more unittestable.
331 *
332 * @param string $method_name The name of the method on the current instance to register.
333 *
334 * @return void
335 */
336 protected function register_shutdown_function( $method_name ) {
337 \register_shutdown_function( [ $this, $method_name ] );
338 }
339 }
340