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
← All changes | lib/page.php +2459 -0 21.13.122.7.0 View file →
@@ -1,0 +1,2459 @@
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 +}