PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.3
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.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 / repositories / indexable-repository.php

indexable-repository.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.3, at src/repositories/indexable-repository.php

539 lines 15.0 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\Repositories;
4
5 use Psr\Log\LoggerInterface;
6 use wpdb;
7 use Yoast\WP\Lib\Model;
8 use Yoast\WP\Lib\ORM;
9 use Yoast\WP\SEO\Builders\Indexable_Builder;
10 use Yoast\WP\SEO\Helpers\Current_Page_Helper;
11 use Yoast\WP\SEO\Helpers\Indexable_Helper;
12 use Yoast\WP\SEO\Loggers\Logger;
13 use Yoast\WP\SEO\Models\Indexable;
14 use Yoast\WP\SEO\Services\Indexables\Indexable_Version_Manager;
15
16 /**
17 * Class Indexable_Repository.
18 */
19 class Indexable_Repository {
20
21 /**
22 * The indexable builder.
23 *
24 * @var Indexable_Builder
25 */
26 private $builder;
27
28 /**
29 * Represents the hierarchy repository.
30 *
31 * @var Indexable_Hierarchy_Repository
32 */
33 protected $hierarchy_repository;
34
35 /**
36 * The current page helper.
37 *
38 * @var Current_Page_Helper
39 */
40 protected $current_page;
41
42 /**
43 * The logger object.
44 *
45 * @var LoggerInterface
46 */
47 protected $logger;
48
49 /**
50 * The WordPress database.
51 *
52 * @var wpdb
53 */
54 protected $wpdb;
55
56 /**
57 * Represents the indexable helper.
58 *
59 * @var Indexable_Helper
60 */
61 protected $indexable_helper;
62
63 /**
64 * Checks if Indexables are up to date.
65 *
66 * @var Indexable_Version_Manager
67 */
68 protected $version_manager;
69
70 /**
71 * Returns the instance of this class constructed through the ORM Wrapper.
72 *
73 * @param Indexable_Builder $builder The indexable builder.
74 * @param Current_Page_Helper $current_page The current post helper.
75 * @param Logger $logger The logger.
76 * @param Indexable_Hierarchy_Repository $hierarchy_repository The hierarchy repository.
77 * @param wpdb $wpdb The WordPress database instance.
78 * @param Indexable_Version_Manager $version_manager The indexable version manager.
79 */
80 public function __construct(
81 Indexable_Builder $builder,
82 Current_Page_Helper $current_page,
83 Logger $logger,
84 Indexable_Hierarchy_Repository $hierarchy_repository,
85 wpdb $wpdb,
86 Indexable_Version_Manager $version_manager
87 ) {
88 $this->builder = $builder;
89 $this->current_page = $current_page;
90 $this->logger = $logger;
91 $this->hierarchy_repository = $hierarchy_repository;
92 $this->wpdb = $wpdb;
93 $this->version_manager = $version_manager;
94 }
95
96 /**
97 * Starts a query for this repository.
98 *
99 * @return ORM
100 */
101 public function query() {
102 return Model::of_type( 'Indexable' );
103 }
104
105 /**
106 * Attempts to find the indexable for the current WordPress page. Returns false if no indexable could be found.
107 * This may be the result of the indexable not existing or of being unable to determine what type of page the
108 * current page is.
109 *
110 * @return bool|Indexable The indexable, false if none could be found.
111 */
112 public function for_current_page() {
113 $indexable = false;
114
115 switch ( true ) {
116 case $this->current_page->is_simple_page():
117 $indexable = $this->find_by_id_and_type( $this->current_page->get_simple_page_id(), 'post' );
118 break;
119 case $this->current_page->is_home_static_page():
120 $indexable = $this->find_by_id_and_type( $this->current_page->get_front_page_id(), 'post' );
121 break;
122 case $this->current_page->is_home_posts_page():
123 $indexable = $this->find_for_home_page();
124 break;
125 case $this->current_page->is_term_archive():
126 $indexable = $this->find_by_id_and_type( $this->current_page->get_term_id(), 'term' );
127 break;
128 case $this->current_page->is_date_archive():
129 $indexable = $this->find_for_date_archive();
130 break;
131 case $this->current_page->is_search_result():
132 $indexable = $this->find_for_system_page( 'search-result' );
133 break;
134 case $this->current_page->is_post_type_archive():
135 $indexable = $this->find_for_post_type_archive( $this->current_page->get_queried_post_type() );
136 break;
137 case $this->current_page->is_author_archive():
138 $indexable = $this->find_by_id_and_type( $this->current_page->get_author_id(), 'user' );
139 break;
140 case $this->current_page->is_404():
141 $indexable = $this->find_for_system_page( '404' );
142 break;
143 }
144
145 if ( $indexable === false ) {
146 return $this->query()->create(
147 [
148 'object_type' => 'unknown',
149 'post_status' => 'unindexed',
150 'version' => 1,
151 ]
152 );
153 }
154
155 return $indexable;
156 }
157
158 /**
159 * Retrieves an indexable by its permalink.
160 *
161 * @param string $permalink The indexable permalink.
162 *
163 * @return bool|Indexable The indexable, false if none could be found.
164 */
165 public function find_by_permalink( $permalink ) {
166 $permalink_hash = \strlen( $permalink ) . ':' . \md5( $permalink );
167
168 // Find by both permalink_hash and permalink, permalink_hash is indexed so will be used first by the DB to optimize the query.
169 return $this->query()
170 ->where( 'permalink_hash', $permalink_hash )
171 ->where( 'permalink', $permalink )
172 ->find_one();
173 }
174
175 /**
176 * Retrieves all the indexable instances of a certain object type.
177 *
178 * @param string $object_type The object type.
179 *
180 * @return Indexable[] The array with all the indexable instances of a certain object type.
181 */
182 public function find_all_with_type( $object_type ) {
183 /**
184 * The array with all the indexable instances of a certain object type.
185 *
186 * @var Indexable[] $indexables
187 */
188 $indexables = $this
189 ->query()
190 ->where( 'object_type', $object_type )
191 ->find_many();
192
193 return \array_map( [ $this, 'upgrade_indexable' ], $indexables );
194 }
195
196 /**
197 * Retrieves all the indexable instances of a certain object subtype.
198 *
199 * @param string $object_type The object type.
200 * @param string $object_sub_type The object subtype.
201 *
202 * @return Indexable[] The array with all the indexable instances of a certain object subtype.
203 */
204 public function find_all_with_type_and_sub_type( $object_type, $object_sub_type ) {
205 /**
206 * The array with all the indexable instances of a certain object type and subtype.
207 *
208 * @var Indexable[] $indexables
209 */
210 $indexables = $this
211 ->query()
212 ->where( 'object_type', $object_type )
213 ->where( 'object_sub_type', $object_sub_type )
214 ->find_many();
215
216 return \array_map( [ $this, 'upgrade_indexable' ], $indexables );
217 }
218
219 /**
220 * Retrieves the homepage indexable.
221 *
222 * @param bool $auto_create Optional. Create the indexable if it does not exist.
223 *
224 * @return bool|Indexable Instance of indexable.
225 */
226 public function find_for_home_page( $auto_create = true ) {
227 $indexable = \wp_cache_get( 'home-page', 'yoast-seo-indexables' );
228 if ( ! $indexable ) {
229 /**
230 * Indexable instance.
231 *
232 * @var Indexable $indexable
233 */
234 $indexable = $this->query()->where( 'object_type', 'home-page' )->find_one();
235
236 if ( $auto_create && ! $indexable ) {
237 $indexable = $this->builder->build_for_home_page();
238 }
239
240 $indexable = $this->upgrade_indexable( $indexable );
241
242 \wp_cache_set( 'home-page', $indexable, 'yoast-seo-indexables', ( 5 * \MINUTE_IN_SECONDS ) );
243 }
244
245 return $indexable;
246 }
247
248 /**
249 * Retrieves the date archive indexable.
250 *
251 * @param bool $auto_create Optional. Create the indexable if it does not exist.
252 *
253 * @return bool|Indexable Instance of indexable.
254 */
255 public function find_for_date_archive( $auto_create = true ) {
256 /**
257 * Indexable instance.
258 *
259 * @var Indexable $indexable
260 */
261 $indexable = $this->query()->where( 'object_type', 'date-archive' )->find_one();
262
263 if ( $auto_create && ! $indexable ) {
264 $indexable = $this->builder->build_for_date_archive();
265 }
266
267 return $this->upgrade_indexable( $indexable );
268 }
269
270 /**
271 * Retrieves an indexable for a post type archive.
272 *
273 * @param string $post_type The post type.
274 * @param bool $auto_create Optional. Create the indexable if it does not exist.
275 *
276 * @return bool|Indexable The indexable, false if none could be found.
277 */
278 public function find_for_post_type_archive( $post_type, $auto_create = true ) {
279 /**
280 * Indexable instance.
281 *
282 * @var Indexable $indexable
283 */
284 $indexable = $this->query()
285 ->where( 'object_type', 'post-type-archive' )
286 ->where( 'object_sub_type', $post_type )
287 ->find_one();
288
289 if ( $auto_create && ! $indexable ) {
290 $indexable = $this->builder->build_for_post_type_archive( $post_type );
291 }
292
293 return $this->upgrade_indexable( $indexable );
294 }
295
296 /**
297 * Retrieves the indexable for a system page.
298 *
299 * @param string $object_sub_type The type of system page.
300 * @param bool $auto_create Optional. Create the indexable if it does not exist.
301 *
302 * @return bool|Indexable Instance of indexable.
303 */
304 public function find_for_system_page( $object_sub_type, $auto_create = true ) {
305 /**
306 * Indexable instance.
307 *
308 * @var Indexable $indexable
309 */
310 $indexable = $this->query()
311 ->where( 'object_type', 'system-page' )
312 ->where( 'object_sub_type', $object_sub_type )
313 ->find_one();
314
315 if ( $auto_create && ! $indexable ) {
316 $indexable = $this->builder->build_for_system_page( $object_sub_type );
317 }
318
319 return $this->upgrade_indexable( $indexable );
320 }
321
322 /**
323 * Retrieves an indexable by its ID and type.
324 *
325 * @param int $object_id The indexable object ID.
326 * @param string $object_type The indexable object type.
327 * @param bool $auto_create Optional. Create the indexable if it does not exist.
328 *
329 * @return bool|Indexable Instance of indexable.
330 */
331 public function find_by_id_and_type( $object_id, $object_type, $auto_create = true ) {
332 $indexable = $this->query()
333 ->where( 'object_id', $object_id )
334 ->where( 'object_type', $object_type )
335 ->find_one();
336
337 if ( $auto_create && ! $indexable ) {
338 $indexable = $this->builder->build_for_id_and_type( $object_id, $object_type );
339 }
340 else {
341 $indexable = $this->upgrade_indexable( $indexable );
342 }
343
344 return $indexable;
345 }
346
347 /**
348 * Retrieves multiple indexables at once by their id's and type.
349 *
350 * @param int[] $object_ids The array of indexable object id's.
351 * @param string $object_type The indexable object type.
352 * @param bool $auto_create Optional. Create the indexable if it does not exist.
353 *
354 * @return Indexable[] An array of indexables.
355 */
356 public function find_by_multiple_ids_and_type( $object_ids, $object_type, $auto_create = true ) {
357 if ( empty( $object_ids ) ) {
358 return [];
359 }
360
361 /**
362 * Represents an array of indexable objects.
363 *
364 * @var Indexable[] $indexables
365 */
366 $indexables = $this->query()
367 ->where_in( 'object_id', $object_ids )
368 ->where( 'object_type', $object_type )
369 ->find_many();
370
371 if ( $auto_create ) {
372 $indexables_available = [];
373 foreach ( $indexables as $indexable ) {
374 $indexables_available[] = $indexable->object_id;
375 }
376
377 $indexables_to_create = \array_diff( $object_ids, $indexables_available );
378
379 foreach ( $indexables_to_create as $indexable_to_create ) {
380 $indexables[] = $this->builder->build_for_id_and_type( $indexable_to_create, $object_type );
381 }
382 }
383
384 return \array_map( [ $this, 'upgrade_indexable' ], $indexables );
385 }
386
387 /**
388 * Finds the indexables by id's.
389 *
390 * @param array $indexable_ids The indexable id's.
391 *
392 * @return Indexable[] The found indexables.
393 */
394 public function find_by_ids( array $indexable_ids ) {
395 if ( empty( $indexable_ids ) ) {
396 return [];
397 }
398
399 $indexables = $this
400 ->query()
401 ->where_in( 'id', $indexable_ids )
402 ->find_many();
403
404 return \array_map( [ $this, 'upgrade_indexable' ], $indexables );
405 }
406
407 /**
408 * Returns all ancestors of a given indexable.
409 *
410 * @param Indexable $indexable The indexable to find the ancestors of.
411 *
412 * @return Indexable[] All ancestors of the given indexable.
413 */
414 public function get_ancestors( Indexable $indexable ) {
415 // If we've already set ancestors on the indexable no need to get them again.
416 if ( \is_array( $indexable->ancestors ) && ! empty( $indexable->ancestors ) ) {
417 return \array_map( [ $this, 'upgrade_indexable' ], $indexable->ancestors );
418 }
419
420 $indexable_ids = $this->hierarchy_repository->find_ancestors( $indexable );
421
422 // If we've set ancestors on the indexable because we had to build them to find them.
423 if ( \is_array( $indexable->ancestors ) && ! empty( $indexable->ancestors ) ) {
424 return \array_map( [ $this, 'upgrade_indexable' ], $indexable->ancestors );
425 }
426
427 if ( empty( $indexable_ids ) ) {
428 return [];
429 }
430
431 if ( $indexable_ids[0] === 0 && \count( $indexable_ids ) === 1 ) {
432 return [];
433 }
434
435 $indexables = $this->query()
436 ->where_in( 'id', $indexable_ids )
437 ->order_by_expr( 'FIELD(id,' . \implode( ',', $indexable_ids ) . ')' )
438 ->find_many();
439
440 return \array_map( [ $this, 'upgrade_indexable' ], $indexables );
441 }
442
443 /**
444 * Returns all subpages with a given post_parent.
445 *
446 * @param int $post_parent The post parent.
447 * @param array $exclude_ids The id's to exclude.
448 *
449 * @return Indexable[] array of indexables.
450 */
451 public function get_subpages_by_post_parent( $post_parent, $exclude_ids = [] ) {
452 $query = $this->query()
453 ->where( 'post_parent', $post_parent )
454 ->where( 'object_type', 'post' )
455 ->where( 'post_status', 'publish' );
456
457 if ( ! empty( $exclude_ids ) ) {
458 $query->where_not_in( 'object_id', $exclude_ids );
459 }
460 return $query->find_many();
461 }
462
463 /**
464 * Updates the incoming link count for an indexable without first fetching it.
465 *
466 * @param int $indexable_id The indexable id.
467 * @param int $count The incoming link count.
468 *
469 * @return bool Whether or not the update was succeful.
470 */
471 public function update_incoming_link_count( $indexable_id, $count ) {
472 return (bool) $this->query()
473 ->set( 'incoming_link_count', $count )
474 ->where( 'id', $indexable_id )
475 ->update_many();
476 }
477
478 /**
479 * Ensures that the given indexable has a permalink.
480 *
481 * Will be deprecated in 17.3 - Use upgrade_indexable instead.
482 *
483 * @codeCoverageIgnore
484 *
485 * @param Indexable $indexable The indexable.
486 *
487 * @return bool|Indexable The indexable.
488 */
489 public function ensure_permalink( $indexable ) {
490 // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- self::class is safe.
491 // @phpcs:ignore Squiz.PHP.CommentedOutCode.Found
492 // _deprecated_function( __METHOD__, 'WPSEO 17.3', self::class . '::upgrade_indexable' );
493
494 return $this->upgrade_indexable( $indexable );
495 }
496
497 /**
498 * Checks if an Indexable is outdated, and rebuilds it when necessary.
499 *
500 * @param Indexable $indexable The indexable.
501 *
502 * @return Indexable The indexable.
503 */
504 public function upgrade_indexable( $indexable ) {
505 if ( $this->version_manager->indexable_needs_upgrade( $indexable ) ) {
506 $indexable = $this->builder->build( $indexable );
507 }
508 return $indexable;
509 }
510
511 /**
512 * Resets the permalinks of the passed object type and subtype.
513 *
514 * @param string|null $type The type of the indexable. Can be null.
515 * @param string|null $subtype The subtype. Can be null.
516 *
517 * @return int|bool The number of permalinks changed if the query was succesful. False otherwise.
518 */
519 public function reset_permalink( $type = null, $subtype = null ) {
520 $query = $this->query()->set(
521 [
522 'permalink' => null,
523 'permalink_hash' => null,
524 'version' => 0,
525 ]
526 );
527
528 if ( $type !== null ) {
529 $query->where( 'object_type', $type );
530 }
531
532 if ( $type !== null && $subtype !== null ) {
533 $query->where( 'object_sub_type', $subtype );
534 }
535
536 return $query->update_many();
537 }
538 }
539