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 / presentations / indexable-presentation.php

indexable-presentation.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.3, at src/presentations/indexable-presentation.php

768 lines 19.1 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\Presentations;
4
5 use Yoast\WP\SEO\Context\Meta_Tags_Context;
6 use Yoast\WP\SEO\Generators\Breadcrumbs_Generator;
7 use Yoast\WP\SEO\Generators\Open_Graph_Image_Generator;
8 use Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator;
9 use Yoast\WP\SEO\Generators\Schema_Generator;
10 use Yoast\WP\SEO\Generators\Twitter_Image_Generator;
11 use Yoast\WP\SEO\Helpers\Current_Page_Helper;
12 use Yoast\WP\SEO\Helpers\Image_Helper;
13 use Yoast\WP\SEO\Helpers\Indexable_Helper;
14 use Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper;
15 use Yoast\WP\SEO\Helpers\Options_Helper;
16 use Yoast\WP\SEO\Helpers\Permalink_Helper;
17 use Yoast\WP\SEO\Helpers\Url_Helper;
18 use Yoast\WP\SEO\Helpers\User_Helper;
19 use Yoast\WP\SEO\Models\Indexable;
20
21 /**
22 * Class Indexable_Presentation.
23 *
24 * Presentation object for indexables.
25 *
26 * @property string $title
27 * @property string $meta_description
28 * @property array $robots
29 * @property string $canonical
30 * @property string $rel_next
31 * @property string $rel_prev
32 * @property string $open_graph_type
33 * @property string $open_graph_title
34 * @property string $open_graph_description
35 * @property array $open_graph_images
36 * @property int $open_graph_image_id
37 * @property string $open_graph_image
38 * @property string $open_graph_url
39 * @property string $open_graph_site_name
40 * @property string $open_graph_article_publisher
41 * @property string $open_graph_article_author
42 * @property string $open_graph_article_published_time
43 * @property string $open_graph_article_modified_time
44 * @property string $open_graph_locale
45 * @property string $open_graph_fb_app_id
46 * @property string $permalink
47 * @property array $schema
48 * @property string $twitter_card
49 * @property string $twitter_title
50 * @property string $twitter_description
51 * @property string $twitter_image
52 * @property string $twitter_creator
53 * @property string $twitter_site
54 * @property object|array $source
55 * @property array $breadcrumbs
56 * @property int $estimated_reading_time_minutes
57 * @property array $googlebot
58 * @property array $bingbot
59 */
60 class Indexable_Presentation extends Abstract_Presentation {
61
62 /**
63 * The indexable.
64 *
65 * @var Indexable
66 */
67 public $model;
68
69 /**
70 * The meta tags context.
71 *
72 * @var Meta_Tags_Context
73 */
74 public $context;
75
76 /**
77 * The Schema generator.
78 *
79 * @var Schema_Generator
80 */
81 protected $schema_generator;
82
83 /**
84 * The Open Graph image generator.
85 *
86 * @var Open_Graph_Image_Generator
87 */
88 protected $open_graph_image_generator;
89
90 /**
91 * The Twitter image generator.
92 *
93 * @var Twitter_Image_Generator
94 */
95 protected $twitter_image_generator;
96
97 /**
98 * The Open Graph locale generator.
99 *
100 * @var Open_Graph_Locale_Generator
101 */
102 private $open_graph_locale_generator;
103
104 /**
105 * The breadcrumbs generator.
106 *
107 * @var Breadcrumbs_Generator
108 */
109 private $breadcrumbs_generator;
110
111 /**
112 * The current page helper.
113 *
114 * @var Current_Page_Helper
115 */
116 protected $current_page;
117
118 /**
119 * The image helper.
120 *
121 * @var Image_Helper
122 */
123 protected $image;
124
125 /**
126 * The options helper.
127 *
128 * @var Options_Helper
129 */
130 protected $options;
131
132 /**
133 * The URL helper.
134 *
135 * @var Url_Helper
136 */
137 protected $url;
138
139 /**
140 * The user helper.
141 *
142 * @var User_Helper
143 */
144 protected $user;
145
146 /**
147 * The indexable helper.
148 *
149 * @var Indexable_Helper
150 */
151 protected $indexable_helper;
152
153 /**
154 * The permalink helper.
155 *
156 * @var Permalink_Helper
157 */
158 protected $permalink_helper;
159
160 /**
161 * The values helper.
162 *
163 * @var Values_Helper
164 */
165 protected $values_helper;
166
167 /**
168 * Sets the generator dependencies.
169 *
170 * @required
171 *
172 * @param Schema_Generator $schema_generator The schema generator.
173 * @param Open_Graph_Locale_Generator $open_graph_locale_generator The Open Graph locale generator.
174 * @param Open_Graph_Image_Generator $open_graph_image_generator The Open Graph image generator.
175 * @param Twitter_Image_Generator $twitter_image_generator The Twitter image generator.
176 * @param Breadcrumbs_Generator $breadcrumbs_generator The breadcrumbs generator.
177 *
178 * @return void
179 */
180 public function set_generators(
181 Schema_Generator $schema_generator,
182 Open_Graph_Locale_Generator $open_graph_locale_generator,
183 Open_Graph_Image_Generator $open_graph_image_generator,
184 Twitter_Image_Generator $twitter_image_generator,
185 Breadcrumbs_Generator $breadcrumbs_generator
186 ) {
187 $this->schema_generator = $schema_generator;
188 $this->open_graph_locale_generator = $open_graph_locale_generator;
189 $this->open_graph_image_generator = $open_graph_image_generator;
190 $this->twitter_image_generator = $twitter_image_generator;
191 $this->breadcrumbs_generator = $breadcrumbs_generator;
192 }
193
194 /**
195 * Used by dependency injection container to inject the helpers.
196 *
197 * @required
198 *
199 * @param Image_Helper $image The image helper.
200 * @param Options_Helper $options The options helper.
201 * @param Current_Page_Helper $current_page The current page helper.
202 * @param Url_Helper $url The URL helper.
203 * @param User_Helper $user The user helper.
204 * @param Indexable_Helper $indexable The indexable helper.
205 * @param Permalink_Helper $permalink The permalink helper.
206 * @param Values_Helper $values The values helper.
207 *
208 * @return void
209 */
210 public function set_helpers(
211 Image_Helper $image,
212 Options_Helper $options,
213 Current_Page_Helper $current_page,
214 Url_Helper $url,
215 User_Helper $user,
216 Indexable_Helper $indexable,
217 Permalink_Helper $permalink,
218 Values_Helper $values
219 ) {
220 $this->image = $image;
221 $this->options = $options;
222 $this->current_page = $current_page;
223 $this->url = $url;
224 $this->user = $user;
225 $this->indexable_helper = $indexable;
226 $this->permalink_helper = $permalink;
227 $this->values_helper = $values;
228 }
229
230 /**
231 * Gets the permalink from the indexable or generates it if dynamic permalinks are enabled.
232 *
233 * @return string The permalink.
234 */
235 public function generate_permalink() {
236 if ( $this->indexable_helper->dynamic_permalinks_enabled() ) {
237 return $this->permalink_helper->get_permalink_for_indexable( $this->model );
238 }
239
240 if ( \is_date() ) {
241 return $this->current_page->get_date_archive_permalink();
242 }
243
244 if ( \is_attachment() ) {
245 global $wp;
246 return \trailingslashit( \home_url( $wp->request ) );
247 }
248
249 return $this->model->permalink;
250 }
251
252 /**
253 * Generates the title.
254 *
255 * @return string The title.
256 */
257 public function generate_title() {
258 if ( $this->model->title ) {
259 return $this->model->title;
260 }
261
262 return '';
263 }
264
265 /**
266 * Generates the meta description.
267 *
268 * @return string The meta description.
269 */
270 public function generate_meta_description() {
271 if ( $this->model->description ) {
272 return $this->model->description;
273 }
274
275 return '';
276 }
277
278 /**
279 * Generates the robots value.
280 *
281 * @return array The robots value.
282 */
283 public function generate_robots() {
284 $robots = $this->get_base_robots();
285
286 return $this->filter_robots( $robots );
287 }
288
289 /**
290 * Gets the base robots value.
291 *
292 * @return array The base robots value.
293 */
294 protected function get_base_robots() {
295 return [
296 'index' => ( $this->model->is_robots_noindex === true ) ? 'noindex' : 'index',
297 'follow' => ( $this->model->is_robots_nofollow === true ) ? 'nofollow' : 'follow',
298 'max-snippet' => 'max-snippet:-1',
299 'max-image-preview' => 'max-image-preview:large',
300 'max-video-preview' => 'max-video-preview:-1',
301 ];
302 }
303
304 /**
305 * Run the robots output content through the `wpseo_robots` filter.
306 *
307 * @param array $robots The meta robots values to filter.
308 *
309 * @return array The filtered meta robots values.
310 */
311 protected function filter_robots( $robots ) {
312 // Remove values that are only listened to when indexing.
313 if ( $robots['index'] === 'noindex' ) {
314 $robots['imageindex'] = null;
315 $robots['archive'] = null;
316 $robots['snippet'] = null;
317 $robots['max-snippet'] = null;
318 $robots['max-image-preview'] = null;
319 $robots['max-video-preview'] = null;
320 }
321
322 $robots_string = \implode( ', ', \array_filter( $robots ) );
323
324 /**
325 * Filter: 'wpseo_robots' - Allows filtering of the meta robots output of Yoast SEO.
326 *
327 * @param string $robots The meta robots directives to be echoed.
328 * @param Indexable_Presentation $presentation The presentation of an indexable.
329 */
330 $robots_filtered = \apply_filters( 'wpseo_robots', $robots_string, $this );
331
332 // Convert the robots string back to an array.
333 if ( \is_string( $robots_filtered ) ) {
334 $robots_values = \explode( ', ', $robots_filtered );
335 $robots_new = [];
336
337 foreach ( $robots_values as $value ) {
338 $key = $value;
339
340 // Change `noindex` to `index.
341 if ( \strpos( $key, 'no' ) === 0 ) {
342 $key = \substr( $value, 2 );
343 }
344 // Change `max-snippet:-1` to `max-snippet`.
345 $colon_position = \strpos( $key, ':' );
346 if ( $colon_position !== false ) {
347 $key = \substr( $value, 0, $colon_position );
348 }
349
350 $robots_new[ $key ] = $value;
351 }
352
353 $robots = $robots_new;
354 }
355
356 if ( \is_bool( $robots_filtered ) && ( $robots_filtered === false ) ) {
357 return [
358 'index' => 'noindex',
359 'follow' => 'nofollow',
360 ];
361 }
362
363 if ( ! $robots_filtered ) {
364 return [];
365 }
366
367 /**
368 * Filter: 'wpseo_robots_array' - Allows filtering of the meta robots output array of Yoast SEO.
369 *
370 * @param array $robots The meta robots directives to be used.
371 * @param Indexable_Presentation $presentation The presentation of an indexable.
372 */
373 return \apply_filters( 'wpseo_robots_array', \array_filter( $robots ), $this );
374 }
375
376 /**
377 * Generates the canonical.
378 *
379 * @return string The canonical.
380 */
381 public function generate_canonical() {
382 if ( $this->model->canonical ) {
383 return $this->model->canonical;
384 }
385
386 if ( $this->permalink ) {
387 return $this->permalink;
388 }
389
390 return '';
391 }
392
393 /**
394 * Generates the rel prev.
395 *
396 * @return string The rel prev value.
397 */
398 public function generate_rel_prev() {
399 return '';
400 }
401
402 /**
403 * Generates the rel next.
404 *
405 * @return string The rel prev next.
406 */
407 public function generate_rel_next() {
408 return '';
409 }
410
411 /**
412 * Generates the Open Graph type.
413 *
414 * @return string The Open Graph type.
415 */
416 public function generate_open_graph_type() {
417 return 'website';
418 }
419
420 /**
421 * Generates the open graph title.
422 *
423 * @return string The open graph title.
424 */
425 public function generate_open_graph_title() {
426 if ( $this->model->open_graph_title ) {
427 $open_graph_title = $this->model->open_graph_title;
428 }
429
430 if ( empty( $open_graph_title ) ) {
431 // The helper applies a filter, but we don't have a default value at this stage so we pass an empty string.
432 $open_graph_title = $this->values_helper->get_open_graph_title( '', $this->model->object_type, $this->model->object_sub_type );
433 }
434
435 if ( empty( $open_graph_title ) ) {
436 $open_graph_title = $this->title;
437 }
438
439 return $open_graph_title;
440 }
441
442 /**
443 * Generates the open graph description.
444 *
445 * @return string The open graph description.
446 */
447 public function generate_open_graph_description() {
448 if ( $this->model->open_graph_description ) {
449 $open_graph_description = $this->model->open_graph_description;
450 }
451
452 if ( empty( $open_graph_description ) ) {
453 // The helper applies a filter, but we don't have a default value at this stage so we pass an empty string.
454 $open_graph_description = $this->values_helper->get_open_graph_description( '', $this->model->object_type, $this->model->object_sub_type );
455 }
456
457 if ( empty( $open_graph_description ) ) {
458 $open_graph_description = $this->meta_description;
459 }
460
461 return $open_graph_description;
462 }
463
464 /**
465 * Generates the open graph images.
466 *
467 * @return array The open graph images.
468 */
469 public function generate_open_graph_images() {
470 if ( $this->context->open_graph_enabled === false ) {
471 return [];
472 }
473
474 return $this->open_graph_image_generator->generate( $this->context );
475 }
476
477 /**
478 * Generates the open graph image ID.
479 *
480 * @return string The open graph image ID.
481 */
482 public function generate_open_graph_image_id() {
483 if ( $this->model->open_graph_image_id ) {
484 return $this->model->open_graph_image_id;
485 }
486
487 return $this->values_helper->get_open_graph_image_id( 0, $this->model->object_type, $this->model->object_sub_type );
488 }
489
490 /**
491 * Generates the open graph image URL.
492 *
493 * @return string The open graph image URL.
494 */
495 public function generate_open_graph_image() {
496 if ( $this->model->open_graph_image ) {
497 return $this->model->open_graph_image;
498 }
499
500 return $this->values_helper->get_open_graph_image( '', $this->model->object_type, $this->model->object_sub_type );
501 }
502
503 /**
504 * Generates the open graph url.
505 *
506 * @return string The open graph url.
507 */
508 public function generate_open_graph_url() {
509 if ( $this->model->canonical ) {
510 return $this->model->canonical;
511 }
512
513 return $this->permalink;
514 }
515
516 /**
517 * Generates the open graph article publisher.
518 *
519 * @return string The open graph article publisher.
520 */
521 public function generate_open_graph_article_publisher() {
522 return '';
523 }
524
525 /**
526 * Generates the open graph article author.
527 *
528 * @return string The open graph article author.
529 */
530 public function generate_open_graph_article_author() {
531 return '';
532 }
533
534 /**
535 * Generates the open graph article published time.
536 *
537 * @return string The open graph article published time.
538 */
539 public function generate_open_graph_article_published_time() {
540 return '';
541 }
542
543 /**
544 * Generates the open graph article modified time.
545 *
546 * @return string The open graph article modified time.
547 */
548 public function generate_open_graph_article_modified_time() {
549 return '';
550 }
551
552 /**
553 * Generates the open graph locale.
554 *
555 * @return string The open graph locale.
556 */
557 public function generate_open_graph_locale() {
558 return $this->open_graph_locale_generator->generate( $this->context );
559 }
560
561 /**
562 * Generates the open graph site name.
563 *
564 * @return string The open graph site name.
565 */
566 public function generate_open_graph_site_name() {
567 return $this->context->wordpress_site_name;
568 }
569
570 /**
571 * Generates the Twitter card type.
572 *
573 * @return string The Twitter card type.
574 */
575 public function generate_twitter_card() {
576 return $this->context->twitter_card;
577 }
578
579 /**
580 * Generates the Twitter title.
581 *
582 * @return string The Twitter title.
583 */
584 public function generate_twitter_title() {
585 if ( $this->model->twitter_title ) {
586 return $this->model->twitter_title;
587 }
588
589 if ( $this->context->open_graph_enabled === true ) {
590 $social_template_title = $this->values_helper->get_open_graph_title( '', $this->model->object_type, $this->model->object_sub_type );
591 $open_graph_title = $this->open_graph_title;
592
593 // If the helper returns a value and it's different from the OG value in the indexable,
594 // output it in a twitter: tag.
595 if ( ! empty( $social_template_title ) && $social_template_title !== $open_graph_title ) {
596 return $social_template_title;
597 }
598
599 // If the OG title is set, let og: tag take care of this.
600 if ( ! empty( $open_graph_title ) ) {
601 return '';
602 }
603 }
604
605 if ( $this->title ) {
606 return $this->title;
607 }
608
609 return '';
610 }
611
612 /**
613 * Generates the Twitter description.
614 *
615 * @return string The Twitter description.
616 */
617 public function generate_twitter_description() {
618 if ( $this->model->twitter_description ) {
619 return $this->model->twitter_description;
620 }
621
622 if ( $this->context->open_graph_enabled === true ) {
623 $social_template_description = $this->values_helper->get_open_graph_description( '', $this->model->object_type, $this->model->object_sub_type );
624 $open_graph_description = $this->open_graph_description;
625
626 // If the helper returns a value and it's different from the OG value in the indexable,
627 // output it in a twitter: tag.
628 if ( ! empty( $social_template_description ) && $social_template_description !== $open_graph_description ) {
629 return $social_template_description;
630 }
631
632 // If the OG description is set, let og: tag take care of this.
633 if ( ! empty( $open_graph_description ) ) {
634 return '';
635 }
636 }
637
638 if ( $this->meta_description ) {
639 return $this->meta_description;
640 }
641
642 return '';
643 }
644
645 /**
646 * Generates the Twitter image.
647 *
648 * @return string The Twitter image.
649 */
650 public function generate_twitter_image() {
651 $images = $this->twitter_image_generator->generate( $this->context );
652 $image = \reset( $images );
653
654 // Use a user-defined Twitter image, if present.
655 if ( $image && $this->context->indexable->twitter_image_source === 'set-by-user' ) {
656 return $image['url'];
657 }
658
659 // Let the Open Graph tags, if enabled, handle the rest of the fallback hierarchy.
660 if ( $this->context->open_graph_enabled === true && $this->open_graph_images ) {
661 return '';
662 }
663
664 // Set a Twitter tag with the featured image, or a prominent image from the content, if present.
665 if ( $image ) {
666 return $image['url'];
667 }
668
669 return '';
670 }
671
672 /**
673 * Generates the Twitter creator.
674 *
675 * @return string The Twitter creator.
676 */
677 public function generate_twitter_creator() {
678 return '';
679 }
680
681 /**
682 * Generates the Twitter site.
683 *
684 * @return string The Twitter site.
685 */
686 public function generate_twitter_site() {
687 switch ( $this->context->site_represents ) {
688 case 'person':
689 $twitter = $this->user->get_the_author_meta( 'twitter', (int) $this->context->site_user_id );
690 if ( empty( $twitter ) ) {
691 $twitter = $this->options->get( 'twitter_site' );
692 }
693 break;
694 case 'company':
695 default:
696 $twitter = $this->options->get( 'twitter_site' );
697 break;
698 }
699
700 return $twitter;
701 }
702
703 /**
704 * Generates the source.
705 *
706 * @return array The source.
707 */
708 public function generate_source() {
709 return [];
710 }
711
712 /**
713 * Generates the schema for the page.
714 *
715 * @codeCoverageIgnore Wrapper method.
716 *
717 * @return array The Schema object.
718 */
719 public function generate_schema() {
720 return $this->schema_generator->generate( $this->context );
721 }
722
723 /**
724 * Generates the breadcrumbs for the page.
725 *
726 * @codeCoverageIgnore Wrapper method.
727 *
728 * @return array The breadcrumbs.
729 */
730 public function generate_breadcrumbs() {
731 return $this->breadcrumbs_generator->generate( $this->context );
732 }
733
734 /**
735 * Generates the estimated reading time.
736 *
737 * @codeCoverageIgnore Wrapper method.
738 *
739 * @return int|null The estimated reading time.
740 */
741 public function generate_estimated_reading_time_minutes() {
742 if ( $this->model->estimated_reading_time_minutes !== null ) {
743 return $this->model->estimated_reading_time_minutes;
744 }
745
746 if ( $this->context->post === null ) {
747 return null;
748 }
749
750 // 200 is the approximate estimated words per minute across languages.
751 $words_per_minute = 200;
752 $words = \str_word_count( \wp_strip_all_tags( $this->context->post->post_content ) );
753 return (int) \round( $words / $words_per_minute );
754 }
755
756 /**
757 * Strips all nested dependencies from the debug info.
758 *
759 * @return array
760 */
761 public function __debugInfo() {
762 return [
763 'model' => $this->model,
764 'context' => $this->context,
765 ];
766 }
767 }
768