PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 19.0
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v19.0
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 / presentations / indexable-presentation.php

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

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