PluginProbe
WPSSO Core – Complete Schema Markup and Meta Tags / 22.7.0
WPSSO Core – Complete Schema Markup and Meta Tags v22.7.0
22.7.0 22.6.1 22.6.0 22.5.3 22.5.2 22.5.1 22.4.0 22.3.0 22.2.1 22.2.0 22.1.3 22.1.2 22.1.1 22.1.0 22.0.0 21.13.3 21.13.2 trunk 21.13.1
wpsso / lib / page.php

page.php in WPSSO Core – Complete Schema Markup and Meta Tags 22.7.0, at lib/page.php

2,460 lines 65.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * License: GPLv3
4 * License URI: https://www.gnu.org/licenses/gpl.txt
5 * Copyright 2012-2026 Jean-Sebastien Morisset (https://wpsso.com/)
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9
10 die( 'These aren\'t the droids you\'re looking for.' );
11 }
12
13 if ( ! defined( 'WPSSO_PLUGINDIR' ) ) {
14
15 die( 'Do. Or do not. There is no try.' );
16 }
17
18 if ( ! class_exists( 'WpssoPage' ) ) {
19
20 /*
21 * This class provides methods for the WebPage document.
22 *
23 * The use of "Page" in the WpssoPage classname refers to the WebPage document, not WordPress Pages.
24 *
25 * For methods related to WordPress Posts, Pages, and custom post types (which are all post objects), see the WpssoPost class.
26 */
27 class WpssoPage { // Aka WpssoWebPage.
28
29 private $p; // Wpsso class object.
30 private $charset;
31
32 public function __construct( &$plugin ) {
33
34 $this->p =& $plugin;
35
36 if ( $this->p->debug->enabled ) {
37
38 $this->p->debug->mark();
39 }
40
41 $this->charset = get_bloginfo( $show = 'charset', $filter = 'raw' );
42
43 /*
44 * Add information about the page $mod to the body tag class.
45 */
46 add_filter( 'body_class', array( $this, 'add_body_class' ), 1000, 1 );
47
48 /*
49 * Maybe add the Validators toolbar menu.
50 */
51 $add_toolbar_validate = empty( $this->p->options[ 'plugin_add_toolbar_validate' ] ) ? false : true;
52
53 $add_toolbar_validate = apply_filters( 'wpsso_add_toolbar_validate', $add_toolbar_validate );
54
55 if ( $add_toolbar_validate ) {
56
57 if ( $this->p->debug->enabled ) {
58
59 $this->p->debug->log( 'adding validators toolbar' );
60 }
61
62 add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_menu_validate' ), WPSSO_TB_VALIDATE_MENU_ORDER, 1 );
63
64 } elseif ( $this->p->debug->enabled ) {
65
66 $this->p->debug->log( 'validators toolbar is disabled' );
67 }
68
69 /*
70 * Maybe add title tag filters.
71 *
72 * Since WordPress v4.4.
73 *
74 * See wordpress/wp-includes/general-template.php.
75 */
76 if ( ! $this->p->util->is_title_tag_disabled() ) {
77
78 if ( $this->p->debug->enabled ) {
79
80 $this->p->debug->log( 'adding title tag filters' );
81 }
82
83 add_filter( 'pre_get_document_title', array( $this, 'pre_get_document_title' ), WPSSO_TITLE_TAG_PRIORITY, 1 );
84 add_filter( 'document_title_separator', array( $this, 'document_title_separator' ), WPSSO_TITLE_TAG_PRIORITY, 1 );
85 add_filter( 'document_title_parts', array( $this, 'document_title_parts' ), WPSSO_TITLE_TAG_PRIORITY, 1 );
86 add_filter( 'document_title', array( $this, 'document_title' ), WPSSO_TITLE_TAG_PRIORITY, 1 );
87 add_filter( 'get_wp_title_rss', array( $this, 'get_wp_title_rss' ), WPSSO_TITLE_TAG_PRIORITY, 1 );
88 add_filter( 'get_bloginfo_rss', array( $this, 'get_bloginfo_rss' ), WPSSO_TITLE_TAG_PRIORITY, 2 );
89
90 } elseif ( $this->p->debug->enabled ) {
91
92 $this->p->debug->log( 'title tag is disabled' );
93 }
94 }
95
96 /*
97 * Add information about the page $mod to the body tag class.
98 *
99 * $classes = An array of body class names.
100 */
101 public function add_body_class( array $classes ) {
102
103 if ( $this->p->debug->enabled ) {
104
105 $this->p->debug->log( 'required call to WpssoPage->get_mod()' );
106 }
107
108 $mod = $this->get_mod();
109
110 $classes[] = 'wpsso-' . SucomUtil::get_mod_css_id( $mod );
111
112 return (array) apply_filters( 'wpsso_body_class', $classes, $mod );
113 }
114
115 /*
116 * This method is hooked to the 'admin_bar_menu' action and receives a reference to the $wp_admin_bar variable.
117 *
118 * WpssoPost->ajax_get_validate_submenu() also calls this method directly, supplying the post ID in $post_id.
119 */
120 public function add_admin_bar_menu_validate( &$wp_admin_bar, $post_id = false ) { // Pass by reference OK.
121
122 if ( ! $user_id = get_current_user_id() ) { // Just in case.
123
124 return;
125 }
126
127 if ( $this->p->debug->enabled ) {
128
129 $this->p->debug->log( 'required call to WpssoPage->get_mod()' );
130 }
131
132 $mod = $this->get_mod( $post_id );
133
134 if ( $mod[ 'is_comment' ] ) {
135
136 $capability = 'edit_comment';
137
138 } elseif ( $mod[ 'is_post' ] ) {
139
140 $capability = 'page' === $mod[ 'post_type' ] ? 'edit_page' : 'edit_post';
141
142 } elseif ( $mod[ 'is_term' ] ) {
143
144 $tax_obj = get_taxonomy( $mod[ 'tax_slug' ] );
145
146 $capability = $tax_obj->cap->edit_terms;
147
148 } elseif ( $mod[ 'is_user' ] ) {
149
150 $capability = 'edit_user';
151
152 } else return false; // Validators are only provided for known modules (post, term, and user).
153
154 if ( ! current_user_can( $capability, $mod[ 'id' ] ) ) {
155
156 if ( $this->p->debug->enabled ) {
157
158 $this->p->debug->log( 'exiting early: cannot ' . $capability . ' for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
159 }
160
161 return false; // Stop here.
162 }
163
164 /*
165 * We do not want to validate settings pages in the back-end, so validators are only provided for known
166 * modules (post, term, and user). If we're on the front-end, validating the current webpage URL is fine.
167 */
168 $validators = $this->p->util->get_validators( $mod, $form = null );
169
170 if ( ! empty( $validators ) ) {
171
172 $parent_id = 'wpsso-validate';
173 $menu_icon = '<span class="ab-icon dashicons-code-standards"></span>';
174 $menu_title = _x( 'Validators', 'toolbar menu title', 'wpsso' );
175 $menu_items = array();
176
177 foreach ( $validators as $key => $el ) {
178
179 if ( empty( $el[ 'type' ] ) ) {
180
181 continue;
182 }
183
184 $menu_items[ $el[ 'type' ] ] = array(
185 'id' => $parent_id . '-' . $key,
186 'title' => $el[ 'type' ],
187 'parent' => $parent_id,
188 'href' => $el[ 'url' ],
189 'group' => false,
190 'meta' => array(
191 'class' => empty( $el[ 'url' ] ) ? 'disabled' : '',
192 'target' => '_blank',
193 'title' => $el[ 'title' ],
194 ),
195 );
196 }
197
198 SucomUtil::natksort( $menu_items );
199
200 $wp_admin_bar->add_node( array(
201 'id' => $parent_id,
202 'title' => $menu_icon . $menu_title,
203 'parent' => false,
204 'href' => false,
205 'group' => false,
206 'meta' => array(
207 'html' => '<style type="text/css">#wp-admin-bar-wpsso-validate .disabled { opacity:0.5; filter:alpha(opacity=50); }</style>',
208 ),
209 ) );
210
211 foreach ( $menu_items as $menu_item ) {
212
213 $wp_admin_bar->add_node( $menu_item );
214 }
215
216 return $parent_id;
217 }
218
219 return false;
220 }
221
222 /*
223 * Since WordPress v4.4.
224 *
225 * Filters the WordPress document title before it is generated. Returning a non-empty value skips the
226 * 'document_title_separator', 'document_title_parts', and 'document_title' filters.
227 *
228 * See wordpress/wp-includes/general-template.php.
229 */
230 public function pre_get_document_title( $pre_title = '' ) {
231
232 if ( $this->p->debug->enabled ) {
233
234 $this->p->debug->log( 'received pre_title value (' . gettype( $pre_title ) . ') = ' . $pre_title );
235 }
236
237 /*
238 * If $pre_title is not an empty string, then maybe force an empty string to make sure the
239 * 'document_title_separator', 'document_title_parts', and 'document_title' filters are applied.
240 */
241 if ( '' !== $pre_title ) {
242
243 if ( 'wp_title' !== $this->p->options[ 'plugin_title_tag' ] ) {
244
245 $pre_title = '';
246
247 if ( $this->p->debug->enabled ) {
248
249 $this->p->debug->log( 'returning an empty string to use document_title filters' );
250 }
251 }
252 }
253
254 return $pre_title;
255 }
256
257 /*
258 * Since WordPress v4.4.
259 *
260 * Filters the separator for the document title.
261 *
262 * See wordpress/wp-includes/general-template.php.
263 */
264 public function document_title_separator( $title_sep = '-' ) {
265
266 if ( $this->p->debug->enabled ) {
267
268 $this->p->debug->mark();
269 }
270
271 $title_sep = $this->maybe_get_title_sep(); // Returns default title separator (decoded).
272
273 return $title_sep;
274 }
275
276 /*
277 * Since WordPress v4.4.
278 *
279 * Filters the parts of the document title.
280 *
281 * Array (
282 * [title] => A Title
283 * [page] => Page 2
284 * [site] => Site Name
285 * [tagline] =>
286 * )
287 *
288 * The final array is imploded into a string using the $title_sep value.
289 *
290 * $title = implode( ' ' . $title_sep . ' ', array_filter( $title ) );
291 *
292 * See wordpress/wp-includes/general-template.php.
293 */
294 public function document_title_parts( $title_parts ) {
295
296 if ( $this->p->debug->enabled ) {
297
298 $this->p->debug->log_arr( 'title_parts (argument)', $title_parts );
299
300 $this->p->debug->log( 'required call to WpssoPage->get_mod()' );
301 }
302
303 /*
304 * Note that in_the_loop() can be true in both archive and singular pages.
305 */
306 $use_post = apply_filters( 'wpsso_use_post', in_the_loop() ? true : false );
307
308 $mod = $this->get_mod( $use_post );
309
310 if ( $this->p->debug->enabled ) {
311
312 $this->p->debug->log_arr( 'mod', $mod );
313
314 $this->p->debug->log( 'getting title for ' . $this->p->options[ 'plugin_title_tag' ] );
315 }
316
317 if ( 'wp_title' === $this->p->options[ 'plugin_title_tag' ] ) {
318
319 if ( ! empty( $title_parts[ 'site' ] ) && empty( $title_parts[ 'title' ] ) ) {
320
321 $title_parts[ 'tagline' ] = true; // Add the tagline before returning the array.
322 }
323
324 } else {
325
326 $md_key = $this->p->options[ 'plugin_title_tag' ];
327 $max_len = $this->p->options[ 'plugin_title_tag' ];
328
329 /*
330 * Return a decoded title, which may be used in the RSS XML <title> tag.
331 */
332 $title_parts[ 'title' ] = $this->get_title( $mod, $md_key, $max_len, $title_sep = null, $num_hashtags = false, $do_encode = false );
333
334 if ( $mod[ 'is_home' ] ) { // Home page (static or blog archive).
335
336 unset( $title_parts[ 'site' ] ); // The title from WPSSO will be the site name.
337
338 $title_parts[ 'tagline' ] = true; // Add the tagline before returning the array.
339 }
340
341 unset( $title_parts[ 'page' ] ); // The title from WPSSO includes the page number.
342 }
343
344 if ( ! empty( $title_parts[ 'site' ] ) ) {
345
346 $title_parts[ 'site' ] = $this->p->opt->get_text( 'plugin_title_part_site' );
347 }
348
349 if ( ! empty( $title_parts[ 'tagline' ] ) ) {
350
351 $title_parts[ 'tagline' ] = $this->p->opt->get_text( 'plugin_title_part_tagline' );
352 }
353
354 /*
355 * Make sure the parts are ordered properly (just in case).
356 */
357 $title_parts = array_merge( array( 'title' => null, 'page' => null, 'site' => null, 'tagline' => null ), $title_parts );
358
359 if ( $this->p->debug->enabled ) {
360
361 $this->p->debug->log_arr( 'title_parts (returned)', $title_parts );
362 }
363
364 return $title_parts;
365 }
366
367 /*
368 * Since WordPress v4.4.
369 *
370 * Filters the document title.
371 *
372 * See wordpress/wp-includes/general-template.php.
373 */
374 public function document_title( $title ) {
375
376 if ( $this->p->debug->enabled ) {
377
378 $this->p->debug->log( 'title = ' . $title );
379 }
380
381 if ( false !== strpos( $title, '%%' ) ) {
382
383 if ( $this->p->debug->enabled ) {
384
385 $this->p->debug->log( 'required call to WpssoPage->get_mod()' );
386 }
387
388 /*
389 * Note that in_the_loop() can be true in both archive and singular pages.
390 */
391 $use_post = apply_filters( 'wpsso_use_post', in_the_loop() ? true : false );
392
393 $mod = $this->get_mod( $use_post );
394
395 $title = $this->p->util->inline->replace_variables( $title, $mod );
396 }
397
398 return SucomUtil::encode_html_emoji( $title ); // Does not double-encode.
399 }
400
401 /*
402 * Since WPSSO Core v15.20.0.
403 *
404 * Filters the string from wp_get_document_title() for get_wp_title_rss() and wp_title_rss() to convert HTML named
405 * entities into numbered entities.
406 *
407 * See wordpress/wp-includes/feed.php:
408 * return apply_filters( 'get_wp_title_rss', wp_get_document_title(), $deprecated );
409 *
410 * See wordpress/wp-includes/feed-rdf.php:
411 * <title><?php wp_title_rss(); ?></title>
412 *
413 * See wordpress/wp-includes/feed-rss2.php:
414 * <title><?php wp_title_rss(); ?></title>
415 *
416 * See wordpress/wp-includes/feed-rss2-comments.php:
417 * <title><?php printf( ent2ncr( __( 'Comments for %s' ) ), get_wp_title_rss() ); ?></title>
418 *
419 * See wordpress/wp-includes/feed-rss.php:
420 * <title><?php wp_title_rss(); ?></title>
421 */
422 public function get_wp_title_rss( $wp_title ) {
423
424 return ent2ncr( $wp_title ); // Converts named entities into numbered entities.
425 }
426
427 /*
428 * Since WPSSO Core v15.20.0.
429 *
430 * Filters the string from get_bloginfo() for get_bloginfo_rss() and bloginfo_rss() to encode special characters
431 * and convert HTML named entities into numbered entities.
432 *
433 * See https://developer.wordpress.org/reference/functions/get_bloginfo/.
434 */
435 public function get_bloginfo_rss( $bloginfo, $show ) {
436
437 $bloginfo = SucomUtil::encode_html_emoji( $bloginfo ); // Does not double-encode.
438
439 return ent2ncr( $bloginfo ); // Converts named entities into numbered entities.
440 }
441
442 /*
443 * Public method to sanitize arguments or modify values for get_title(), get_description(), etc.
444 *
445 * The $mod array argument is preferred but not required.
446 *
447 * $mod = true | false | post_id | $mod array
448 */
449 public function maybe_get_mod( $mod ) {
450
451 if ( ! is_array( $mod ) ) {
452
453 if ( $this->p->debug->enabled ) {
454
455 $this->p->debug->log( 'optional call to WpssoPage->get_mod()' );
456 }
457
458 $mod = $this->get_mod( $mod );
459 }
460
461 return $mod;
462 }
463
464 /*
465 * Determine and return the post/user/term module array.
466 *
467 * If any custom modifications are required to the WP_Query 'query_vars', they should be done before the 'wp_head'
468 * action is triggered. The WpssoHead->show_head() method calls WpssoPage->get_mod() to determine the current
469 * WordPress object (comment, post, term, or user), if any, and saves the 'query_vars' value for WordPress archive
470 * queries.
471 */
472 public function get_mod( $use_post = false, $mod = false, $wp_obj = false ) {
473
474 if ( ! is_array( $mod ) ) {
475
476 $mod = array();
477
478 } elseif ( isset( $mod[ 'obj' ] ) && is_object( $mod[ 'obj' ] ) ) {
479
480 if ( $this->p->debug->enabled ) {
481
482 $this->p->debug->log( 'exiting early: module object is already defined' );
483 }
484
485 return $mod;
486 }
487
488 if ( $this->p->debug->enabled ) {
489
490 $this->p->debug->log( 'use_post is ' . SucomUtil::get_use_post_string( $use_post ) );
491 }
492
493 /*
494 * Check for known WP objects and set the object module name and its object ID.
495 */
496 if ( is_object( $wp_obj ) ) {
497
498 if ( $this->p->debug->enabled ) {
499
500 $this->p->debug->log( 'wp_obj argument is ' . get_class( $wp_obj ) . ' object' );
501 }
502
503 switch ( get_class( $wp_obj ) ) {
504
505 case 'WP_Comment':
506
507 $mod[ 'name' ] = 'comment';
508
509 $mod[ 'id' ] = $wp_obj->comment_ID;
510
511 break;
512
513 case 'WP_Post':
514
515 $mod[ 'name' ] = 'post';
516
517 $mod[ 'id' ] = $wp_obj->ID;
518
519 break;
520
521 case 'WP_Term':
522
523 $mod[ 'name' ] = 'term';
524
525 $mod[ 'id' ] = $wp_obj->term_id;
526
527 break;
528
529 case 'WP_User':
530
531 $mod[ 'name' ] = 'user';
532
533 $mod[ 'id' ] = $wp_obj->ID;
534
535 break;
536
537 default:
538
539 if ( $this->p->debug->enabled ) {
540
541 $this->p->debug->log( 'unknown wp object class = ' . get_class( $wp_obj ) );
542 }
543
544 break;
545 }
546
547 } elseif ( $this->p->debug->enabled ) {
548
549 $this->p->debug->log( 'wp_obj argument is not an object' );
550 }
551
552
553 unset( $wp_obj ); // Done with $wp_obj.
554
555 if ( empty( $mod[ 'name' ] ) ) {
556
557 if ( SucomUtilWP::is_post_page( $use_post ) ) { // $use_post = true | false | post ID.
558
559 if ( $this->p->debug->enabled ) {
560
561 $this->p->debug->log( 'is_post_page is true' );
562 }
563
564 $mod[ 'name' ] = 'post';
565
566 } elseif ( SucomUtilWP::is_term_page() ) {
567
568 if ( $this->p->debug->enabled ) {
569
570 $this->p->debug->log( 'is_term_page is true' );
571 }
572
573 $mod[ 'name' ] = 'term';
574
575 } elseif ( SucomUtilWP::is_user_page() ) {
576
577 if ( $this->p->debug->enabled ) {
578
579 $this->p->debug->log( 'is_user_page is true' );
580 }
581
582 $mod[ 'name' ] = 'user';
583
584 } else $mod[ 'name' ] = false;
585 }
586
587 if ( empty( $mod[ 'id' ] ) ) {
588
589 switch ( $mod[ 'name' ] ) {
590
591 case 'comment':
592
593 $mod[ 'id' ] = SucomUtilWP::get_comment_object( false, 'id' );
594
595 break;
596
597 case 'post':
598
599 $mod[ 'id' ] = SucomUtilWP::get_post_object( $use_post, 'id' ); // $use_post = true | false | post_id
600
601 break;
602
603 case 'term':
604
605 $mod[ 'id' ] = SucomUtilWP::get_term_object( false, '', 'id' );
606
607 break;
608
609 case 'user':
610
611 $mod[ 'id' ] = SucomUtilWP::get_user_object( false, 'id' );
612
613 break;
614
615 default:
616
617 $mod[ 'id' ] = false;
618
619 break;
620 }
621 }
622
623 if ( ! empty( $mod[ 'name' ] ) ) {
624
625 if ( $this->p->debug->enabled ) {
626
627 $this->p->debug->log( 'getting $mod array from ' . $mod[ 'name' ] . ' module object' );
628 }
629 }
630
631 switch ( $mod[ 'name' ] ) {
632
633 case 'comment':
634
635 $mod = $this->p->comment->get_mod( $mod[ 'id' ] );
636
637 break;
638
639 case 'post':
640
641 $mod = $this->p->post->get_mod( $mod[ 'id' ] );
642
643 break;
644
645 case 'term':
646
647 $mod = $this->p->term->get_mod( $mod[ 'id' ] );
648
649 break;
650
651 case 'user':
652
653 $mod = $this->p->user->get_mod( $mod[ 'id' ] );
654
655 break;
656
657 default:
658
659 if ( $this->p->debug->enabled ) {
660
661 $this->p->debug->log( 'unknown module object - merging $mod defaults array' );
662 }
663
664 $mod = array_merge( WpssoAbstractWpMeta::get_mod_defaults(), $mod );
665
666 break;
667 }
668
669 /*
670 * WpssoPage elements.
671 */
672 global $wp_query;
673
674 $mod[ 'query_vars' ] = $wp_query->query_vars;
675
676 if ( empty( $mod[ 'paged' ] ) ) { // False by default.
677
678 if ( ! empty( $mod[ 'query_vars' ][ 'page' ] ) ) {
679
680 $mod[ 'paged' ] = $mod[ 'query_vars' ][ 'page' ];
681
682 } elseif ( ! empty( $mod[ 'query_vars' ][ 'paged' ] ) ) {
683
684 $mod[ 'paged' ] = $mod[ 'query_vars' ][ 'paged' ];
685 }
686 }
687
688 /*
689 * Note that 'paged_total' can be pre-defined by WpssoPost->get_mod() for posts with content (ie. singular)
690 * and paging in their content.
691 */
692 if ( empty( $mod[ 'paged_total' ] ) ) { // False by default.
693
694 if ( ! empty( $wp_query->max_num_pages ) ) {
695
696 $mod[ 'paged_total' ] = $wp_query->max_num_pages;
697 }
698 }
699
700 if ( $mod[ 'paged' ] && $mod[ 'paged_total' ] && $mod[ 'paged' ] > $mod[ 'paged_total' ] ) { // Just in case.
701
702 if ( $this->p->debug->enabled ) {
703
704 $this->p->debug->log( 'paged greater than paged_total - adjusting paged value' );
705 }
706
707 $mod[ 'paged' ] = $mod[ 'paged_total' ];
708 }
709
710 if ( empty( $mod[ 'comment_paged' ] ) ) { // False by default.
711
712 if ( ! empty( $mod[ 'query_vars' ][ 'cpage' ] ) ) {
713
714 $mod[ 'comment_paged' ] = $mod[ 'query_vars' ][ 'cpage' ];
715 }
716 }
717
718 $mod[ 'use_post' ] = $use_post;
719
720 if ( empty( $mod[ 'name' ] ) ) { // Not a comment, post, term, or user object.
721
722 if ( is_home() ) {
723
724 $mod[ 'is_home' ] = true; // Home page (static or blog archive).
725
726 $mod[ 'is_home_posts' ] = true; // Static posts page or blog archive page.
727
728 } elseif ( is_feed() ) {
729
730 $mod[ 'is_feed' ] = true;
731
732 } elseif ( is_404() ) {
733
734 $mod[ 'is_404' ] = true;
735
736 } elseif ( is_search() ) {
737
738 $mod[ 'is_search' ] = true;
739
740 } elseif ( is_archive() ) {
741
742 $mod[ 'is_archive' ] = true;
743
744 if ( is_date() ) {
745
746 $mod[ 'is_date' ] = true;
747
748 if ( is_year() ) {
749
750 $mod[ 'is_year' ] = true;
751
752 } elseif ( is_month() ) {
753
754 $mod[ 'is_month' ] = true;
755
756 } elseif ( is_day() ) {
757
758 $mod[ 'is_day' ] = true;
759 }
760 }
761 }
762
763 } elseif ( empty( $mod[ 'id' ] ) ) {
764
765 switch ( $mod[ 'name' ] ) {
766
767 case 'post':
768
769 /*
770 * If the query is a post with an ID of 0, then it may be a post type archive page
771 * that was setup incorrectly (ie. a post object without an ID or slug).
772 */
773 if ( ! empty( $mod[ 'query_vars' ][ 'post_type' ] ) ) {
774
775 $mod[ 'post_type' ] = $mod[ 'query_vars' ][ 'post_type' ];
776
777 $post_type_obj = get_post_type_object( $mod[ 'post_type' ] );
778
779 if ( is_object( $post_type_obj ) ) { // Just in case.
780
781 if ( isset( $post_type_obj->labels->name ) ) {
782
783 $mod[ 'post_type_label_plural' ] = $post_type_obj->labels->name;
784 }
785
786 if ( isset( $post_type_obj->labels->singular_name ) ) {
787
788 $mod[ 'post_type_label_single' ] = $post_type_obj->labels->singular_name;
789 }
790
791 if ( isset( $post_type_obj->public ) ) {
792
793 $mod[ 'is_public' ] = $post_type_obj->public ? true : false;
794 }
795
796 if ( is_post_type_archive() ) {
797
798 $mod[ 'is_post_type_archive' ] = true;
799
800 $mod[ 'is_archive' ] = $mod[ 'is_post_type_archive' ];
801 }
802 }
803
804 unset( $post_type_obj ); // Done with $post_type_obj.
805 }
806
807 break;
808 }
809 }
810
811 return $mod;
812 }
813
814 /*
815 * See WpssoFaqShortcodeFaq->do_shortcode().
816 * See WpssoSchema->add_itemlist_data().
817 * See WpssoSchema->add_posts_data().
818 */
819 public function get_posts_mods( array $mod ) {
820
821 if ( $this->p->debug->enabled ) {
822
823 $this->p->debug->mark();
824 }
825
826 $page_posts_mods = array();
827
828 if ( ! empty( $mod[ 'query_vars' ] ) ) {
829
830 if ( $this->p->debug->enabled ) {
831
832 $this->p->debug->log( 'using WP_Query query_vars to get post mods' );
833 }
834
835 global $wp_query;
836
837 $saved_wp_query = $wp_query;
838
839 $wp_query = new WP_Query( $mod[ 'query_vars' ] );
840
841 if ( $mod[ 'is_home_posts' ] ) { // Static posts page or blog archive page.
842
843 $wp_query->is_home = true;
844 }
845
846 if ( have_posts() ) {
847
848 if ( $this->p->debug->enabled ) {
849
850 $this->p->debug->log( 'looping through posts' );
851 }
852
853 $have_num = 0;
854
855 while ( have_posts() ) {
856
857 $have_num++;
858
859 the_post(); // Defines the $post global.
860
861 global $post;
862
863 if ( $this->p->debug->enabled ) {
864
865 $this->p->debug->log( '#' . $have_num . ': getting mod for post ID ' . $post->ID );
866 }
867
868 $page_posts_mods[] = $this->p->post->get_mod( $post->ID );
869 }
870
871 if ( $this->p->debug->enabled ) {
872
873 $this->p->debug->log( 'retrieved ' . $have_num . ' post mods' );
874 }
875
876 rewind_posts();
877
878 } elseif ( $this->p->debug->enabled ) {
879
880 $this->p->debug->log( 'no posts to add' );
881 }
882
883 $wp_query = $saved_wp_query;
884
885 } elseif ( ! empty( $mod[ 'obj' ] ) ) {
886
887 if ( $this->p->debug->enabled ) {
888
889 $this->p->debug->log( 'using module object to get post mods' );
890 }
891
892 $page_posts_mods = $mod[ 'obj' ]->get_posts_mods( $mod );
893
894 } elseif ( $this->p->debug->enabled ) {
895
896 $this->p->debug->log( 'no source to get post mods' );
897 }
898
899 $page_posts_mods = apply_filters( 'wpsso_page_posts_mods', $page_posts_mods, $mod );
900
901 if ( $this->p->debug->enabled ) {
902
903 $this->p->debug->log( 'returning ' . count( $page_posts_mods ) . ' post mods' );
904 }
905
906 return $page_posts_mods;
907 }
908
909 /*
910 * $mod = true | false | post_id | array
911 *
912 * $md_key = true | false | string | array
913 *
914 * $caption_type = 'title' | 'excerpt' | 'both'
915 *
916 * See WpssoRrssbFiltersEdit->filter_post_edit_share_rows().
917 */
918 public function get_caption( $mod = false, $md_key = null, $caption_type = 'title', $caption_max_len = 0, $num_hashtags = false, $do_encode = true ) {
919
920 if ( $this->p->debug->enabled ) {
921
922 $this->p->debug->log_args( array(
923 'mod' => $mod,
924 'md_key' => $md_key,
925 'caption_type' => $caption_type,
926 'caption_max_len' => $caption_max_len,
927 'num_hashtags' => $num_hashtags, // True, false, or numeric.
928 'do_encode' => $do_encode,
929 ) );
930 }
931
932 switch ( $caption_type ) {
933
934 case 'title':
935
936 $md_key = $this->sanitize_md_key( $md_key, $def_key = 'og_title' ); // Returns an array of metadata keys (can be empty).
937
938 break;
939
940 case 'excerpt':
941
942 $md_key = $this->sanitize_md_key( $md_key, $def_key = 'og_desc' ); // Returns an array of metadata keys (can be empty).
943
944 break;
945
946 case 'both':
947 default:
948
949 $md_key = $this->sanitize_md_key( $md_key, $def_key = 'og_caption' ); // Returns an array of metadata keys (can be empty).
950
951 break;
952 }
953
954 $caption_max_len = $this->sanitize_max_len( $caption_max_len ); // Returns max integer for numeric, string, or array value.
955 $mod = $this->maybe_get_mod( $mod ); // Returns $mod array if not provided.
956 $caption_text = $this->maybe_get_opt_multi( $mod, $md_key ); // Returns null or custom value.
957 $is_custom = empty( $caption_text ) ? false : true;
958
959 /*
960 * If there's no custom caption text, then go ahead and generate the caption text value.
961 */
962 if ( empty( $caption_text ) ) { // No custom value found.
963
964 /*
965 * Request all values un-encoded, then encode once we have the complete caption text.
966 */
967 switch ( $caption_type ) {
968
969 case 'title':
970
971 $caption_text = $this->get_title( $mod, $md_key_title = 'og_title', $caption_max_len,
972 $title_sep = null, $num_hashtags, $do_encode_title = false );
973
974 break;
975
976 case 'excerpt':
977
978 $caption_text = $this->get_description( $mod, $md_key_desc = 'og_desc', $caption_max_len,
979 $num_hashtags, $do_encode_desc = false );
980
981 break;
982
983 case 'both':
984
985 $title_sep = $this->maybe_get_title_sep(); // Returns default title separator (decoded).
986
987 $caption_text = $this->get_title( $mod, $md_key_title = 'og_title', $max_len = 'og_title',
988 $title_sep, $num_hashtags_title = false, $do_encode_title = false );
989
990 $caption_text_len = strlen( trim( $caption_text . ' ' . $title_sep ) . ' ' );
991
992 $adj_max_len = $caption_max_len - $caption_text_len;
993
994 $caption_desc = $this->get_description( $mod, $md_key_desc = 'og_desc', $adj_max_len,
995 $num_hashtags, $do_encode_desc = false );
996
997 $this->add_title_part( $caption_text, $title_sep, $caption_desc );
998
999 break;
1000 }
1001 }
1002
1003 if ( true === $do_encode ) {
1004
1005 $caption_text = SucomUtil::encode_html_emoji( $caption_text ); // Does not double-encode.
1006
1007 } else $caption_text = html_entity_decode( SucomUtil::decode_utf8( $caption_text ), ENT_QUOTES, $this->charset );
1008
1009 return apply_filters( 'wpsso_caption', $caption_text, $mod, $num_hashtags, $md_key );
1010 }
1011
1012 /*
1013 * $mod = true | false | post_id | array
1014 *
1015 * $md_key = true | false | string | array
1016 *
1017 * Use $title_sep = false to avoid adding term parent names in the term title.
1018 *
1019 * WpssoUtilInline->replace_variables() is applied to the final title text.
1020 *
1021 * The WPSSO BC add-on uses $title_sep = false to avoid prefixing term parents in the term titles.
1022 */
1023 public function get_title( $mod = false, $md_key = null, $max_len = 0, $title_sep = null, $num_hashtags = false, $do_encode = true ) {
1024
1025 if ( $this->p->debug->enabled ) {
1026
1027 $this->p->debug->log_args( array(
1028 'mod' => $mod,
1029 'md_key' => $md_key,
1030 'max_len' => $max_len,
1031 'title_sep' => $title_sep,
1032 'num_hashtags' => $num_hashtags, // True, false, or numeric.
1033 'do_encode' => $do_encode,
1034 ) );
1035 }
1036
1037 $mod = $this->maybe_get_mod( $mod ); // Returns $mod array if not provided.
1038 $md_key = $this->sanitize_md_key( $md_key, $def_key = 'seo_title' ); // Returns an array of metadata keys (can be empty).
1039 $max_len = $this->sanitize_max_len( $max_len ); // Returns max integer for numeric, string, or array value.
1040 $dots = $this->maybe_get_ellipsis(); // Returns default ellipsis (decoded).
1041 $title_sep = $this->maybe_get_title_sep( $title_sep ); // Returns default title separator (decoded) if not provided.
1042 $title_text = $this->maybe_get_opt_multi( $mod, $md_key ); // Returns null or custom value.
1043 $is_custom = empty( $title_text ) ? false : true;
1044 $hashtags = '';
1045
1046 /*
1047 * Get seed if no custom meta title.
1048 */
1049 if ( empty( $title_text ) ) {
1050
1051 $title_text = apply_filters( 'wpsso_title_seed', '', $mod, $num_hashtags, $md_key, $title_sep );
1052
1053 if ( ! empty( $title_text ) ) {
1054
1055 if ( $this->p->debug->enabled ) {
1056
1057 $this->p->debug->log( 'title seed = ' . $title_text );
1058 }
1059 }
1060 }
1061
1062 /*
1063 * If there's no custom title, and no pre-seed, then go ahead and generate the title value.
1064 *
1065 * A custom or pre-seed title is expected to provide any hashtags, so get hashtags only when generating the
1066 * title value.
1067 */
1068 if ( empty( $title_text ) ) {
1069
1070 $title_text = $this->get_the_title( $mod, $title_sep );
1071
1072 $hashtags = $this->get_hashtags( $mod, $num_hashtags );
1073 }
1074
1075 /*
1076 * Add the page number if it's greater than 1 and we don't already have a '%%page%%' or '%%pagenumber%%'
1077 * inline variable in the title.
1078 */
1079 $page_number_transl = '';
1080
1081 if ( $mod[ 'paged' ] > 1 ) {
1082
1083 if ( false === strpos( $title_text, '%%page%%' ) && false === strpos( $title_text, '%%pagenumber%%' ) ) {
1084
1085 if ( $mod[ 'paged_total' ] > 1 ) {
1086
1087 $page_number_transl = sprintf( __( 'Page %1$d of %2$d', 'wpsso' ), $mod[ 'paged' ], $mod[ 'paged_total' ] );
1088
1089 } else {
1090
1091 $page_number_transl = sprintf( __( 'Page %1$d', 'wpsso' ), $mod[ 'paged' ] );
1092 }
1093 }
1094 }
1095
1096 /*
1097 * Titles comprised entirely of HTML content will be empty after running cleanup_html_tags(), so remove the
1098 * HTML tags before maybe falling back to the generic title.
1099 */
1100 $title_text = $this->p->util->cleanup_html_tags( $title_text, $strip_tags = true, $use_img_alt = true );
1101
1102 /*
1103 * If there's still no title, then fallback to a generic version.
1104 */
1105 if ( empty( $title_text ) ) {
1106
1107 if ( $this->p->debug->enabled ) {
1108
1109 $this->p->debug->log( 'title is empty - using generic title text' );
1110 }
1111
1112 $title_text = $this->p->opt->get_text( 'plugin_no_title_text' ); // No Title Text.
1113 }
1114
1115 /*
1116 * Replace any inline variables in the string before checking/adjusting its length.
1117 */
1118 if ( false !== strpos( $title_text, '%%' ) ) {
1119
1120 /*
1121 * Override the default 'title_sep' value.
1122 */
1123 $title_text = $this->p->util->inline->replace_variables( $title_text, $mod, $atts = array( 'title_sep' => $title_sep ) );
1124 }
1125
1126 /*
1127 * Check title against string length limits.
1128 */
1129 if ( $max_len > 0 ) {
1130
1131 /*
1132 * If we have a page number, reduce the max length by the separator, page number, and two spaces.
1133 */
1134 $adj_max_len = empty( $page_number_transl ) ? $max_len : $max_len - strlen ( $title_sep ) - strlen( $page_number_transl ) - 2;
1135
1136 /*
1137 * If we have any hashtags, further reduce the max title length by the hashtags and one space.
1138 */
1139 $adj_max_len = empty( $hashtags ) ? $adj_max_len : $adj_max_len - strlen( $hashtags ) - 1;
1140
1141 $title_text = $this->p->util->limit_text_length( $title_text, $adj_max_len, $dots, $cleanup_html = false );
1142 }
1143
1144 /*
1145 * Once the title length has been adjusted, we can add the page number and hashtags.
1146 */
1147 if ( ! empty( $page_number_transl ) ) {
1148
1149 $this->add_title_part( $title_text, $title_sep, $page_number_transl );
1150 }
1151
1152 if ( ! empty( $hashtags ) ) {
1153
1154 $this->add_title_part( $title_text, '', $hashtags );
1155 }
1156
1157 /*
1158 * Maybe return the values encoded (true by default).
1159 */
1160 if ( $do_encode ) {
1161
1162 $title_text = SucomUtil::encode_html_emoji( $title_text ); // Does not double-encode.
1163
1164 $title_sep = SucomUtil::encode_html_emoji( $title_sep ); // Does not double-encode.
1165 }
1166
1167 return apply_filters( 'wpsso_title', $title_text, $mod, $num_hashtags, $md_key, $title_sep, $is_custom );
1168 }
1169
1170 /*
1171 * $mod = true | false | post_id | array.
1172 *
1173 * $md_key = true | false | string | array.
1174 *
1175 * WpssoUtilInline->replace_variables() is applied to the final description text.
1176 */
1177 public function get_description( $mod = false, $md_key = null, $max_len = 0, $num_hashtags = false, $do_encode = true ) {
1178
1179 if ( $this->p->debug->enabled ) {
1180
1181 $this->p->debug->log_args( array(
1182 'mod' => $mod,
1183 'md_key' => $md_key,
1184 'max_len' => $max_len,
1185 'num_hashtags' => $num_hashtags, // True, false, or numeric.
1186 'do_encode' => $do_encode,
1187 ) );
1188 }
1189
1190 $mod = $this->maybe_get_mod( $mod ); // Returns $mod array if not provided.
1191 $md_key = $this->sanitize_md_key( $md_key, $def_key = 'seo_desc' ); // Returns an array of metadata keys (can be empty).
1192 $max_len = $this->sanitize_max_len( $max_len ); // Returns max integer for numeric, string, or array value.
1193 $dots = $this->maybe_get_ellipsis(); // Returns default ellipsis (decoded).
1194 $desc_text = $this->maybe_get_opt_multi( $mod, $md_key ); // Returns null or custom value.
1195 $is_custom = empty( $desc_text ) ? false : true;
1196 $hashtags = '';
1197
1198 /*
1199 * Get seed if no custom meta description.
1200 */
1201 if ( empty( $desc_text ) ) {
1202
1203 $desc_text = apply_filters( 'wpsso_description_seed', '', $mod, $num_hashtags, $md_key );
1204
1205 if ( ! empty( $desc_text ) ) {
1206
1207 if ( $this->p->debug->enabled ) {
1208
1209 $this->p->debug->log( 'description seed = ' . $desc_text );
1210 }
1211 }
1212 }
1213
1214 /*
1215 * If there's no custom description, and no pre-seed, then go ahead and generate the description value.
1216 *
1217 * A custom or pre-seed description is expected to provide any hashtags, so get hashtags only when
1218 * generating the description value.
1219 */
1220 if ( empty( $desc_text ) ) {
1221
1222 $desc_text = $this->get_the_description( $mod );
1223
1224 $hashtags = $this->get_hashtags( $mod, $num_hashtags );
1225 }
1226
1227 /*
1228 * Descriptions comprised entirely of HTML content will be empty after running cleanup_html_tags(), so
1229 * remove the HTML tags before maybe falling back to the generic description.
1230 */
1231 $desc_text = $this->p->util->cleanup_html_tags( $desc_text, $strip_tags = true, $use_img_alt = true );
1232
1233 /*
1234 * If there's still no description, then fallback to a generic version.
1235 */
1236 if ( empty( $desc_text ) ) {
1237
1238 if ( $this->p->debug->enabled ) {
1239
1240 $this->p->debug->log( 'description is empty - using generic description text' );
1241 }
1242
1243 $desc_text = $this->p->opt->get_text( 'plugin_no_desc_text' ); // No Description Text.
1244 }
1245
1246 /*
1247 * Replace any inline variables in the string before checking/adjusting its length.
1248 */
1249 if ( false !== strpos( $desc_text, '%%' ) ) {
1250
1251 $desc_text = $this->p->util->inline->replace_variables( $desc_text, $mod );
1252 }
1253
1254 /*
1255 * Check description against string length limits.
1256 */
1257 if ( $max_len > 0 ) {
1258
1259 /*
1260 * If we have any hashtags, reduce the max length by the hashtags and one space.
1261 */
1262 $adj_max_len = empty( $hashtags ) ? $max_len : $max_len - strlen( $hashtags ) - 1;
1263
1264 $desc_text = $this->p->util->limit_text_length( $desc_text, $adj_max_len, $dots, $cleanup_html = false );
1265 }
1266
1267 /*
1268 * Once the description length has been adjusted, we can add the hashtags.
1269 */
1270 if ( ! empty( $hashtags ) ) {
1271
1272 $this->add_title_part( $desc_text, '', $hashtags );
1273 }
1274
1275 /*
1276 * Maybe return the values encoded (true by default).
1277 */
1278 if ( $do_encode ) {
1279
1280 $desc_text = SucomUtil::encode_html_emoji( $desc_text ); // Does not double-encode.
1281 }
1282
1283 return apply_filters( 'wpsso_description', $desc_text, $mod, $num_hashtags, $md_key, $is_custom );
1284 }
1285
1286 public function get_text( $mod = false, $md_key = null, $max_len = 0, $num_hashtags = false, $do_encode = true ) {
1287
1288 if ( $this->p->debug->enabled ) {
1289
1290 $this->p->debug->log_args( array(
1291 'mod' => $mod,
1292 'md_key' => $md_key,
1293 'max_len' => $max_len,
1294 'num_hashtags' => $num_hashtags, // True, false, or numeric.
1295 'do_encode' => $do_encode,
1296 ) );
1297 }
1298
1299 $mod = $this->maybe_get_mod( $mod ); // Returns $mod array if not provided.
1300 $md_key = $this->sanitize_md_key( $md_key, $def_key = 'schema_text' ); // Returns an array of metadata keys (can be empty).
1301 $max_len = $this->sanitize_max_len( $max_len ); // Returns max integer for numeric, string, or array value.
1302 $dots = $this->maybe_get_ellipsis(); // Returns default ellipsis (decoded).
1303 $text = $this->maybe_get_opt_multi( $mod, $md_key ); // Returns null or custom value.
1304 $is_custom = empty( $text ) ? false : true;
1305
1306 /*
1307 * If there's no custom text, then go ahead and generate the text value.
1308 */
1309 if ( empty( $title_text ) ) {
1310
1311 $text = $this->get_the_text( $mod );
1312
1313 $hashtags = $this->get_hashtags( $mod, $num_hashtags );
1314 }
1315
1316 /*
1317 * Check text against string length limits.
1318 */
1319 if ( $max_len > 0 ) {
1320
1321 /*
1322 * If we have any hashtags, reduce the max length by the hashtags and one space.
1323 */
1324 $adj_max_len = empty( $hashtags ) ? $max_len : $max_len - strlen( $hashtags ) - 1;
1325
1326 $text = $this->p->util->limit_text_length( $text, $adj_max_len, $dots, $cleanup_html = false );
1327 }
1328
1329 /*
1330 * Once the text length has been adjusted, we can add the hashtags.
1331 */
1332 if ( ! empty( $hashtags ) ) {
1333
1334 $this->add_title_part( $text, '', $hashtags );
1335 }
1336
1337 /*
1338 * Maybe return the values encoded (true by default).
1339 */
1340 if ( $do_encode ) {
1341
1342 $text = SucomUtil::encode_html_emoji( $text ); // Does not double-encode.
1343 }
1344
1345 return apply_filters( 'wpsso_text', $text, $mod, $num_hashtags, $md_key, $is_custom );
1346 }
1347
1348 /*
1349 * Use $title_sep = false to avoid adding term parent names in the term title.
1350 *
1351 * WpssoUtilInline->replace_variables() is called from the WpssoPage->get_title() method. Do not use
1352 * WpssoUtilInline->replace_variables() in this method as WpssoPage->get_title() is also called from
1353 * WpssoUtilInline->replace_variables(), and this would create a circular loop.
1354 *
1355 * See WpssoBcBreadcrumb->add_breadcrumblist_data().
1356 * See WpssoPage->get_title().
1357 * See WpssoUtilInline->replace_callback().
1358 */
1359 public function get_the_title( array $mod, $title_sep = null ) {
1360
1361 if ( $this->p->debug->enabled ) {
1362
1363 $this->p->debug->mark();
1364 }
1365
1366 $title_sep = $this->maybe_get_title_sep( $title_sep ); // Returns default title separator (decoded) if not provided.
1367 $title_text = '';
1368
1369 /*
1370 * Similar module type logic can be found in the following methods:
1371 *
1372 * See WpssoOpenGraph->get_mod_og_type().
1373 * See WpssoPage->get_the_title().
1374 * See WpssoPage->get_the_description().
1375 * See WpssoSchema->get_mod_schema_type().
1376 * See WpssoUtil->get_canonical_url().
1377 */
1378 if ( $mod[ 'is_home' ] ) { // Home page (static or blog archive).
1379
1380 $title_text = SucomUtilWP::get_site_name( $this->p->options );
1381
1382 } elseif ( $mod[ 'is_comment' ] ) {
1383
1384 if ( is_numeric( $mod[ 'comment_rating' ] ) ) {
1385
1386 $title_text = $this->p->opt->get_text( 'plugin_comment_review_title' );
1387
1388 } elseif ( $mod[ 'comment_parent' ] ) {
1389
1390 $title_text = $this->p->opt->get_text( 'plugin_comment_reply_title' );
1391
1392 } else $title_text = $this->p->opt->get_text( 'plugin_comment_title' );
1393
1394 } elseif ( $mod[ 'is_post' ] ) {
1395
1396 if ( $mod[ 'post_type' ] && is_string( $mod[ 'post_type' ] ) ) { // Just in case.
1397
1398 if ( $mod[ 'is_post_type_archive' ] ) { // The post ID may be 0.
1399
1400 $title_text = $this->p->opt->get_text( 'plugin_pta_' . $mod[ 'post_type' ] . '_title' );
1401
1402 if ( empty( $title_text ) ) { // Just in case.
1403
1404 $post_type_obj = get_post_type_object( $mod[ 'post_type' ] );
1405
1406 if ( isset( $post_type_obj->label ) ) { // Just in case.
1407
1408 $title_text = $post_type_obj->label;
1409 }
1410 }
1411
1412 } elseif ( $mod[ 'wp_obj' ] instanceof WP_Post ) {
1413
1414 /*
1415 * Note that get_the_title() applies the 'the_title' filter, not the 'wp_title' filter.
1416 */
1417 $title_text = get_the_title( $mod[ 'wp_obj' ] );
1418
1419 } elseif ( $mod[ 'id' ] ) {
1420
1421 /*
1422 * Note that get_the_title() applies the 'the_title' filter, not the 'wp_title' filter.
1423 */
1424 $title_text = get_the_title( $mod[ 'id' ] );
1425
1426 } elseif ( $this->p->debug->enabled ) {
1427
1428 $this->p->debug->log( 'no post object or id' );
1429 }
1430
1431 $title_text = html_entity_decode( $title_text, ENT_QUOTES, $this->charset );
1432
1433 } elseif ( $this->p->debug->enabled ) {
1434
1435 $this->p->debug->log( 'no post type' );
1436 }
1437
1438 } elseif ( $mod[ 'is_term' ] ) {
1439
1440 /*
1441 * The default 'plugin_term_page_title' value is '%%term_hierarchy%%'.
1442 *
1443 * If $title_sep is false, then use '%%term_title%%' instead.
1444 */
1445 if ( false === $title_sep ) {
1446
1447 $title_text = '%%term_title%%';
1448
1449 } else $title_text = $this->p->opt->get_text( 'plugin_term_page_title' );
1450
1451 } elseif ( $mod[ 'is_user' ] ) {
1452
1453 $title_text = $this->p->opt->get_text( 'plugin_author_page_title' );
1454
1455 } elseif ( $mod[ 'is_feed' ] ) {
1456
1457 $title_text = $this->p->opt->get_text( 'plugin_feed_title' );
1458
1459 } elseif ( $mod[ 'is_404' ] ) {
1460
1461 $title_text = $this->p->opt->get_text( 'plugin_404_page_title' );
1462
1463 } elseif ( $mod[ 'is_search' ] ) {
1464
1465 $title_text = $this->p->opt->get_text( 'plugin_search_page_title' );
1466
1467 } elseif ( $mod[ 'is_archive' ] ) {
1468
1469 if ( $mod[ 'is_date' ] ) {
1470
1471 if ( $mod[ 'is_year' ] ) {
1472
1473 $title_text = $this->p->opt->get_text( 'plugin_year_page_title' );
1474
1475 } elseif ( $mod[ 'is_month' ] ) {
1476
1477 $title_text = $this->p->opt->get_text( 'plugin_month_page_title' );
1478
1479 } elseif ( $mod[ 'is_day' ] ) {
1480
1481 $title_text = $this->p->opt->get_text( 'plugin_day_page_title' );
1482 }
1483 }
1484 }
1485
1486 return apply_filters( 'wpsso_the_title', $title_text, $mod, $title_sep );
1487 }
1488
1489 /*
1490 * WpssoUtilInline->replace_variables() is called from the WpssoPage->get_description() method. Do not use
1491 * WpssoUtilInline->replace_variables() in this method as WpssoPage->get_description() is also called from
1492 * WpssoUtilInline->replace_variables(), and this would create a circular loop.
1493 *
1494 * See WpssoPage->get_description().
1495 * See WpssoUtilInline->replace_callback().
1496 */
1497 public function get_the_description( array $mod ) {
1498
1499 if ( $this->p->debug->enabled ) {
1500
1501 $this->p->debug->mark();
1502 }
1503
1504 $desc_text = '';
1505
1506 /*
1507 * Similar module type logic can be found in the following methods:
1508 *
1509 * See WpssoOpenGraph->get_mod_og_type().
1510 * See WpssoPage->get_the_title().
1511 * See WpssoPage->get_the_description().
1512 * See WpssoSchema->get_mod_schema_type().
1513 * See WpssoUtil->get_canonical_url().
1514 */
1515 if ( $mod[ 'is_home' ] ) { // Home page (static or blog archive).
1516
1517 if ( $mod[ 'is_post' ] && $mod[ 'id' ] ) {
1518
1519 $desc_text = $this->get_the_excerpt( $mod );
1520 }
1521
1522 if ( empty( $desc_text ) ) {
1523
1524 $desc_text = SucomUtilWP::get_site_description( $this->p->options );
1525 }
1526
1527 } elseif ( $mod[ 'is_comment' ] ) {
1528
1529 if ( $mod[ 'wp_obj' ] instanceof WP_Comment ) {
1530
1531 $desc_text = get_comment_excerpt( $mod[ 'wp_obj' ] );
1532
1533 } elseif ( $mod[ 'id' ] ) { // Just in case.
1534
1535 $desc_text = get_comment_excerpt( $mod[ 'id' ] );
1536 }
1537
1538 } elseif ( $mod[ 'is_post' ] ) {
1539
1540 if ( $mod[ 'post_type' ] && is_string( $mod[ 'post_type' ] ) ) { // Just in case.
1541
1542 if ( $mod[ 'is_post_type_archive' ] ) { // The post ID may be 0.
1543
1544 $desc_text = $this->p->opt->get_text( 'plugin_pta_' . $mod[ 'post_type' ] . '_desc' );
1545
1546 if ( empty( $desc_text ) ) { // Just in case.
1547
1548 $post_type_obj = get_post_type_object( $mod[ 'post_type' ] );
1549
1550 if ( ! empty( $post_type_obj->description ) ) {
1551
1552 $desc_text = $post_type_obj->description;
1553 }
1554 }
1555
1556 } else {
1557
1558 $desc_text = $this->get_the_excerpt( $mod );
1559
1560 /*
1561 * If there's no excerpt, then fallback to the content.
1562 */
1563 if ( empty( $desc_text ) ) {
1564
1565 if ( $this->p->debug->enabled ) {
1566
1567 $this->p->debug->log( 'getting the content for post ID ' . $mod[ 'id' ] );
1568 }
1569
1570 $desc_text = $this->get_the_content( $mod );
1571
1572 /*
1573 * Ignore everything before the first paragraph.
1574 */
1575 if ( ! empty( $desc_text ) ) {
1576
1577 if ( $this->p->debug->enabled ) {
1578
1579 $this->p->debug->log( 'removing text before the first paragraph' );
1580 }
1581
1582 /*
1583 * U = Inverts the "greediness" of quantifiers so that they are not greedy by default.
1584 * i = Letters in the pattern match both upper and lower case letters.
1585 *
1586 * See http://php.net/manual/en/reference.pcre.pattern.modifiers.php.
1587 */
1588 $desc_text = preg_replace( '/^.*<p[^>]*>/Usi', '', $desc_text );
1589 }
1590 }
1591
1592 /*
1593 * Fallback to the image alt value.
1594 */
1595 if ( empty( $desc_text ) ) {
1596
1597 if ( $mod[ 'is_attachment' ] && 'image' === $mod[ 'post_mime_group' ] ) {
1598
1599 if ( $this->p->debug->enabled ) {
1600
1601 $this->p->debug->log( 'falling back to attachment image alt text' );
1602 }
1603
1604 $desc_text = get_metadata( 'post', $mod[ 'id' ], '_wp_attachment_image_alt', $single = true );
1605 }
1606 }
1607 }
1608
1609 } elseif ( $this->p->debug->enabled ) {
1610
1611 $this->p->debug->log( 'no post type' );
1612 }
1613
1614 } elseif ( $mod[ 'is_term' ] ) {
1615
1616 if ( $mod[ 'wp_obj' ] instanceof WP_Term ) {
1617
1618 if ( isset( $mod[ 'wp_obj' ]->description ) ) {
1619
1620 $desc_text = $mod[ 'wp_obj' ]->description;
1621 }
1622
1623 } elseif ( $mod[ 'id' ] ) {
1624
1625 $term_obj = SucomUtilWP::get_term_object( $mod[ 'id' ], $mod[ 'tax_slug' ] );
1626
1627 if ( isset( $term_obj->description ) ) {
1628
1629 $desc_text = $term_obj->description;
1630 }
1631 }
1632
1633 if ( '' === $desc_text ) {
1634
1635 $desc_text = $this->p->opt->get_text( 'plugin_term_page_desc' );
1636 }
1637
1638 } elseif ( $mod[ 'is_user' ] ) {
1639
1640 if ( $mod[ 'wp_obj' ] instanceof WP_User ) {
1641
1642 if ( isset( $mod[ 'wp_obj' ]->description ) ) {
1643
1644 $desc_text = $mod[ 'wp_obj' ]->description;
1645 }
1646
1647 } elseif ( $mod[ 'id' ] ) { // Just in case.
1648
1649 $user_obj = SucomUtilWP::get_user_object( $mod[ 'id' ] );
1650
1651 if ( isset( $user_obj->description ) ) {
1652
1653 $desc_text = $user_obj->description;
1654 }
1655 }
1656
1657 if ( '' === $desc_text ) {
1658
1659 $desc_text = $this->p->opt->get_text( 'plugin_author_page_desc' );
1660 }
1661
1662 } elseif ( $mod[ 'is_404' ] ) {
1663
1664 $desc_text = $this->p->opt->get_text( 'plugin_404_page_desc' );
1665
1666 } elseif ( $mod[ 'is_search' ] ) {
1667
1668 $desc_text = $this->p->opt->get_text( 'plugin_search_page_desc' );
1669
1670 } elseif ( $mod[ 'is_archive' ] ) {
1671
1672 if ( $mod[ 'is_date' ] ) {
1673
1674 if ( $mod[ 'is_year' ] ) {
1675
1676 $desc_text = $this->p->opt->get_text( 'plugin_year_page_desc' );
1677
1678 } elseif ( $mod[ 'is_month' ] ) {
1679
1680 $desc_text = $this->p->opt->get_text( 'plugin_month_page_desc' );
1681
1682 } elseif ( $mod[ 'is_day' ] ) {
1683
1684 $desc_text = $this->p->opt->get_text( 'plugin_day_page_desc' );
1685 }
1686 }
1687 }
1688
1689 return apply_filters( 'wpsso_the_description', $desc_text, $mod );
1690 }
1691
1692 public function get_the_excerpt( array $mod ) {
1693
1694 if ( $this->p->debug->enabled ) {
1695
1696 $this->p->debug->mark();
1697 }
1698
1699 $excerpt_text = '';
1700
1701 if ( $mod[ 'is_post' ] ) { // Only post objects have excerpts.
1702
1703 if ( $mod[ 'wp_obj' ] instanceof WP_Post ) {
1704
1705 $excerpt_text = get_post_field( 'post_excerpt', $mod[ 'wp_obj' ] );
1706
1707 if ( $this->p->debug->enabled ) {
1708
1709 $this->p->debug->log( 'get_post_field() for WP_Post = ' . $excerpt_text );
1710 }
1711
1712 } elseif ( $mod[ 'id' ] ) {
1713
1714 $excerpt_text = get_post_field( 'post_excerpt', $mod[ 'id' ] );
1715
1716 if ( $this->p->debug->enabled ) {
1717
1718 $this->p->debug->log( 'get_post_field() for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] . ' = ' . $excerpt_text );
1719 }
1720 }
1721
1722 $filter_excerpt = empty( $this->p->options[ 'plugin_filter_excerpt' ] ) ? false : true;
1723
1724 $filter_excerpt = apply_filters( 'wpsso_the_excerpt_filter_excerpt', $filter_excerpt );
1725
1726 if ( $filter_excerpt ) {
1727
1728 $excerpt_text = $this->p->util->safe_apply_filters( array( 'get_the_excerpt', $excerpt_text, $mod[ 'wp_obj' ] ), $mod );
1729
1730 if ( ! is_string( $excerpt_text ) ) {
1731
1732 if ( $this->p->debug->enabled ) {
1733
1734 $this->p->debug->log( 'excerpt_text from "get_the_excerpt" is ' . gettype( $content ) );
1735 }
1736
1737 $excerpt_text = '';
1738 }
1739 }
1740 }
1741
1742 return apply_filters( 'wpsso_the_excerpt', $excerpt_text, $mod );
1743 }
1744
1745 /*
1746 * The cache is cleared by WpssoAbstractWpMeta->clear_mod_cache().
1747 */
1748 public function clear_the_content( array $mod ) {
1749
1750 if ( $this->p->debug->enabled ) {
1751
1752 $this->p->debug->mark();
1753 }
1754
1755 /*
1756 * Note that the sort order, page number, locale, amp and embed checks are provided by
1757 * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
1758 */
1759 $canonical_url = $this->p->util->get_canonical_url( $mod );
1760 $cache_md5_pre = 'wpsso_c_';
1761 $cache_salt = __CLASS__ . '::the_content(' . SucomUtil::get_mod_salt( $mod, $canonical_url ) . ')';
1762 $cache_id = $cache_md5_pre . md5( $cache_salt );
1763
1764 if ( $this->p->debug->enabled ) {
1765
1766 $this->p->debug->log( 'canonical url = ' . $canonical_url );
1767 $this->p->debug->log( 'wp cache salt = ' . $cache_salt );
1768 $this->p->debug->log( 'wp cache id = ' . $cache_id );
1769 }
1770
1771 wp_cache_delete( $cache_id );
1772
1773 return;
1774 }
1775
1776 /*
1777 * The cache is cleared by WpssoAbstractWpMeta->clear_mod_cache().
1778 */
1779 public function get_the_content( array $mod, $flatten = true ) {
1780
1781 if ( $this->p->debug->enabled ) {
1782
1783 $this->p->debug->mark(); // Log execution time and memory usage.
1784
1785 $this->p->debug->log_args( array(
1786 'mod' => $mod,
1787 ) );
1788 }
1789
1790 /*
1791 * Note that the sort order, page number, locale, amp and embed checks are provided by
1792 * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
1793 */
1794 $filter_content = empty( $this->p->options[ 'plugin_filter_content' ] ) ? false : true;
1795 $filter_content = apply_filters( 'wpsso_the_content_filter_content', $filter_content );
1796 $filter_blocks = apply_filters( 'wpsso_the_content_filter_blocks', true );
1797 $canonical_url = $this->p->util->get_canonical_url( $mod );
1798 $cache_md5_pre = 'wpsso_c_';
1799 $cache_exp_secs = $this->p->util->get_cache_exp_secs( $cache_md5_pre, $cache_type = 'wp_cache' );
1800 $cache_salt = __CLASS__ . '::the_content(' . SucomUtil::get_mod_salt( $mod, $canonical_url ) . ')';
1801 $cache_id = $cache_md5_pre . md5( $cache_salt );
1802 $cache_index = 'locale:' . SucomUtilWP::get_locale( $mod ) . '_filter:' . ( $filter_content ? 'true' : 'false' );
1803 $cache_array = array();
1804
1805 if ( $this->p->debug->enabled ) {
1806
1807 $this->p->debug->log( 'canonical url = ' . $canonical_url );
1808 $this->p->debug->log( 'filter content = ' . ( $filter_content ? 'true' : 'false' ) );
1809 $this->p->debug->log( 'wp cache expire = ' . $cache_exp_secs );
1810 $this->p->debug->log( 'wp cache salt = ' . $cache_salt );
1811 $this->p->debug->log( 'wp cache id = ' . $cache_id );
1812 $this->p->debug->log( 'wp cache index = ' . $cache_index );
1813 }
1814
1815 if ( $cache_exp_secs > 0 ) {
1816
1817 $cache_array = wp_cache_get( $cache_id, __METHOD__ );
1818
1819 if ( isset( $cache_array[ $cache_index ] ) ) {
1820
1821 if ( $this->p->debug->enabled ) {
1822
1823 $this->p->debug->log( 'exiting early: cache index found in wp_cache' );
1824 }
1825
1826 $content =& $cache_array[ $cache_index ];
1827
1828 /*
1829 * Maybe put everything on one line but do not cache the re-formatted content.
1830 */
1831 return $flatten ? preg_replace( '/[\s\r\n]+/s', ' ', $content ) : $content;
1832
1833 } else {
1834
1835 if ( $this->p->debug->enabled ) {
1836
1837 $this->p->debug->log( 'cache index not in wp_cache' );
1838 }
1839
1840 if ( ! is_array( $cache_array ) ) {
1841
1842 $cache_array = array();
1843 }
1844 }
1845 }
1846
1847 if ( $this->p->debug->enabled ) {
1848
1849 $this->p->debug->log( 'initializing new wp cache element' );
1850 }
1851
1852 $cache_array[ $cache_index ] = ''; // Initialize the cache element.
1853
1854 $content =& $cache_array[ $cache_index ]; // Reference the cache element.
1855
1856 /*
1857 * Apply the seed filter.
1858 *
1859 * Return false to prevent the comment or post content from being used.
1860 *
1861 * See WpssoIntegEcomWooCommerce->filter_the_content_seed().
1862 */
1863 $content = apply_filters( 'wpsso_the_content_seed', '', $mod );
1864
1865 /*
1866 * Make sure that we have a string (empty or not) to work with.
1867 */
1868 if ( ! is_string( $content ) ) {
1869
1870 if ( $this->p->debug->enabled ) {
1871
1872 $this->p->debug->log( 'content from "wpsso_the_content_seed" is ' . gettype( $content ) );
1873 }
1874
1875 $content = '';
1876
1877 } elseif ( '' !== $content ) {
1878
1879 if ( $this->p->debug->enabled ) {
1880
1881 $this->p->debug->log( 'content seed = ' . $content );
1882 }
1883
1884 } elseif ( $mod[ 'is_post' ] && $mod[ 'id' ] ) {
1885
1886 $content = (string) $mod[ 'wp_obj' ]->post_content;
1887
1888 } elseif ( $mod[ 'is_comment' ] && $mod[ 'id' ] ) {
1889
1890 $content = (string) $mod[ 'wp_obj' ]->comment_content;
1891 }
1892
1893 /*
1894 * Remove singlepics, which we detect and use before-hand.
1895 */
1896 $count = null;
1897
1898 $content = preg_replace( '/\[singlepic[^\]]+\]/', '', $content, $limit = -1, $count );
1899
1900 if ( $count ) {
1901
1902 if ( $this->p->debug->enabled ) {
1903
1904 $this->p->debug->log( $count . ' singlepic shortcode(s) removed from content' );
1905 }
1906 }
1907
1908 /*
1909 * Maybe apply 'the_content' filter to expand shortcodes and blocks.
1910 */
1911 if ( $filter_content ) {
1912
1913 $use_bfo = SucomUtil::get_const( 'WPSSO_CONTENT_BLOCK_FILTER_OUTPUT', true );
1914 $mtime_max = SucomUtil::get_const( 'WPSSO_CONTENT_FILTERS_MAX_TIME', 1.50 );
1915
1916 if ( $this->p->debug->enabled ) {
1917
1918 $this->p->debug->mark( 'applying filters "the_content"' ); // Begin timer.
1919 }
1920
1921 $content = $this->p->util->safe_apply_filters( array( 'the_content', $content ), $mod, $mtime_max, $use_bfo );
1922
1923 if ( $this->p->debug->enabled ) {
1924
1925 $this->p->debug->mark( 'applying filters "the_content"' ); // End timer.
1926 }
1927
1928 if ( ! is_string( $content ) ) {
1929
1930 if ( $this->p->debug->enabled ) {
1931
1932 $this->p->debug->log( 'content from "the_content" is ' . gettype( $content ) );
1933 }
1934
1935 $content = '';
1936 }
1937
1938 } else {
1939
1940 /*
1941 * Maybe fallback and apply only the 'do_blocks' filters.
1942 */
1943 if ( $filter_blocks ) {
1944
1945 if ( $this->p->debug->enabled ) {
1946
1947 $this->p->debug->mark( 'calling do_blocks()' ); // Begin timer.
1948 }
1949
1950 $content = do_blocks( $content );
1951
1952 if ( $this->p->debug->enabled ) {
1953
1954 $this->p->debug->mark( 'calling do_blocks()' ); // End timer.
1955 }
1956
1957 if ( ! is_string( $content ) ) {
1958
1959 if ( $this->p->debug->enabled ) {
1960
1961 $this->p->debug->log( 'content from do_blocks() is ' . gettype( $content ) );
1962 }
1963
1964 $content = '';
1965 }
1966 }
1967
1968 /*
1969 * When the content filter is disabled, fallback and apply our own shortcode filter.
1970 */
1971 if ( false !== strpos( $content, '[' ) ) {
1972
1973 if ( $this->p->debug->enabled ) {
1974
1975 $this->p->debug->mark( 'applying filters "wpsso_do_shortcode"' ); // Begin timer.
1976 }
1977
1978 $content = apply_filters( 'wpsso_do_shortcode', $content );
1979
1980 if ( $this->p->debug->enabled ) {
1981
1982 $this->p->debug->mark( 'applying filters "wpsso_do_shortcode"' ); // End timer.
1983 }
1984
1985 if ( ! is_string( $content ) ) {
1986
1987 if ( $this->p->debug->enabled ) {
1988
1989 $this->p->debug->log( 'content from "wpsso_do_shortcode" is ' . gettype( $content ) );
1990 }
1991
1992 $content = '';
1993 }
1994 }
1995 }
1996
1997 /*
1998 * Maybe use only a certain part of the content.
1999 */
2000 if ( false !== strpos( $content, 'wpsso-content' ) ) {
2001
2002 $content = preg_replace( '/^.*<!-- *wpsso-content *-->(.*)<!--\/wpsso-content *-->.*$/Us', '$1', $content );
2003 }
2004
2005 /*
2006 * Maybe remove text between ignore markers.
2007 */
2008 if ( false !== strpos( $content, 'wpsso-ignore' ) ) {
2009
2010 $content = preg_replace( '/<!-- *wpsso-ignore *-->.*<!-- *\/wpsso-ignore *-->/Us', ' ', $content );
2011 }
2012
2013 /*
2014 * Remove "Google+" link and text.
2015 */
2016 if ( false !== strpos( $content, '>Google+<' ) ) {
2017
2018 $content = preg_replace( '/<a +rel="author" +href="" +style="display:none;">Google\+<\/a>/', ' ', $content );
2019 }
2020
2021 /*
2022 * Prefix caption text.
2023 */
2024 if ( false !== strpos( $content, '<p class="wp-caption-text">' ) ) {
2025
2026 $caption_prefix = $this->p->opt->get_text( 'plugin_p_cap_prefix' );
2027
2028 if ( ! empty( $caption_prefix ) ) {
2029
2030 $content = preg_replace( '/<p class="wp-caption-text">/', '$0' . $caption_prefix . ' ', $content );
2031 }
2032 }
2033
2034 /*
2035 * Apply the filter.
2036 */
2037 $content = apply_filters( 'wpsso_the_content', $content, $mod );
2038
2039 if ( ! is_string( $content ) ) {
2040
2041 if ( $this->p->debug->enabled ) {
2042
2043 $this->p->debug->log( 'content from "wpsso_the_content" is ' . gettype( $content ) );
2044 }
2045
2046 $content = '';
2047 }
2048
2049 /*
2050 * Save content to non-persistant cache.
2051 */
2052 if ( $cache_exp_secs > 0 ) {
2053
2054 /*
2055 * Adds a group or set of groups to the list of non-persistent groups.
2056 *
2057 * Note that only a few caching plugins support this feature.
2058 */
2059 wp_cache_add_non_persistent_groups( array( __METHOD__ ) );
2060
2061 wp_cache_set( $cache_id, $cache_array, __METHOD__, $cache_exp_secs );
2062
2063 if ( $this->p->debug->enabled ) {
2064
2065 $this->p->debug->log( 'content array saved to wp_cache for ' . $cache_exp_secs . ' seconds');
2066 }
2067 }
2068
2069 /*
2070 * Maybe put everything on one line but do not cache the re-formatted content.
2071 */
2072 return $flatten ? preg_replace( '/[\s\r\n]+/s', ' ', $content ) : $content;
2073 }
2074
2075 /*
2076 * Returns the content text, stripped of all HTML tags.
2077 */
2078 public function get_the_text( array $mod ) {
2079
2080 if ( $this->p->debug->enabled ) {
2081
2082 $this->p->debug->mark();
2083 }
2084
2085 $text = $this->get_the_content( $mod );
2086 $text = preg_replace( '/<!\[CDATA\[.*\]\]>/Us', '', $text );
2087 $text = preg_replace( '/<pre[^>]*>.*<\/pre>/Us', '', $text );
2088 $text = $this->p->util->cleanup_html_tags( $text, $strip_tags = true, $use_img_alt = true );
2089
2090 return apply_filters( 'wpsso_the_text', $text, $mod );
2091 }
2092
2093 /*
2094 * Returns a comma delimited text string of keywords (ie. post tag names).
2095 */
2096 public function get_keywords_csv( array $mod, $md_key = null ) {
2097
2098 if ( $this->p->debug->enabled ) {
2099
2100 $this->p->debug->mark();
2101 }
2102
2103 $keywords = '';
2104 $is_custom = false;
2105
2106 /*
2107 * Check for custom keywords if a metadata index key is provided.
2108 */
2109 if ( ! empty( $md_key ) && 'none' !== $md_key ) { // $md_key can be a string or array.
2110
2111 if ( ! empty( $mod[ 'obj' ] ) && $mod[ 'id' ] ) { // Just in case.
2112
2113 $keywords = $mod[ 'obj' ]->get_options_multi( $mod[ 'id' ], $md_key );
2114
2115 if ( ! empty( $keywords ) ) {
2116
2117 $is_custom = false;
2118
2119 if ( $this->p->debug->enabled ) {
2120
2121 $this->p->debug->log( 'custom keywords = ' . $keywords );
2122 }
2123 }
2124 }
2125 }
2126
2127 /*
2128 * If there's no custom keywords, then go ahead and generate the keywords value.
2129 */
2130 if ( empty( $keywords ) ) {
2131
2132 $tags = $this->get_tag_names( $mod );
2133
2134 if ( ! empty( $tags ) ) {
2135
2136 $keywords = SucomUtil::array_to_keywords( $tags ); // Returns a comma delimited text string.
2137
2138 if ( $this->p->debug->enabled ) {
2139
2140 $this->p->debug->log( 'keywords = ' . $keywords );
2141 }
2142 }
2143 }
2144
2145 return apply_filters( 'wpsso_keywords', $keywords, $mod, $md_key );
2146 }
2147
2148 /*
2149 * Returns a space delimited text string of hashtags.
2150 */
2151 public function get_hashtags( array $mod, $num_hashtags = false ) {
2152
2153 if ( $this->p->debug->enabled ) {
2154
2155 $this->p->debug->mark();
2156 }
2157
2158 $hashtags = apply_filters( 'wpsso_hashtags_seed', '', $mod, $num_hashtags );
2159
2160 if ( ! empty( $hashtags ) ) { // Seed hashtags returned.
2161
2162 if ( $this->p->debug->enabled ) {
2163
2164 $this->p->debug->log( 'hashtags seed = ' . $hashtags );
2165 }
2166
2167 } elseif ( false !== $num_hashtags ) {
2168
2169 if ( true === $num_hashtags ) {
2170
2171 $num_hashtags = $this->p->options[ 'og_desc_hashtags' ];
2172 }
2173
2174 if ( is_numeric( $num_hashtags ) && $num_hashtags >= 1 ) {
2175
2176 $tags = $this->get_tag_names( $mod );
2177
2178 $tags = array_slice( $tags, 0, $num_hashtags );
2179
2180 if ( ! empty( $tags ) ) {
2181
2182 $hashtags = SucomUtil::array_to_hashtags( $tags );
2183
2184 if ( $this->p->debug->enabled ) {
2185
2186 $this->p->debug->log( 'hashtags = ' . $hashtags );
2187 }
2188 }
2189 }
2190 }
2191
2192 return apply_filters( 'wpsso_hashtags', $hashtags, $mod, $num_hashtags );
2193 }
2194
2195 /*
2196 * Returns an array of post tags.
2197 */
2198 public function get_tag_names( array $mod ) {
2199
2200 if ( $this->p->debug->enabled ) {
2201
2202 $this->p->debug->mark();
2203 }
2204
2205 /*
2206 * See WpssoIntegEcomWooCommerce->filter_tag_names_seed().
2207 */
2208 $tags = apply_filters( 'wpsso_tag_names_seed', null, $mod );
2209
2210 if ( ! is_array( $tags ) ) {
2211
2212 if ( $mod[ 'is_post' ] ) {
2213
2214 if ( 'post' === $mod[ 'post_type' ] ) {
2215
2216 $taxonomy = 'post_tag';
2217
2218 } elseif ( 'page' === $mod[ 'post_type' ] && ! empty( $this->p->options[ 'plugin_page_tags' ] ) ) {
2219
2220 $taxonomy = SucomUtil::get_const( 'WPSSO_PAGE_TAG_TAXONOMY' );
2221
2222 } else $taxonomy = '';
2223
2224 if ( ! empty( $mod[ 'post_type' ] ) && is_string( $mod[ 'post_type' ] ) ) { // Not empty string.
2225
2226 $filter_name = SucomUtil::sanitize_hookname( 'wpsso_' . $mod[ 'post_type' ] . '_tag_taxonomy' );
2227
2228 $taxonomy = apply_filters( $filter_name, $taxonomy, $mod );
2229
2230 if ( ! empty( $taxonomy ) ) {
2231
2232 $tags = wp_get_post_terms( $mod[ 'id' ], $taxonomy, $args = array( 'fields' => 'names' ) );
2233 }
2234
2235 unset( $filter_name, $taxonomy );
2236 }
2237 }
2238 }
2239
2240 $tags = is_array( $tags ) ? array_unique( $tags ) : array();
2241
2242 $tags = apply_filters( 'wpsso_tag_names', $tags, $mod );
2243
2244 if ( $this->p->debug->enabled ) {
2245
2246 $this->p->debug->log_arr( 'tags', $tags );
2247 }
2248
2249 return $tags;
2250 }
2251
2252 /*
2253 * Includes parent names in the term title by default, unless the $title_sep value is false.
2254 *
2255 * See WpssoUtilInline->replace_callback().
2256 * See WpssoFaqShortcodeFaq->do_shortcode().
2257 */
2258 public function get_term_title( $term_id = 0, $title_sep = null ) {
2259
2260 if ( $this->p->debug->enabled ) {
2261
2262 $this->p->debug->mark();
2263 }
2264
2265 $term_obj = SucomUtilWP::get_term_object( $term_id );
2266 $term_id = isset( $term_obj->term_id ) ? $term_obj->term_id : 0;
2267 $title_text = isset( $term_obj->name ) ? $term_obj->name : '';
2268 $title_sep = $this->maybe_get_title_sep( $title_sep ); // Returns default title separator (decoded) if not provided.
2269
2270 /*
2271 * If we have a title separator and a parent, then redefine the title text with the parent list.
2272 */
2273 if ( ! empty( $title_sep ) ) {
2274
2275 if ( ! empty( $term_obj->parent ) ) {
2276
2277 $term_parents = get_term_parents_list( $term_id, $term_obj->taxonomy, $args = array(
2278 'format' => 'name', // Use term names or slugs for display.
2279 'separator' => ' ' . $title_sep . ' ', // Separator for between the terms.
2280 'link' => false, // Whether to format as a link.
2281 'inclusive' => true, // Include the term to get the parents for.
2282 ) );
2283
2284 if ( $term_parents && ! is_wp_error( $term_parents ) ) {
2285
2286 /*
2287 * Trim excess separator.
2288 */
2289 $title_text = preg_replace( '/ *' . preg_quote( $title_sep, '/' ) . ' *$/', '', $term_parents );
2290 }
2291 }
2292 }
2293
2294 return apply_filters( 'wpsso_term_title', $title_text, $term_id, $title_sep );
2295 }
2296
2297 /*
2298 * Returns an empty or formatted string (number with minutes).
2299 */
2300 public function get_reading_time( array $mod ) {
2301
2302 $reading_mins = $this->get_reading_mins( $mod );
2303
2304 return $this->fmt_reading_mins( $reading_mins );
2305 }
2306
2307 public function get_reading_mins( array $mod ) {
2308
2309 $content = $this->get_the_content( $mod );
2310 $words_per_min = WPSSO_READING_WORDS_PER_MIN;
2311 $reading_mins = null;
2312
2313 if ( $mod[ 'obj' ] && $mod[ 'id' ] ) { // Just in case.
2314
2315 $reading_mins = $mod[ 'obj' ]->get_options( $mod[ 'id' ], 'schema_reading_mins' );
2316 }
2317
2318 if ( null === $reading_mins ) { // Default value or no custom value.
2319
2320 $reading_mins = round( str_word_count( wp_strip_all_tags( $content ) ) / $words_per_min );
2321 }
2322
2323 return $reading_mins;
2324 }
2325
2326 public function fmt_reading_mins( $reading_mins ) {
2327
2328 return $reading_mins ? sprintf( _n( '%s minute', '%s minutes', $reading_mins, 'wpsso' ), $reading_mins ) : '';
2329 }
2330
2331 /*
2332 * Add a separator and a value to the left/right hand side of the title.
2333 */
2334 private function add_title_part( &$title, $title_sep, $part, $hand = 'right' ) {
2335
2336 if ( $part ) { // Adding an empty $part would leave a hanging separator.
2337
2338 if ( $title ) {
2339
2340 if ( 'left' === $hand ) {
2341
2342 $title = $part . ' ' . trim( $title_sep . ' ' . $title );
2343
2344 } else $title = trim( $title . ' ' . $title_sep ) . ' ' . $part;
2345
2346 } else $title = $part; // We have a part, but no title.
2347 }
2348
2349 return trim( $title );
2350 }
2351
2352 /*
2353 * Public method to sanitize arguments or modify values for get_title(), get_description(), etc.
2354 *
2355 * Returns default ellipsis (decoded) if not provided (ie. $ellipsis = null).
2356 */
2357 private function maybe_get_ellipsis( $ellipsis = null ) {
2358
2359 if ( null === $ellipsis ) {
2360
2361 $ellipsis = html_entity_decode( $this->p->options[ 'og_ellipsis' ], ENT_QUOTES, $this->charset );
2362 }
2363
2364 return $ellipsis;
2365 }
2366
2367 /*
2368 * Public method to sanitize arguments or modify values for get_title(), get_description(), etc.
2369 *
2370 * Returns default title separator (decoded) if not provided (ie. $title_sep = null).
2371 */
2372 private function maybe_get_title_sep( $title_sep = null ) {
2373
2374 if ( null === $title_sep ) {
2375
2376 $title_sep = html_entity_decode( $this->p->options[ 'og_title_sep' ], ENT_QUOTES, $this->charset );
2377 }
2378
2379 return $title_sep;
2380 }
2381
2382 /*
2383 * Public method to sanitize arguments or modify values for get_title(), get_description(), etc.
2384 */
2385 private function maybe_get_opt_multi( $mod, $md_key ) {
2386
2387 if ( ! empty( $md_key ) && 'none' !== $md_key ) { // Make sure we have something to work with.
2388
2389 if ( ! empty( $mod[ 'obj' ] ) && $mod[ 'id' ] ) { // Just in case.
2390
2391 return $mod[ 'obj' ]->get_options_multi( $mod[ 'id' ], $md_key );
2392 }
2393 }
2394
2395 return null;
2396 }
2397
2398 /*
2399 * Private method to sanitize arguments or modify values for get_title(), get_description(), etc.
2400 *
2401 * Returns an array of metadata keys (can be empty).
2402 *
2403 * $md_key = true | false | string | array
2404 */
2405 private function sanitize_md_key( $md_key, $def_key = '' ) {
2406
2407 if ( false === $md_key || 'none' === $md_key || '' === $md_key ) { // Nothing to do.
2408
2409 return array();
2410
2411 } elseif ( true === $md_key || null === $md_key || $md_key === $def_key ) { // Return the default key array.
2412
2413 return WpssoConfig::get_md_keys_fallback( $def_key );
2414 }
2415
2416 if ( ! is_array( $md_key ) ) {
2417
2418 $md_key = WpssoConfig::get_md_keys_fallback( $md_key );
2419 }
2420
2421 foreach ( WpssoConfig::get_md_keys_fallback( $def_key ) as $key ) {
2422
2423 $md_key[] = $key;
2424 }
2425
2426 $md_key = array_unique( $md_key ); // Just in case.
2427
2428 return $md_key;
2429 }
2430
2431 /*
2432 * Private method to sanitize arguments or modify values for get_title(), get_description(), etc.
2433 *
2434 * Return 0 by default.
2435 */
2436 private function sanitize_max_len( $max_len ) {
2437
2438 if ( is_numeric( $max_len ) ) {
2439
2440 return (int) $max_len;
2441
2442 } elseif ( is_array( $max_len ) && isset( $max_len[ 'max' ] ) ) {
2443
2444 return (int) $max_len[ 'max' ];
2445
2446 } elseif ( is_string( $max_len ) ) {
2447
2448 $input_limits = WpssoConfig::get_input_limits( $max_len ); // Uses a local cache.
2449
2450 if ( ! empty( $input_limits[ 'max' ] ) ) {
2451
2452 return (int) $input_limits[ 'max' ];
2453 }
2454 }
2455
2456 return 0;
2457 }
2458 }
2459 }
2460