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/util.php +4210 -0 21.13.122.7.0 View file →
@@ -1,0 +1,4210 @@
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( 'SucomUtil' ) ) {
19 +
20 + require_once WPSSO_PLUGINDIR . 'lib/com/util.php';
21 +}
22 +
23 +if ( ! class_exists( 'WpssoUtil' ) ) {
24 +
25 + class WpssoUtil extends SucomUtil {
26 +
27 + private $p; // Wpsso class object.
28 +
29 + private $cache_size_labels = array(); // Array for image size labels.
30 + private $cache_size_opts = array(); // Array for image size option prefix.
31 + private $cache_uniq_urls = array(); // Array to detect duplicate image URLs.
32 +
33 + private $is_functions = array(
34 + 'is_404',
35 + 'is_admin',
36 + 'is_archive',
37 + 'is_attachment',
38 + 'is_author',
39 + 'is_category',
40 + 'is_customize_preview',
41 + 'is_embed',
42 + 'is_feed',
43 + 'is_front_page',
44 + 'is_home',
45 + 'is_multisite',
46 + 'is_page',
47 + 'is_post_type_archive',
48 + 'is_preview',
49 + 'is_rtl',
50 + 'is_search',
51 + 'is_single',
52 + 'is_singular',
53 + 'is_ssl',
54 + 'is_tag',
55 + 'is_tax',
56 +
57 + /*
58 + * Common e-commerce functions.
59 + */
60 + 'is_account_page',
61 + 'is_cart',
62 + 'is_checkout',
63 + 'is_checkout_pay_page',
64 + 'is_product',
65 + 'is_product_category',
66 + 'is_product_tag',
67 + 'is_shop',
68 +
69 + /*
70 + * Other functions.
71 + */
72 + 'is_amp_endpoint',
73 + 'is_sitemap',
74 + 'is_sitemap_stylesheet',
75 + 'wp_doing_ajax',
76 + 'wp_doing_cron',
77 + 'wp_using_ext_object_cache',
78 + );
79 +
80 + public $blocks; // WpssoUtilBlocks.
81 + public $cache; // WpssoUtilCache.
82 + public $cf; // WpssoUtilCustomFields.
83 + public $inline; // WpssoUtilInline.
84 + public $metabox; // WpssoUtilMetabox.
85 + public $reg; // WpssoUtilReg.
86 + public $robots; // WpssoUtilRobots.
87 + public $units; // WpssoUtilUnits.
88 + public $wc; // WpssoUtilWooCommerce.
89 +
90 + public function __construct( &$plugin ) {
91 +
92 + $this->p =& $plugin;
93 +
94 + if ( $this->p->debug->enabled ) {
95 +
96 + $this->p->debug->mark();
97 + }
98 +
99 + $this->set_util_instances();
100 +
101 + $this->add_plugin_actions( $this, array( 'scheduled_task_started' => 1 ), $prio = -1000 );
102 +
103 + /*
104 + * Log the locale change and clear the SucomUtilWP::get_locale() cache.
105 + */
106 + add_action( 'change_locale', array( $this, 'locale_changed' ), -100, 1 );
107 + add_action( 'switch_locale', array( $this, 'locale_switched' ), -100, 1 );
108 + add_action( 'restore_previous_locale', array( $this, 'locale_restored' ), -100, 2 );
109 +
110 + /*
111 + * Add our image sizes on the front-end, back-end, AJAX calls, and REST API calls.
112 + */
113 + if ( $this->p->debug->enabled ) {
114 +
115 + $this->p->debug->log( 'adding WpssoUtil->add_plugin_image_sizes() action hooks' );
116 + }
117 +
118 + add_action( 'wp', array( $this, 'add_plugin_image_sizes' ), -100 ); // Front-end compatibility.
119 + add_action( 'admin_init', array( $this, 'add_plugin_image_sizes' ), -100 ); // Back-end + AJAX compatibility.
120 + add_action( 'rest_api_init', array( $this, 'add_plugin_image_sizes' ), -100 ); // REST API compatibility.
121 + }
122 +
123 + public function set_util_instances() {
124 +
125 + /*
126 + * Instantiate WpssoUtilBlocks.
127 + */
128 + if ( ! class_exists( 'WpssoUtilBlocks' ) ) {
129 +
130 + require_once WPSSO_PLUGINDIR . 'lib/util-blocks.php';
131 + }
132 +
133 + $this->blocks = new WpssoUtilBlocks( $this->p, $this );
134 +
135 + /*
136 + * Instantiate WpssoUtilCache.
137 + */
138 + if ( ! class_exists( 'WpssoUtilCache' ) ) {
139 +
140 + require_once WPSSO_PLUGINDIR . 'lib/util-cache.php';
141 + }
142 +
143 + $this->cache = new WpssoUtilCache( $this->p, $this );
144 +
145 + /*
146 + * Instantiate WpssoUtilCustomFields.
147 + */
148 + if ( ! class_exists( 'WpssoUtilCustomFields' ) ) {
149 +
150 + require_once WPSSO_PLUGINDIR . 'lib/util-custom-fields.php';
151 + }
152 +
153 + $this->cf = new WpssoUtilCustomFields( $this->p, $this );
154 +
155 + /*
156 + * Instantiate WpssoUtilInline.
157 + */
158 + if ( ! class_exists( 'WpssoUtilInline' ) ) {
159 +
160 + require_once WPSSO_PLUGINDIR . 'lib/util-inline.php';
161 + }
162 +
163 + $this->inline = new WpssoUtilInline( $this->p, $this );
164 +
165 + /*
166 + * Instantiate WpssoUtilMetabox.
167 + */
168 + if ( ! class_exists( 'WpssoUtilMetabox' ) ) {
169 +
170 + require_once WPSSO_PLUGINDIR . 'lib/util-metabox.php';
171 + }
172 +
173 + $this->metabox = new WpssoUtilMetabox( $this->p );
174 +
175 + /*
176 + * Instantiate WpssoUtilReg.
177 + */
178 + if ( ! class_exists( 'WpssoUtilReg' ) ) {
179 +
180 + require_once WPSSO_PLUGINDIR . 'lib/util-reg.php';
181 + }
182 +
183 + $this->reg = new WpssoUtilReg( $this->p );
184 +
185 + /*
186 + * Instantiate WpssoUtilReg.
187 + */
188 + if ( ! class_exists( 'WpssoUtilRobots' ) ) {
189 +
190 + require_once WPSSO_PLUGINDIR . 'lib/util-robots.php';
191 + }
192 +
193 + $this->robots = new WpssoUtilRobots( $this->p );
194 +
195 + /*
196 + * Instantiate WpssoUtilUnits.
197 + */
198 + if ( ! class_exists( 'WpssoUtilUnits' ) ) {
199 +
200 + require_once WPSSO_PLUGINDIR . 'lib/util-units.php';
201 + }
202 +
203 + $this->units = new WpssoUtilUnits( $this->p );
204 +
205 + /*
206 + * Instantiate WpssoUtilWooCommerce.
207 + */
208 + if ( ! empty( $this->p->avail[ 'ecom' ][ 'woocommerce' ] ) ) {
209 +
210 + if ( ! class_exists( 'WpssoUtilWooCommerce' ) ) {
211 +
212 + require_once WPSSO_PLUGINDIR . 'lib/util-woocommerce.php';
213 + }
214 +
215 + $this->wc = new WpssoUtilWooCommerce( $this->p );
216 + }
217 + }
218 +
219 + /*
220 + * Add plugin image sizes before starting a background task, like refreshing the plugin cache.
221 + */
222 + public function action_scheduled_task_started( $user_id ) {
223 +
224 + $this->add_plugin_image_sizes();
225 + }
226 +
227 + /*
228 + * Monitor the WordPress 'change_locale' action for locale changes.
229 + *
230 + * Log the locale change and clear the SucomUtilWP::get_locale() cache.
231 + */
232 + public function locale_changed( $locale ) {
233 +
234 + if ( $this->p->debug->enabled ) {
235 +
236 + $this->p->debug->log( 'wp locale changed to ' . $locale );
237 + }
238 +
239 + SucomUtilWP::clear_locale_cache();
240 + }
241 +
242 + /*
243 + * Cache is cleared by WpssoUtil->locale_changed().
244 + */
245 + public function locale_switched( $locale ) {
246 +
247 + if ( $this->p->debug->enabled ) {
248 +
249 + $this->p->debug->log( 'wp locale switched to ' . $locale );
250 + }
251 + }
252 +
253 + /*
254 + * Cache is cleared by WpssoUtil->locale_changed().
255 + */
256 + public function locale_restored( $locale, $previous_locale ) {
257 +
258 + if ( $this->p->debug->enabled ) {
259 +
260 + $this->p->debug->log( 'wp locale restored to ' . $locale . ' from ' . $previous_locale );
261 + }
262 + }
263 +
264 + /*
265 + * Can be called directly and from the "wp", "rest_api_init", and "current_screen" actions.
266 + *
267 + * This method does not return a value, so do not use it as a filter. ;-)
268 + */
269 + public function add_plugin_image_sizes() {
270 +
271 + /*
272 + * Allow various plugin add-ons to provide their image names, labels, etc. The first dimension array key is
273 + * the option name prefix by default. You can also include the width, height, crop, crop_x, and crop_y
274 + * values.
275 + *
276 + * Array (
277 + * [og] => Array (
278 + * [name] => opengraph
279 + * [label] => Open Graph // Pre-translated.
280 + * )
281 + * )
282 + */
283 + if ( $this->p->debug->enabled ) {
284 +
285 + $this->p->debug->mark( 'define image sizes' ); // Begin timer.
286 + }
287 +
288 + /*
289 + * Get the default options only once.
290 + */
291 + static $defs = null;
292 +
293 + $image_sizes = apply_filters( 'wpsso_plugin_image_sizes', array() );
294 +
295 + foreach( $image_sizes as $opt_prefix => $size_info ) {
296 +
297 + if ( ! is_array( $size_info ) ) { // Just in case.
298 +
299 + continue;
300 + }
301 +
302 + foreach ( array( 'width', 'height', 'crop', 'crop_x', 'crop_y' ) as $opt_suffix ) {
303 +
304 + /*
305 + * Value provided by filters.
306 + */
307 + if ( isset( $size_info[ $opt_suffix ] ) ) {
308 +
309 + continue;
310 +
311 + /*
312 + * Plugin settings.
313 + */
314 + } elseif ( isset( $this->p->options[ $opt_prefix . '_img_' . $opt_suffix ] ) ) {
315 +
316 + $size_info[ $opt_suffix ] = $this->p->options[ $opt_prefix . '_img_' . $opt_suffix ];
317 +
318 + /*
319 + * Default settings.
320 + */
321 + } else {
322 +
323 + if ( null === $defs ) {
324 +
325 + if ( $this->p->debug->enabled ) {
326 +
327 + $this->p->debug->log( 'getting default option values' );
328 + }
329 +
330 + $defs = $this->p->opt->get_defaults();
331 + }
332 +
333 + if ( isset( $defs[ $opt_prefix . '_img_' . $opt_suffix ] ) ) { // Just in case.
334 +
335 + $size_info[ $opt_suffix ] = $defs[ $opt_prefix . '_img_' . $opt_suffix ];
336 +
337 + } else $size_info[ $opt_suffix ] = null;
338 + }
339 + }
340 +
341 + if ( empty( $size_info[ 'crop' ] ) ) {
342 +
343 + $size_info[ 'crop' ] = false;
344 +
345 + } else {
346 +
347 + $size_info[ 'crop' ] = true;
348 +
349 + $new_crop = array( 'center', 'center' );
350 +
351 + foreach ( array( 'crop_x', 'crop_y' ) as $crop_key => $opt_suffix ) {
352 +
353 + if ( ! empty( $size_info[ $opt_suffix ] ) && $size_info[ $opt_suffix ] !== 'none' ) {
354 +
355 + $new_crop[ $crop_key ] = $size_info[ $opt_suffix ];
356 + }
357 + }
358 +
359 + if ( $new_crop !== array( 'center', 'center' ) ) {
360 +
361 + $size_info[ 'crop' ] = $new_crop;
362 + }
363 + }
364 +
365 + if ( $size_info[ 'width' ] > 0 && $size_info[ 'height' ] > 0 ) {
366 +
367 + if ( isset( $size_info[ 'label_transl' ] ) ) {
368 +
369 + $size_label = $size_info[ 'label_transl' ];
370 +
371 + } elseif ( isset( $size_info[ 'label' ] ) ) {
372 +
373 + $size_label = $size_info[ 'label' ];
374 +
375 + } else {
376 +
377 + $size_label = $size_info[ 'name' ];
378 + }
379 +
380 + $this->cache_size_labels[ 'wpsso-' . $size_info[ 'name' ] ] = $size_label;
381 +
382 + $this->cache_size_opts[ 'wpsso-' . $size_info[ 'name' ] ] = $opt_prefix;
383 +
384 + /*
385 + * Add the image size.
386 + */
387 + add_image_size( 'wpsso-' . $size_info[ 'name' ], $size_info[ 'width' ], $size_info[ 'height' ], $size_info[ 'crop' ] );
388 +
389 + if ( $this->p->debug->enabled ) {
390 +
391 + $this->p->debug->log( 'added image size wpsso-' . $size_info[ 'name' ] . ' ' .
392 + $size_info[ 'width' ] . 'x' . $size_info[ 'height' ] . ' ' . ( empty( $size_info[ 'crop' ] ) ?
393 + 'uncropped' : 'cropped ' . $size_info[ 'crop_x' ] . '/' . $size_info[ 'crop_y' ] ) );
394 + }
395 + }
396 + }
397 +
398 + /*
399 + * Reset the default options variable.
400 + */
401 + $defs = null;
402 +
403 + if ( $this->p->debug->enabled ) {
404 +
405 + $this->p->debug->mark( 'define image sizes' ); // End timer.
406 + }
407 + }
408 +
409 + /*
410 + * Get the width, height and crop value for all image sizes.
411 + *
412 + * Returns an associative array with the image size name as the array key.
413 + */
414 + public function get_image_sizes( $attachment_id = false ) {
415 +
416 + $image_sizes = array();
417 +
418 + foreach ( get_intermediate_image_sizes() as $size_name ) {
419 +
420 + $image_sizes[ $size_name ] = $this->get_size_info( $size_name, $attachment_id );
421 + }
422 +
423 + return $image_sizes;
424 + }
425 +
426 + public function is_size_cropped( $size_name = 'thumbnail', $attachment_id = false ) {
427 +
428 + /*
429 + * Example $size_info = Array (
430 + * [size_name] => wpsso-opengraph,
431 + * [attachment_id] => false,
432 + * [width] => 1200,
433 + * [height] => 630,
434 + * [crop] => 1,
435 + * [is_cropped] => 1,
436 + * [dims_transl] => 1200x630 cropped,
437 + * [label_transl] => Open Graph (Facebook and oEmbed),
438 + * [opt_prefix] => og,
439 + * );
440 + */
441 + $size_info = $this->get_size_info( $size_name, $attachment_id );
442 +
443 + return empty( $size_info[ 'is_cropped' ] ) ? false : true;
444 + }
445 +
446 + /*
447 + * Get the width, height and crop value for a specific image size.
448 + */
449 + public function get_size_info( $size_name = 'thumbnail', $attachment_id = false ) {
450 +
451 + if ( ! is_string( $size_name ) ) { // Just in case.
452 +
453 + return false;
454 + }
455 +
456 + static $local_fifo = array();
457 +
458 + if ( isset( $local_fifo[ $size_name ][ $attachment_id ] ) ) {
459 +
460 + return $local_fifo[ $size_name ][ $attachment_id ];
461 +
462 + } elseif ( isset( $local_fifo[ $size_name ] ) ) {
463 +
464 + /*
465 + * Maybe limit the number of array elements.
466 + */
467 + $local_fifo[ $size_name ] = SucomUtil::array_slice_fifo( $local_fifo[ $size_name ], WPSSO_CACHE_ARRAY_FIFO_MAX );
468 +
469 + } else $local_fifo[ $size_name ] = array();
470 +
471 + global $_wp_additional_image_sizes;
472 +
473 + if ( isset( $_wp_additional_image_sizes[ $size_name ][ 'width' ] ) ) {
474 +
475 + $width = intval( $_wp_additional_image_sizes[ $size_name ][ 'width' ] );
476 +
477 + } else $width = get_option( $size_name . '_size_w' );
478 +
479 + if ( isset( $_wp_additional_image_sizes[ $size_name ][ 'height' ] ) ) {
480 +
481 + $height = intval( $_wp_additional_image_sizes[ $size_name ][ 'height' ] );
482 +
483 + } else $height = get_option( $size_name . '_size_h' );
484 +
485 + if ( isset( $_wp_additional_image_sizes[ $size_name ][ 'crop' ] ) ) {
486 +
487 + $crop = $_wp_additional_image_sizes[ $size_name ][ 'crop' ];
488 +
489 + } else $crop = get_option( $size_name . '_crop' );
490 +
491 + /*
492 + * Standardize to true, false, or non-empty array.
493 + */
494 + if ( empty( $crop ) ) { // 0, false, null, or empty array.
495 +
496 + $crop = false;
497 +
498 + } elseif ( ! is_array( $crop ) ) { // 1, or true.
499 +
500 + $crop = true;
501 + }
502 +
503 + /*
504 + * If the image size is cropped, then check the image metadata for a custom crop area.
505 + */
506 + if ( $crop && $attachment_id && is_numeric( $attachment_id ) ) {
507 +
508 + $new_crop = is_array( $crop ) ? $crop : array( 'center', 'center' );
509 +
510 + foreach ( array( 'attach_img_crop_x', 'attach_img_crop_y' ) as $crop_key => $md_key ) {
511 +
512 + $value = $this->p->post->get_options( $attachment_id, $md_key );
513 +
514 + if ( $value && $value !== 'none' ) { // Custom crop value found.
515 +
516 + $new_crop[ $crop_key ] = $value; // Adjust the crop value.
517 +
518 + $crop = $new_crop; // Update the crop array.
519 + }
520 + }
521 + }
522 +
523 + if ( $crop === array( 'center', 'center' ) ) {
524 +
525 + $crop = true;
526 + }
527 +
528 + $is_cropped = empty( $crop ) ? false : true;
529 +
530 + /*
531 + * Crop can be true, false, or an array.
532 + */
533 + $local_fifo[ $size_name ][ $attachment_id ] = array(
534 + 'size_name' => $size_name,
535 + 'attachment_id' => $attachment_id,
536 + 'width' => $width,
537 + 'height' => $height,
538 + 'crop' => $crop,
539 + 'is_cropped' => $is_cropped,
540 + 'dims_transl' => $width . 'x' . $height . ' ' . ( $is_cropped ? __( 'cropped', 'wpsso' ) : __( 'uncropped', 'wpsso' ) ),
541 + 'label_transl' => $this->get_image_size_label( $size_name ),
542 + 'opt_prefix' => $this->get_image_size_opt( $size_name ),
543 + );
544 +
545 + if ( $this->p->debug->enabled ) {
546 +
547 + $this->p->debug->log_size( 'local_fifo', $local_fifo );
548 + }
549 +
550 + return $local_fifo[ $size_name ][ $attachment_id ];
551 + }
552 +
553 + /*
554 + * Example $size_name = 'wpsso-opengraph' returns 'Open Graph' pre-translated.
555 + */
556 + public function get_image_size_label( $size_name ) {
557 +
558 + if ( ! empty( $this->cache_size_labels[ $size_name ] ) ) {
559 +
560 + return $this->cache_size_labels[ $size_name ];
561 +
562 + }
563 +
564 + return $size_name;
565 + }
566 +
567 + /*
568 + * Example $size_name = 'wpsso-opengraph' returns 'og'.
569 + */
570 + public function get_image_size_opt( $size_name ) {
571 +
572 + if ( isset( $this->cache_size_opts[ $size_name ] ) ) {
573 +
574 + return $this->cache_size_opts[ $size_name ];
575 +
576 + }
577 +
578 + return '';
579 + }
580 +
581 + /*
582 + * See WpssoMedia->get_all_images().
583 + * See WpssoMedia->get_mt_pid_images().
584 + * See WpssoUtil->clear_uniq_urls().
585 + * See WpssoSscShortcodeSchema->do_shortcode().
586 + */
587 + public function get_image_size_names( $mixed = null, $sanitize = true ) {
588 +
589 + $size_names = array_keys( $this->cache_size_labels );
590 +
591 + if ( null === $mixed ) {
592 +
593 + return $size_names;
594 +
595 + } elseif ( is_array( $mixed ) ) {
596 +
597 + return $sanitize ? array_intersect( $size_names, $mixed ) : $mixed; // Sanitize and return.
598 +
599 + } elseif ( is_string( $mixed ) ) {
600 +
601 + switch ( $mixed ) {
602 +
603 + case 'og':
604 + case 'opengraph':
605 +
606 + return $this->get_obj_image_size_names( $this->p->og );
607 +
608 + case 'pin':
609 + case 'pinterest':
610 +
611 + return $this->get_obj_image_size_names( $this->p->pinterest );
612 +
613 + case 'thumb':
614 + case 'thumbnail':
615 +
616 + return array( 'wpsso-' . $mixed );
617 +
618 + case 'tc':
619 + case 'twitter':
620 + case 'twittercard':
621 +
622 + return $this->get_obj_image_size_names( $this->p->tc );
623 +
624 + case 'schema':
625 +
626 + return $this->get_obj_image_size_names( $this->p->schema );
627 +
628 + default:
629 +
630 + return $sanitize ? array_intersect( $size_names, array( $mixed ) ) : array( $mixed );
631 + }
632 + }
633 +
634 + return array();
635 + }
636 +
637 + private function get_obj_image_size_names( object $obj ) {
638 +
639 + $names = array();
640 +
641 + if ( is_callable( array( $obj, 'filter_plugin_image_sizes' ) ) ) { // Just in case.
642 +
643 + $sizes = $obj->filter_plugin_image_sizes( array() );
644 +
645 + foreach ( $sizes as $opt_prefix => $arr ) {
646 +
647 + if ( ! empty( $arr[ 'name' ] ) ) {
648 +
649 + $names[] = 'wpsso-' . $arr[ 'name' ];
650 + }
651 + }
652 + }
653 +
654 + return $names;
655 + }
656 +
657 + /*
658 + * If this is an '_img_url' option, add the image dimensions and unset the '_img_id' option.
659 + */
660 + public function maybe_add_img_url_size( array &$opts, $opt_key ) {
661 +
662 + /*
663 + * Only process option keys with '_img_url' in their name.
664 + */
665 + if ( false === strpos( $opt_key, '_img_url' ) ) {
666 +
667 + return;
668 + }
669 +
670 + $this->add_image_url_size( $opts, $opt_key );
671 +
672 + $count = null;
673 + $img_id_key = str_replace( '_img_url', '_img_id', $opt_key, $count ); // Image ID key.
674 + $img_id_lib_key = str_replace( '_img_url', '_img_id_lib', $opt_key ); // Image ID media library prefix key.
675 +
676 + if ( $count ) { // Just in case.
677 +
678 + unset( $opts[ $img_id_key ] );
679 + unset( $opts[ $img_id_lib_key ] );
680 + }
681 + }
682 +
683 + /*
684 + * If this is a '_value' option, add the '_units' option.
685 + */
686 + public function maybe_add_md_key_units( array &$md_opts, $md_key ) {
687 +
688 + if ( false !== strpos( $md_key, '_value' ) ) {
689 +
690 + $count = null;
691 +
692 + $md_units_key = preg_replace( '/_value$/', '_units', $md_key, $limit = -1, $count );
693 +
694 + if ( $count ) { // Just in case.
695 +
696 + $md_opts[ $md_units_key ] = WpssoUtilUnits::get_mixed_text( $md_units_key );
697 +
698 + $md_opts[ $md_units_key . ':disabled' ] = true;
699 + }
700 + }
701 + }
702 +
703 + /*
704 + * See WpssoIntegEcomWooCommerce->filter_import_product_attributes().
705 + * See WpssoUtilCustomFields->filter_import_custom_fields().
706 + */
707 + public function maybe_renum_md_key( array &$md_opts, $md_key, array $values, $is_disabled = true ) {
708 +
709 + /*
710 + * Remove any old values from the options array.
711 + */
712 + $md_opts = SucomUtil::preg_grep_keys( '/^' . $md_key . '_[0-9]+$/', $md_opts, $invert = true );
713 +
714 + /*
715 + * Renumber the options starting from 0.
716 + */
717 + foreach ( $values as $num => $val ) {
718 +
719 + $md_num_key = $md_key . '_' . $num;
720 +
721 + $md_opts[ $md_num_key ] = $val;
722 +
723 + if ( $is_disabled ) {
724 +
725 + $md_opts[ $md_num_key . ':disabled' ] = true;
726 + }
727 +
728 + if ( $this->p->debug->enabled ) {
729 +
730 + $this->p->debug->log( 'option ' . $md_num_key . ' = ' . print_r( $md_opts[ $md_num_key ], true ) );
731 + }
732 + }
733 + }
734 +
735 + /*
736 + * $media_prefixes can be a single key name or an array of key names.
737 + *
738 + * Uses a reference variable to modify the $opts array directly.
739 + */
740 + public function add_image_url_size( array &$opts, $media_prefixes = 'og:image' ) {
741 +
742 + if ( $this->p->debug->enabled ) {
743 +
744 + $this->p->debug->mark();
745 + }
746 +
747 + if ( ! is_array( $media_prefixes ) ) {
748 +
749 + $media_prefixes = array( $media_prefixes );
750 + }
751 +
752 + foreach ( $media_prefixes as $media_pre ) {
753 +
754 + $opt_suffix = '';
755 +
756 + if ( preg_match( '/^(.*)(#.*)$/', $media_pre, $matches ) ) { // Language.
757 +
758 + $media_pre = $matches[ 1 ];
759 + $opt_suffix = $matches[ 2 ] . $opt_suffix;
760 + }
761 +
762 + if ( preg_match( '/^(.*)(_[0-9]+)$/', $media_pre, $matches ) ) { // Multi-option.
763 +
764 + $media_pre = $matches[ 1 ];
765 + $opt_suffix = $matches[ 2 ] . $opt_suffix;
766 + }
767 +
768 + $media_url = self::get_first_mt_media_url( $opts, $media_pre . $opt_suffix );
769 +
770 + if ( ! empty( $media_url ) ) {
771 +
772 + list(
773 + $opts[ $media_pre . ':width' . $opt_suffix ], // Example: place_img_url:width_1.
774 + $opts[ $media_pre . ':height' . $opt_suffix ], // Example: place_img_url:height_1.
775 + $image_type,
776 + $image_attr
777 + ) = $this->get_image_url_info( $media_url );
778 + }
779 + }
780 +
781 + return $opts;
782 + }
783 +
784 + /*
785 + * Always returns an array.
786 + *
787 + * Note that WebP is only supported since PHP v7.1.
788 + */
789 + public function get_image_url_info( $image_url ) {
790 +
791 + if ( $this->p->debug->enabled ) {
792 +
793 + $this->p->debug->mark();
794 + }
795 +
796 + static $local_fifo = array(); // Optimize and get image size for a given URL only once.
797 +
798 + $image_url = trim( $image_url ); // Just in case.
799 +
800 + if ( isset( $local_fifo[ $image_url ] ) ) {
801 +
802 + if ( $this->p->debug->enabled ) {
803 +
804 + $this->p->debug->log( 'exiting early: returning image info from static cache' );
805 + }
806 +
807 + return $local_fifo[ $image_url ];
808 + }
809 +
810 + /*
811 + * Maybe limit the number of array elements.
812 + */
813 + $local_fifo = SucomUtil::array_slice_fifo( $local_fifo, WPSSO_CACHE_ARRAY_FIFO_MAX );
814 +
815 + $def_image_info = array( WPSSO_UNDEF, WPSSO_UNDEF, '', '' );
816 +
817 + if ( empty( $image_url ) ) {
818 +
819 + if ( $this->p->debug->enabled ) {
820 +
821 + $this->p->debug->log( 'exiting early: image URL is empty' );
822 + }
823 +
824 + return $local_fifo[ $image_url ] = $def_image_info; // Stop here.
825 +
826 + } elseif ( false === filter_var( $image_url, FILTER_VALIDATE_URL ) ) {
827 +
828 + if ( $this->p->debug->enabled ) {
829 +
830 + $this->p->debug->log( 'exiting early: invalid image url "' . $image_url . '"' );
831 + }
832 +
833 + return $local_fifo[ $image_url ] = $def_image_info; // Stop here.
834 + }
835 +
836 + $cache_md5_pre = 'wpsso_i_'; // Transient prefix for image URL info.
837 + $cache_exp_secs = $this->get_cache_exp_secs( $cache_md5_pre );
838 +
839 + if ( $cache_exp_secs > 0 ) {
840 +
841 + /*
842 + * Note that cache_id is a unique identifier for the cached data and should be 45 characters or
843 + * less in length. If using a site transient, it should be 40 characters or less in length.
844 + */
845 + $cache_salt = __METHOD__ . '(url:' . $image_url . ')';
846 + $cache_id = $cache_md5_pre . md5( $cache_salt );
847 +
848 + if ( $this->p->debug->enabled ) {
849 +
850 + $this->p->debug->log( 'transient cache salt ' . $cache_salt );
851 + }
852 +
853 + $image_info = get_transient( $cache_id );
854 +
855 + if ( is_array( $image_info ) ) {
856 +
857 + if ( $this->p->debug->enabled ) {
858 +
859 + $this->p->debug->log( 'exiting early: returning image info from transient' );
860 + }
861 +
862 + /*
863 + * Optimize and save the transient cache value to local cache.
864 + */
865 + return $local_fifo[ $image_url ] = $image_info;
866 + }
867 +
868 + } elseif ( $this->p->debug->enabled ) {
869 +
870 + $this->p->debug->log( 'transient cache for image info is disabled' );
871 + }
872 +
873 + $mtime_start = microtime( $get_float = true );
874 +
875 + /*
876 + * Example $image_info:
877 + *
878 + * Array (
879 + * [0] => 2048
880 + * [1] => 2048
881 + * [2] => 3
882 + * [3] => width="2048" height="2048"
883 + * [bits] => 8
884 + * [mime] => image/png
885 + * )
886 + */
887 + $image_info = $this->p->cache->get_image_size( $image_url, $exp_secs = 300, $curl_opts = array(), $error_handler = 'wpsso_error_handler' );
888 + $mtime_total = microtime( $get_float = true ) - $mtime_start;
889 + $mtime_max = WPSSO_PHP_GETIMGSIZE_MAX_TIME;
890 +
891 + if ( $mtime_total > $mtime_max ) {
892 +
893 + $func_name = 'getimagesize';
894 + $current_url = isset( $_SERVER[ 'REQUEST_URI' ] ) ? $_SERVER[ 'REQUEST_URI' ] : '';
895 + $php_ref_url = sprintf( __( 'https://www.php.net/manual/en/function.%s.php', 'wpsso' ), $func_name );
896 + $php_ref_link = sprintf( '<a href="%1$s">%2$s</a>', $php_ref_url, $func_name );
897 + $error_pre = sprintf( __( '%s warning:', 'wpsso' ), __METHOD__ );
898 + $rec_max_msg = sprintf( __( 'longer than recommended max of %1$.3f secs', 'wpsso' ), $mtime_max );
899 + $notice_msg = sprintf( __( 'Slow PHP function detected - %1$s took %2$.3f secs for %3$s (%4$s).', 'wpsso' ),
900 + '<code>' . $func_name . '()</code>', $mtime_total, $image_url, $rec_max_msg );
901 +
902 + if ( $this->p->notice->is_admin_pre_notices() ) {
903 +
904 + $this->p->notice->warn( $notice_msg );
905 + }
906 +
907 + /*
908 + * Add extra information for the debug log.
909 + */
910 + if ( $php_ref_url ) $notice_msg .= ' ' . sprintf( __( 'See %s for more information.', 'wpsso' ), $php_ref_url );
911 + if ( $current_url ) $notice_msg .= ' ' . sprintf( __( 'Server request URI = %s', 'wpsso' ), $current_url );
912 +
913 + self::safe_error_log( $error_pre . ' ' . $notice_msg, $strip_html = true );
914 +
915 + if ( $this->p->debug->enabled ) {
916 +
917 + $this->p->debug->log( sprintf( 'slow PHP function detected - %1$s took %2$.3f secs for %3$s',
918 + $func_name . '()', $mtime_total, $image_url ) );
919 + }
920 + }
921 +
922 + if ( is_array( $image_info ) ) {
923 +
924 + if ( $this->p->debug->enabled ) {
925 +
926 + $this->p->debug->log( 'PHP getimagesize() image info: ' . $image_info[ 0 ] . 'x' . $image_info[ 1 ] );
927 + }
928 +
929 + } else {
930 +
931 + if ( $this->p->debug->enabled ) {
932 +
933 + $this->p->debug->log( 'PHP getimagesize() did not return an array - using defaults' );
934 + }
935 +
936 + $image_info = $def_image_info;
937 + }
938 +
939 + if ( $cache_exp_secs > 0 ) {
940 +
941 + set_transient( $cache_id, $image_info, $cache_exp_secs );
942 +
943 + if ( $this->p->debug->enabled ) {
944 +
945 + $this->p->debug->log( 'image info saved to transient cache for ' . $cache_exp_secs . ' seconds' );
946 + }
947 + }
948 +
949 + return $local_fifo[ $image_url ] = $image_info;
950 + }
951 +
952 + /*
953 + * Called by several class __construct() methods to hook their filters.
954 + */
955 + public function add_plugin_filters( $class, $filters, $prio = 10, $ext = '' ) {
956 +
957 + $this->add_plugin_callbacks( $type = 'filter', $class, $filters, $prio, $ext );
958 + }
959 +
960 + /*
961 + * Called by several class __construct() methods to hook their actions.
962 + */
963 + public function add_plugin_actions( $class, $actions, $prio = 10, $ext = '' ) {
964 +
965 + $this->add_plugin_callbacks( $type = 'action', $class, $actions, $prio, $ext );
966 + }
967 +
968 + /*
969 + * Called by WpssoUtil->add_plugin_filters().
970 + * Called by WpssoUtil->add_plugin_actions().
971 + *
972 + * $type = 'filter' | 'action'.
973 + */
974 + private function add_plugin_callbacks( $type, $class, $hook_list, $prio, $ext = '' ) {
975 +
976 + $ext = $ext === '' ? $this->p->id : $ext;
977 +
978 + foreach ( $hook_list as $name => $val ) {
979 +
980 + if ( ! is_string( $name ) ) {
981 +
982 + if ( $this->p->debug->enabled ) {
983 +
984 + $this->p->debug->log( $name . ' => ' . $val . ' ' . $type . ' skipped: filter name must be a string' );
985 + }
986 +
987 + continue;
988 + }
989 +
990 + /*
991 + * Example:
992 + *
993 + * 'json_data_https_schema_org_website' => 5
994 + */
995 + if ( is_int( $val ) ) {
996 +
997 + $arg_nums = $val;
998 + $hook_name = self::sanitize_hookname( $ext . '_' . $name );
999 + $method_name = self::sanitize_hookname( $type . '_' . $name );
1000 +
1001 + if ( is_callable( array( &$class, $method_name ) ) ) {
1002 +
1003 + call_user_func( 'add_' . $type, $hook_name, array( &$class, $method_name ), $prio, $arg_nums );
1004 +
1005 + if ( $this->p->debug->enabled ) {
1006 +
1007 + $this->p->debug->log( 'added ' . $method_name . ' method ' . $type, $class_seq = 3 );
1008 + }
1009 +
1010 + } else {
1011 +
1012 + $error_pre = sprintf( __( '%s warning:', 'wpsso' ), __METHOD__ );
1013 +
1014 + self::safe_error_log( $error_pre . ' ' . $method_name . ' method ' . $type . ' is not callable' );
1015 +
1016 + if ( $this->p->debug->enabled ) {
1017 +
1018 + $this->p->debug->log( $method_name . ' method ' . $type . ' is not callable' );
1019 + }
1020 + }
1021 +
1022 + /*
1023 + * Example:
1024 + *
1025 + * 'wpsso_filter_hook_name' => '__return_false'
1026 + */
1027 + } elseif ( is_string( $val ) ) {
1028 +
1029 + $arg_nums = 1;
1030 + $hook_name = self::sanitize_hookname( $ext . '_' . $name );
1031 + $function_name = self::sanitize_hookname( $val );
1032 +
1033 + if ( is_callable( $function_name ) ) {
1034 +
1035 + call_user_func( 'add_' . $type, $hook_name, $function_name, $prio, $arg_nums );
1036 +
1037 + if ( $this->p->debug->enabled ) {
1038 +
1039 + $this->p->debug->log( 'added ' . $function_name . ' function ' . $type . ' for ' . $hook_name, $class_seq = 3 );
1040 + }
1041 +
1042 + } else {
1043 +
1044 + $error_pre = sprintf( __( '%s warning:', 'wpsso' ), __METHOD__ );
1045 +
1046 + self::safe_error_log( $error_pre . ' ' . $method_name . ' method ' . $type . ' for ' . $hook_name . ' is not callable' );
1047 +
1048 + if ( $this->p->debug->enabled ) {
1049 +
1050 + $this->p->debug->log( $function_name . ' function ' . $type . ' for ' . $hook_name . ' is not callable' );
1051 + }
1052 + }
1053 +
1054 + /*
1055 + * Example:
1056 + *
1057 + * 'json_data_https_schema_org_article' => array(
1058 + * 'json_data_https_schema_org_article' => 5,
1059 + * 'json_data_https_schema_org_newsarticle' => 5,
1060 + * 'json_data_https_schema_org_techarticle' => 5,
1061 + * )
1062 + */
1063 + } elseif ( is_array( $val ) ) {
1064 +
1065 + $method_name = self::sanitize_hookname( $type . '_' . $name );
1066 +
1067 + foreach ( $val as $hook_name => $arg_nums ) {
1068 +
1069 + $hook_name = self::sanitize_hookname( $ext . '_' . $hook_name );
1070 +
1071 + if ( is_callable( array( &$class, $method_name ) ) ) {
1072 +
1073 + call_user_func( 'add_' . $type, $hook_name, array( &$class, $method_name ), $prio, $arg_nums );
1074 +
1075 + if ( $this->p->debug->enabled ) {
1076 +
1077 + $this->p->debug->log( 'added ' . $method_name . ' method ' . $type . ' for ' . $hook_name, $class_seq = 3 );
1078 + }
1079 +
1080 + } else {
1081 +
1082 + $error_pre = sprintf( __( '%s warning:', 'wpsso' ), __METHOD__ );
1083 +
1084 + self::safe_error_log( $error_pre . ' ' . $method_name . ' method ' . $type . ' for ' . $hook_name . ' is not callable' );
1085 +
1086 + if ( $this->p->debug->enabled ) {
1087 +
1088 + $this->p->debug->log( $method_name . ' method ' . $type . ' for ' . $hook_name . ' is not callable' );
1089 + }
1090 + }
1091 + }
1092 + }
1093 + }
1094 + }
1095 +
1096 + /*
1097 + * Add options using a key prefix array and post type names.
1098 + */
1099 + public function add_post_type_names( array &$opts, array $opt_prefixes, $args = null ) {
1100 +
1101 + foreach ( $opt_prefixes as $opt_prefix => $def_val ) {
1102 +
1103 + $type_names = SucomUtilWP::get_post_types( $output = 'names', $sort = false, $args );
1104 +
1105 + foreach ( $type_names as $opt_suffix ) {
1106 +
1107 + $opt_key = $opt_prefix . '_' . $opt_suffix;
1108 +
1109 + if ( ! isset( $opts[ $opt_key ] ) ) {
1110 +
1111 + $opts[ $opt_key ] = $def_val;
1112 + }
1113 + }
1114 + }
1115 +
1116 + return $opts;
1117 + }
1118 +
1119 + /*
1120 + * Add options using a key prefix array and post type archive names.
1121 + *
1122 + * Note that 'has_archive' = 1 will not match post types registered with a string in 'has_archive'.
1123 + *
1124 + * Use 'has_archive' = true to include the WooCommerce product archive page (ie. 'has_archive' = 'shop').
1125 + */
1126 + public function add_post_type_archive_names( array &$opts, array $opt_prefixes ) {
1127 +
1128 + $args = array( 'has_archive' => true );
1129 +
1130 + return $this->add_post_type_names( $opts, $opt_prefixes, $args );
1131 + }
1132 +
1133 + /*
1134 + * Add options using a key prefix array and taxonomy names.
1135 + */
1136 + public function add_taxonomy_names( array &$opts, array $opt_prefixes ) {
1137 +
1138 + foreach ( $opt_prefixes as $opt_prefix => $def_val ) {
1139 +
1140 + $tax_names = SucomUtilWP::get_taxonomies( $output = 'names' );
1141 +
1142 + foreach ( $tax_names as $opt_suffix ) {
1143 +
1144 + $opt_key = $opt_prefix . '_' . $opt_suffix;
1145 +
1146 + if ( ! isset( $opts[ $opt_key ] ) ) {
1147 +
1148 + $opts[ $opt_key ] = $def_val;
1149 + }
1150 + }
1151 + }
1152 +
1153 + return $opts;
1154 + }
1155 +
1156 + public function get_pkg_info( $get_ext = null, $get_key = null ) {
1157 +
1158 + static $local_cache = array();
1159 +
1160 + if ( empty( $local_cache ) ) {
1161 +
1162 + foreach ( $this->p->cf[ 'plugin' ] as $ext => $info ) {
1163 +
1164 + if ( empty( $info[ 'name' ] ) ) continue; // Just in case.
1165 +
1166 + $local_cache[ $ext ] = array();
1167 +
1168 + $ext_pdir = $this->p->check->pp( $ext, $li = false );
1169 + $ext_auth_id = $this->p->check->get_ext_auth_id( $ext );
1170 + $ext_pkg_std = ! $ext_pdir || ( isset( $GLOBALS[ $ext . '_dist' ] ) && 'std' === $GLOBALS[ $ext . '_dist' ] ) ? true : false;
1171 + $ext_pp = ! $ext_pkg_std && $ext_auth_id && $this->p->check->pp( $ext, $li = true, WPSSO_UNDEF ) === WPSSO_UNDEF ? true : false;
1172 + $ext_status = ( $ext_pp ? 'L' : ( $ext_pdir ? 'U' : 'S' ) ) . ( $ext_auth_id ? '*' : '' );
1173 + $ext_name_transl = _x( $info[ 'name' ], 'plugin name', 'wpsso' );
1174 + $pkg_pro_transl = _x( $this->p->cf[ 'packages' ][ 'pro' ], 'package name', 'wpsso' );
1175 + $pkg_std_transl = _x( $this->p->cf[ 'packages' ][ 'std' ], 'package name', 'wpsso' );
1176 +
1177 + $local_cache[ $ext ][ 'gen' ] = $info[ 'short' ] . ( isset( $info[ 'version' ] ) ? ' ' . $info[ 'version' ] . '/' . $ext_status : '' );
1178 + $local_cache[ $ext ][ 'name' ] = $ext_name_transl;
1179 + $local_cache[ $ext ][ 'name_pkg' ] = $this->get_pkg_name( $ext_name_transl, $ext_pp ? $pkg_pro_transl : $pkg_std_transl );
1180 + $local_cache[ $ext ][ 'name_pro' ] = $this->get_pkg_name( $ext_name_transl, $pkg_pro_transl );
1181 + $local_cache[ $ext ][ 'name_std' ] = $this->get_pkg_name( $ext_name_transl, $pkg_std_transl );
1182 + $local_cache[ $ext ][ 'pdir' ] = $ext_pdir;
1183 + $local_cache[ $ext ][ 'pkg' ] = $ext_pp ? $pkg_pro_transl : $pkg_std_transl;
1184 + $local_cache[ $ext ][ 'pp' ] = $ext_pp;
1185 + $local_cache[ $ext ][ 'short' ] = $info[ 'short' ];
1186 + $local_cache[ $ext ][ 'short_pkg' ] = $info[ 'short' ] . ' ' . $ext_pp ? $pkg_pro_transl : $pkg_std_transl;
1187 + $local_cache[ $ext ][ 'short_pro' ] = $info[ 'short' ] . ' ' . $pkg_pro_transl;
1188 + $local_cache[ $ext ][ 'short_std' ] = $info[ 'short' ] . ' ' . $pkg_std_transl;
1189 + $local_cache[ $ext ][ 'slug' ] = $info[ 'slug' ];
1190 + }
1191 + }
1192 +
1193 + return isset( $local_cache[ $get_ext ][ $get_key ] ) ? $local_cache[ $get_ext ][ $get_key ] : $local_cache;
1194 + }
1195 +
1196 + public function get_pkg_name( $name, $pkg ) {
1197 +
1198 + if ( false !== strpos( $name, $pkg ) ) {
1199 +
1200 + $name = preg_replace( '/^(.*) ' . $pkg . '( [\[\(].+[\)\]])?$/U', '$1$2', $name );
1201 + }
1202 +
1203 + return preg_replace( '/^(.*)( [\[\(].+[\)\]])?$/U', '$1 ' . $pkg . '$2', $name );
1204 + }
1205 +
1206 + public function get_form_cache( $name, $add_none = false, $excl_keys = array() ) {
1207 +
1208 + if ( $this->p->debug->enabled ) {
1209 +
1210 + $this->p->debug->log_args( array(
1211 + 'name' => $name,
1212 + 'add_none' => $add_none,
1213 + ) );
1214 + }
1215 +
1216 + static $local_cache = array();
1217 +
1218 + $filter_key = self::sanitize_key( $name );
1219 + $filter_name = self::sanitize_hookname( 'wpsso_form_cache_' . $filter_key );
1220 +
1221 + if ( ! isset( $local_cache[ $filter_key ] ) ) {
1222 +
1223 + $local_cache[ $filter_key ] = array(); // Initialize a default value.
1224 +
1225 + switch ( $filter_key ) {
1226 +
1227 + case 'contact_types': // Returns a multi-dimentional array.
1228 +
1229 + $this->get_form_cache( 'schema_types' );
1230 +
1231 + $local_cache[ $filter_key ] =& $local_cache[ 'schema_types' ][ 'thing' ][ 'intangible' ][ 'structured.value' ][ 'contact.point' ];
1232 +
1233 + break;
1234 +
1235 + case 'contact_types_select':
1236 +
1237 + $this->get_form_cache( 'contact_types' ); // Sets $local_cache[ 'contact_types' ].
1238 +
1239 + $local_cache[ $filter_key ] = $this->p->schema->get_schema_types_select( $local_cache[ 'contact_types' ] );
1240 +
1241 + break;
1242 +
1243 + case 'og_types':
1244 +
1245 + $local_cache[ $filter_key ] = $this->p->og->get_og_types();
1246 +
1247 + break;
1248 +
1249 + case 'og_types_select':
1250 +
1251 + $local_cache[ $filter_key ] = $this->p->og->get_og_types_select();
1252 +
1253 + break;
1254 +
1255 + case 'org_names':
1256 +
1257 + $local_cache[ $filter_key ] = array( 'site' => $this->p->cf[ 'form' ][ 'org_select' ][ 'site' ] );
1258 +
1259 + break;
1260 +
1261 + case 'org_types': // Returns a multi-dimentional array.
1262 +
1263 + $this->get_form_cache( 'schema_types' );
1264 +
1265 + $local_cache[ $filter_key ] =& $local_cache[ 'schema_types' ][ 'thing' ][ 'organization' ];
1266 +
1267 + break;
1268 +
1269 + case 'org_types_select':
1270 + case 'org_types_select_strict': // Use strict for Google.
1271 +
1272 + $this->get_form_cache( 'org_types' ); // Sets $local_cache[ 'org_types' ].
1273 +
1274 + $local_cache[ $filter_key ] = $this->p->schema->get_schema_types_select( $local_cache[ 'org_types' ] );
1275 +
1276 + /*
1277 + * Remove Schema types that are also places.
1278 + */
1279 + if ( 'org_types_select_strict' === $filter_key ) {
1280 +
1281 + foreach ( $this->get_form_cache( 'place_types_select' ) as $key => $val ) {
1282 +
1283 + unset ( $local_cache[ $filter_key ][ $key ] );
1284 + }
1285 + }
1286 +
1287 + break;
1288 +
1289 + case 'person_names':
1290 +
1291 + $local_cache[ $filter_key ] = WpssoUser::get_persons_names();
1292 +
1293 + break;
1294 +
1295 + case 'place_types': // Returns a multi-dimentional array.
1296 +
1297 + $this->get_form_cache( 'schema_types' );
1298 +
1299 + $local_cache[ $filter_key ] =& $local_cache[ 'schema_types' ][ 'thing' ][ 'place' ];
1300 +
1301 + break;
1302 +
1303 + case 'place_types_select':
1304 + case 'place_types_select_strict':
1305 +
1306 + $this->get_form_cache( 'place_types' );
1307 +
1308 + $local_cache[ $filter_key ] = $this->p->schema->get_schema_types_select( $local_cache[ 'place_types' ] );
1309 +
1310 + /*
1311 + * Remove Schema types that are also organizations.
1312 + */
1313 + if ( 'place_types_select_strict' === $filter_key ) {
1314 +
1315 + foreach ( $this->get_form_cache( 'org_types_select' ) as $key => $val ) {
1316 +
1317 + unset ( $local_cache[ $filter_key ][ $key ] );
1318 + }
1319 + }
1320 +
1321 + break;
1322 +
1323 + case 'schema_types': // Returns a multi-dimentional array.
1324 +
1325 + $local_cache[ $filter_key ] = $this->p->schema->get_schema_types( $flatten = false );
1326 +
1327 + break;
1328 +
1329 + case 'schema_types_select':
1330 +
1331 + $local_cache[ $filter_key ] = $this->p->schema->get_schema_types_select();
1332 +
1333 + break;
1334 + }
1335 +
1336 + if ( $this->p->debug->enabled ) {
1337 +
1338 + $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1339 + }
1340 +
1341 + $local_cache[ $filter_key ] = apply_filters( $filter_name, $local_cache[ $filter_key ] );
1342 +
1343 + } elseif ( $this->p->debug->enabled ) {
1344 +
1345 + $this->p->debug->log( 'using existing form cache entry for ' . $filter_key );
1346 + }
1347 +
1348 + if ( isset( $local_cache[ $filter_key ][ 'none' ] ) ) { // Just in case.
1349 +
1350 + unset( $local_cache[ $filter_key ][ 'none' ] );
1351 + }
1352 +
1353 + /*
1354 + * Maybe modify the returned array.
1355 + */
1356 + if ( empty( $add_none ) && empty( $excl_keys ) ) {
1357 +
1358 + if ( $this->p->debug->enabled ) {
1359 +
1360 + $this->p->debug->log( 'returning cached array' );
1361 + }
1362 +
1363 + return $local_cache[ $filter_key ];
1364 + }
1365 +
1366 + $ret = $local_cache[ $filter_key ];
1367 +
1368 + if ( $add_none ) {
1369 +
1370 + if ( $this->p->debug->enabled ) {
1371 +
1372 + $this->p->debug->log( 'adding "none" as first element' );
1373 + }
1374 +
1375 + $ret = array( 'none' => '[None]' ) + $ret;
1376 + }
1377 +
1378 + if ( ! empty( $excl_keys ) ) {
1379 +
1380 + foreach ( $excl_keys as $key ) {
1381 +
1382 + unset( $ret[ $key ] );
1383 + }
1384 + }
1385 +
1386 + if ( $this->p->debug->enabled ) {
1387 +
1388 + $this->p->debug->log_arr( 'returning modified array', $ret );
1389 + }
1390 +
1391 + return $ret;
1392 + }
1393 +
1394 + /*
1395 + * Returns an associative array, with 'none' as the first element.
1396 + */
1397 + public function get_article_sections() {
1398 +
1399 + if ( $this->p->debug->enabled ) {
1400 +
1401 + $this->p->debug->mark();
1402 + }
1403 +
1404 + $sections = array();
1405 +
1406 + if ( ! defined( 'WPSSO_ARTICLE_SECTIONS_LIST' ) || empty( WPSSO_ARTICLE_SECTIONS_LIST ) ) {
1407 +
1408 + return $sections;
1409 + }
1410 +
1411 + $text_list_file = self::get_file_path_locale( WPSSO_ARTICLE_SECTIONS_LIST );
1412 + $cache_md5_pre = 'wpsso_f_';
1413 + $cache_exp_secs = MONTH_IN_SECONDS;
1414 + $cache_salt = __METHOD__ . '(' . $text_list_file . ')';
1415 + $cache_id = $cache_md5_pre . md5( $cache_salt );
1416 +
1417 + if ( $cache_exp_secs > 0 ) {
1418 +
1419 +
1420 + if ( $this->p->debug->enabled ) {
1421 +
1422 + $this->p->debug->log( 'transient cache salt ' . $cache_salt );
1423 + }
1424 +
1425 + $sections = get_transient( $cache_id );
1426 +
1427 + if ( is_array( $sections ) ) {
1428 +
1429 + if ( $this->p->debug->enabled ) {
1430 +
1431 + $this->p->debug->log( 'article sections retrieved from transient ' . $cache_id );
1432 + }
1433 +
1434 + return $sections;
1435 + }
1436 + }
1437 +
1438 + $raw_sections = file( $text_list_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ); // Returns false on error.
1439 +
1440 + if ( ! is_array( $raw_sections ) ) {
1441 +
1442 + $error_pre = sprintf( '%s error:', __METHOD__ );
1443 + $notice_msg = sprintf( __( 'Error reading the %s file for the article sections list.', 'wpsso' ), $text_list_file );
1444 +
1445 + if ( $this->p->notice->is_admin_pre_notices() ) {
1446 +
1447 + $this->p->notice->err( $notice_msg );
1448 + }
1449 +
1450 + self::safe_error_log( $error_pre . ' ' . $notice_msg );
1451 +
1452 + if ( $this->p->debug->enabled ) {
1453 +
1454 + $this->p->debug->log( 'error reading %s article sections list file' );
1455 + }
1456 +
1457 + return array();
1458 + }
1459 +
1460 + $sections = array();
1461 +
1462 + foreach ( $raw_sections as $num => $section_name ) {
1463 +
1464 + if ( 0 === strpos( $section_name, '#' ) ) { // Skip comment lines.
1465 +
1466 + continue;
1467 + }
1468 +
1469 + $sections[ $section_name ] = $section_name;
1470 +
1471 + unset( $sections[ $num ] ); // Save memory and unset as we go.
1472 + }
1473 +
1474 + unset( $raw_sections );
1475 +
1476 + $sections = apply_filters( 'wpsso_article_sections', $sections );
1477 +
1478 + asort( $sections, SORT_NATURAL );
1479 +
1480 + $sections = array( 'none' => '[None]' ) + $sections; // After sorting the array, put 'none' first.
1481 +
1482 + if ( $cache_exp_secs > 0 ) {
1483 +
1484 + set_transient( $cache_id, $sections, $cache_exp_secs );
1485 +
1486 + if ( $this->p->debug->enabled ) {
1487 +
1488 + $this->p->debug->log( 'article sections saved to transient cache for ' . $cache_exp_secs . ' seconds' );
1489 + }
1490 + }
1491 +
1492 + return $sections;
1493 + }
1494 +
1495 + /*
1496 + * Format the product category list from https://www.google.com/basepages/producttype/taxonomy-with-ids.en-US.txt.
1497 + *
1498 + * Returns an associative array, with 'none' as the first element.
1499 + */
1500 + public function get_google_product_categories() {
1501 +
1502 + if ( $this->p->debug->enabled ) {
1503 +
1504 + $this->p->debug->mark();
1505 + }
1506 +
1507 + $categories = array();
1508 +
1509 + if ( ! defined( 'WPSSO_PRODUCT_CATEGORIES_LIST' ) || empty( WPSSO_PRODUCT_CATEGORIES_LIST ) ) {
1510 +
1511 + return $categories;
1512 + }
1513 +
1514 + $text_list_file = self::get_file_path_locale( WPSSO_PRODUCT_CATEGORIES_LIST );
1515 + $cache_md5_pre = 'wpsso_f_';
1516 + $cache_exp_secs = MONTH_IN_SECONDS;
1517 + $cache_salt = __METHOD__ . '(' . $text_list_file . ')';
1518 + $cache_id = $cache_md5_pre . md5( $cache_salt );
1519 +
1520 + if ( $cache_exp_secs > 0 ) {
1521 +
1522 + if ( $this->p->debug->enabled ) {
1523 +
1524 + $this->p->debug->log( 'transient cache salt ' . $cache_salt );
1525 + }
1526 +
1527 + $categories = get_transient( $cache_id );
1528 +
1529 + if ( is_array( $categories ) ) {
1530 +
1531 + if ( $this->p->debug->enabled ) {
1532 +
1533 + $this->p->debug->log( 'product categories retrieved from transient ' . $cache_id );
1534 + }
1535 +
1536 + return $categories;
1537 + }
1538 + }
1539 +
1540 + $raw_categories = file( $text_list_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ); // Returns false on error.
1541 +
1542 + if ( ! is_array( $raw_categories ) ) {
1543 +
1544 + $error_pre = sprintf( '%s error:', __METHOD__ );
1545 + $notice_msg = sprintf( __( 'Error reading the %s file for the product categories list.', 'wpsso' ), $text_list_file );
1546 +
1547 + if ( $this->p->notice->is_admin_pre_notices() ) {
1548 +
1549 + $this->p->notice->err( $notice_msg );
1550 + }
1551 +
1552 + self::safe_error_log( $error_pre . ' ' . $notice_msg );
1553 +
1554 + if ( $this->p->debug->enabled ) {
1555 +
1556 + $this->p->debug->log( 'error reading %s product categories list file' );
1557 + }
1558 +
1559 + return array();
1560 + }
1561 +
1562 + $categories = array();
1563 +
1564 + foreach ( $raw_categories as $num => $category_id_name ) {
1565 +
1566 + if ( 0 === strpos( $category_id_name, '#' ) ) { // Skip comment lines.
1567 +
1568 + continue;
1569 + }
1570 +
1571 + if ( preg_match( '/^([0-9]+) - (.*)$/', $category_id_name, $match ) ) {
1572 +
1573 + $categories[ $match[ 1 ] ] = $match[ 2 ];
1574 + }
1575 +
1576 + unset( $raw_categories[ $num ] ); // Save memory and unset as we go.
1577 + }
1578 +
1579 + unset( $raw_categories );
1580 +
1581 + $categories = apply_filters( 'wpsso_google_product_categories', $categories );
1582 +
1583 + asort( $categories, SORT_NATURAL );
1584 +
1585 + $categories = array( 'none' => '[None]' ) + $categories; // After sorting the array, put 'none' first.
1586 +
1587 + if ( $cache_exp_secs > 0 ) {
1588 +
1589 + set_transient( $cache_id, $categories, $cache_exp_secs );
1590 +
1591 + if ( $this->p->debug->enabled ) {
1592 +
1593 + $this->p->debug->log( 'product categories saved to transient cache for ' . $cache_exp_secs . ' seconds' );
1594 + }
1595 + }
1596 +
1597 + return $categories;
1598 + }
1599 +
1600 + /*
1601 + * See WpssoMedia->add_og_video_from_url().
1602 + */
1603 + public function is_html_head_meta_url_cached( $url, $cache_exp_secs = 0 ) {
1604 +
1605 + if ( empty( $url ) ) return false; // Just in case.
1606 +
1607 + if ( false !== stripos( $url, '<html' ) ) return false; // Request contains html.
1608 +
1609 + if ( false === filter_var( $url, FILTER_VALIDATE_URL ) ) return false; // Request is an invalid url.
1610 +
1611 + $cache_format = 'url';
1612 + $cache_type = 'file';
1613 + $cache_file_ext = '.html';
1614 +
1615 + $cache_url = $this->p->cache->is_cached( $url, $cache_format, $cache_type, $cache_exp_secs, $cache_file_ext );
1616 +
1617 + return $cache_url;
1618 + }
1619 +
1620 + /*
1621 + * Query argument examples:
1622 + *
1623 + * /html/head/link|/html/head/meta
1624 + * /html/head/link[@rel="canonical"]
1625 + * /html/head/meta[starts-with(@property, "og:video:")]
1626 + *
1627 + * See WpssoMedia->add_og_video_from_url().
1628 + * See WpssoPost->check_post_head().
1629 + * See WpssoProMediaSoundcloud->filter_video_details().
1630 + * See WpssoProMediaWistia->filter_video_details().
1631 + */
1632 + public function get_html_head_meta( $request, $query = '/html/head/meta', $libxml_errors = false, $cache_exp_secs = 0, $curl_opts = array() ) {
1633 +
1634 + if ( $this->p->debug->enabled ) {
1635 +
1636 + $this->p->debug->mark();
1637 + }
1638 +
1639 + $is_admin = is_admin(); // Optimize and call once.
1640 +
1641 + if ( ! function_exists( 'mb_convert_encoding' ) ) {
1642 +
1643 + $this->php_function_missing( 'mb_convert_encoding()', __METHOD__ );
1644 +
1645 + return false;
1646 + }
1647 +
1648 + if ( ! class_exists( 'DOMDocument' ) ) {
1649 +
1650 + $this->php_class_missing( 'DOMDocument', __METHOD__ );
1651 +
1652 + return false;
1653 + }
1654 +
1655 + if ( empty( $request ) ) { // Just in case.
1656 +
1657 + if ( $this->p->debug->enabled ) {
1658 +
1659 + $this->p->debug->log( 'exiting early: the request argument is empty' );
1660 + }
1661 +
1662 + return false;
1663 + }
1664 +
1665 + if ( empty( $query ) ) { // Just in case.
1666 +
1667 + if ( $this->p->debug->enabled ) {
1668 +
1669 + $this->p->debug->log( 'exiting early: the query argument is empty' );
1670 + }
1671 +
1672 + return false;
1673 + }
1674 +
1675 + if ( false !== stripos( $request, '<html' ) ) { // Request contains html.
1676 +
1677 + if ( $this->p->debug->enabled ) {
1678 +
1679 + $this->p->debug->log( 'using the html submitted as the request argument' );
1680 + }
1681 +
1682 + $html = $request;
1683 +
1684 + $request = false; // Just in case.
1685 +
1686 + } elseif ( false === filter_var( $request, FILTER_VALIDATE_URL ) ) { // Request is an invalid url.
1687 +
1688 + if ( $this->p->debug->enabled ) {
1689 +
1690 + $this->p->debug->log( 'exiting early: request argument is not html or a valid url' );
1691 + }
1692 +
1693 + if ( $is_admin ) {
1694 +
1695 + $this->p->notice->err( sprintf( __( 'The <code>%1$s</code> request argument is not HTML or a valid URL.',
1696 + 'wpsso' ), __METHOD__ . '()' ) );
1697 + }
1698 +
1699 + return false;
1700 +
1701 + } else {
1702 +
1703 + if ( $this->p->debug->enabled ) {
1704 +
1705 + $this->p->debug->log( 'getting HTML for ' . $request );
1706 + }
1707 +
1708 + $cache_format = 'raw';
1709 + $cache_type = 'file';
1710 + $cache_file_ext = '.html';
1711 +
1712 + $html = $this->p->cache->get( $request, $cache_format, $cache_type, $cache_exp_secs, $cache_file_ext, $curl_opts );
1713 +
1714 + if ( empty( $html ) ) {
1715 +
1716 + if ( $this->p->debug->enabled ) {
1717 +
1718 + $this->p->debug->log( 'exiting early: error getting HTML from ' . $request );
1719 + }
1720 +
1721 + if ( $is_admin ) {
1722 +
1723 + $this->p->notice->err( sprintf( __( 'Error getting HTML from <a href="%1$s">%1$s</a>.', 'wpsso' ), $request ) );
1724 + }
1725 +
1726 + return false;
1727 + }
1728 + }
1729 +
1730 + if ( function_exists( 'mb_convert_encoding' ) ) { // Just in case.
1731 +
1732 + /*
1733 + * Use call_user_func_array() to pass $html by reference.
1734 + */
1735 + call_user_func_array( 'mb_convert_encoding', array( &$html, $to_encoding = 'HTML-ENTITIES', $from_encoding = 'UTF-8' ) );
1736 + }
1737 +
1738 + /*
1739 + * U = Invert greediness of quantifiers, so they are NOT greedy by default, but become greedy if followed by ?.
1740 + * m = The "^" and "$" constructs match newlines and the complete subject string.
1741 + * s = A dot metacharacter in the pattern matches all characters, including newlines.
1742 + */
1743 + $html = preg_replace( '/<!--.*-->/Ums', '', $html );
1744 +
1745 + if ( empty( $html ) ) { // Returned html for url is empty.
1746 +
1747 + if ( $request ) {
1748 +
1749 + if ( $this->p->debug->enabled ) {
1750 +
1751 + $this->p->debug->log( 'exiting early: html for ' . $request . ' is empty' );
1752 + }
1753 +
1754 + if ( $is_admin ) {
1755 +
1756 + $this->p->notice->err( sprintf( __( 'Webpage retrieved from <a href="%1$s">%1$s</a> is empty.', 'wpsso' ), $request ) );
1757 + }
1758 +
1759 + } elseif ( $this->p->debug->enabled ) {
1760 +
1761 + $this->p->debug->log( 'exiting early: submitted html is empty' );
1762 + }
1763 +
1764 + return false;
1765 +
1766 + }
1767 +
1768 + $doc = new DOMDocument(); // Since PHP v4.1.
1769 +
1770 + if ( function_exists( 'libxml_use_internal_errors' ) ) { // Since PHP v5.1.
1771 +
1772 + $libxml_prev_state = libxml_use_internal_errors( true ); // Enable user error handling.
1773 +
1774 + if ( ! $doc->loadHTML( $html ) ) { // loadXML() is too strict for most webpages.
1775 +
1776 + if ( $this->p->debug->enabled ) {
1777 +
1778 + $this->p->debug->log( 'loadHTML returned error(s)' );
1779 + }
1780 +
1781 + /*
1782 + * libXMLError {
1783 + * public int $level;
1784 + * public int $code;
1785 + * public int $column;
1786 + * public string $message;
1787 + * public string $file;
1788 + * public int $line;
1789 + * }
1790 + */
1791 + foreach ( libxml_get_errors() as $error ) {
1792 +
1793 + if ( $this->p->debug->enabled ) {
1794 +
1795 + $this->p->debug->log( 'libxml error: ' . $error->message );
1796 + }
1797 +
1798 + if ( $libxml_errors ) {
1799 +
1800 + if ( $is_admin ) {
1801 +
1802 + $this->p->notice->err( 'PHP libXML error: ' . $error->message );
1803 + }
1804 + }
1805 + }
1806 +
1807 + libxml_clear_errors(); // Clear any HTML parsing errors.
1808 +
1809 + } elseif ( $this->p->debug->enabled ) {
1810 +
1811 + $this->p->debug->log( 'loadHTML was successful' );
1812 + }
1813 +
1814 + libxml_use_internal_errors( $libxml_prev_state ); // Restore previous error handling.
1815 +
1816 + } else @$doc->loadHTML( $html ); // Load HTML and ignore errors.
1817 +
1818 + unset( $html ); // No longer required.
1819 +
1820 + $xpath = new DOMXPath( $doc );
1821 + $metas = $xpath->query( $query );
1822 + $mt_ret = array();
1823 +
1824 + foreach ( $metas as $m ) {
1825 +
1826 + $m_atts = array(); // Put all attributes in a single array.
1827 +
1828 + foreach ( $m->attributes as $a ) {
1829 +
1830 + $m_atts[ $a->name ] = $a->value;
1831 + }
1832 +
1833 + if ( isset( $m->textContent ) ) {
1834 +
1835 + $m_atts[ 'textContent' ] = $m->textContent;
1836 + }
1837 +
1838 + $mt_ret[ $m->tagName ][] = $m_atts;
1839 + }
1840 +
1841 + if ( $this->p->debug->enabled ) {
1842 +
1843 + if ( empty( $mt_ret ) ) { // Empty array.
1844 +
1845 + if ( false === $request ) { // $request argument is html
1846 +
1847 + $this->p->debug->log( 'meta tags found in submitted html' );
1848 +
1849 + } else $this->p->debug->log( 'no meta tags found in ' . $request );
1850 +
1851 + } else $this->p->debug->log( 'returning array of ' . count( $mt_ret ) . ' meta tags' );
1852 + }
1853 +
1854 + return $mt_ret;
1855 + }
1856 +
1857 + public function php_class_missing( $class, $method = null ) {
1858 +
1859 + if ( $this->p->debug->enabled ) {
1860 +
1861 + $this->p->debug->log( $class . ' PHP class is missing' );
1862 + }
1863 +
1864 + if ( is_admin() ) {
1865 +
1866 + // translators: %1$s is the class name.
1867 + $this->p->notice->err( sprintf( __( 'The PHP %s class is missing &ndash; contact your hosting provider to have the missing class installed.', 'wpsso' ), '<code>' . $class . '</code>' ) );
1868 + }
1869 + }
1870 +
1871 + public function php_function_missing( $function, $method = null ) {
1872 +
1873 + if ( $this->p->debug->enabled ) {
1874 +
1875 + $this->p->debug->log( $function . ' PHP function is missing' );
1876 + }
1877 +
1878 + if ( is_admin() ) {
1879 +
1880 + // translators: %1$s is the function name.
1881 + $this->p->notice->err( sprintf( __( 'The PHP %s function is missing &ndash; contact your hosting provider to have the missing function installed.', 'wpsso' ), '<code>' . $function . '</code>' ) );
1882 + }
1883 + }
1884 +
1885 + /*
1886 + * Used by WpssoHead->show_head() and WpssoAdminHead->__construct().
1887 + */
1888 + public function log_is_functions() {
1889 +
1890 + if ( ! $this->p->debug->enabled ) { // Nothing to do.
1891 +
1892 + return;
1893 + }
1894 +
1895 + $function_info = $this->get_is_functions();
1896 +
1897 + foreach ( $function_info as $function => $info ) {
1898 +
1899 + $this->p->debug->log( $info[ 0 ] );
1900 + }
1901 + }
1902 +
1903 + public function get_is_functions() {
1904 +
1905 + $function_info = array();
1906 +
1907 + foreach ( apply_filters( 'wpsso_is_functions', $this->is_functions ) as $function ) {
1908 +
1909 + if ( is_callable( $function ) ) {
1910 +
1911 + $mtime_start = microtime( $get_float = true );
1912 + $ret_val = $function();
1913 + $mtime_total = microtime( $get_float = true ) - $mtime_start;
1914 +
1915 + $function_info[ $function ] = array(
1916 + sprintf( '%-40s (%f secs)', $function . '() = ' . ( $ret_val ? 'TRUE' : 'false' ), $mtime_total ),
1917 + $ret_val, $mtime_total,
1918 + );
1919 +
1920 + } else $function_info[ $function ] = array( $function . '() not found', null, 0 );
1921 + }
1922 +
1923 + return $function_info;
1924 + }
1925 +
1926 + /*
1927 + * Some themes and plugins have been known to hook the WordPress 'get_shortlink' filter and return an empty URL to
1928 + * disable the WordPress shortlink meta tag. This breaks the WordPress wp_get_shortlink() function and is a
1929 + * violation of the WordPress theme guidelines.
1930 + *
1931 + * This method calls the WordPress wp_get_shortlink() function, and if an empty string is returned, calls an
1932 + * unfiltered version of the same function.
1933 + *
1934 + * $context = 'blog', 'post' (default), 'media', or 'query'
1935 + */
1936 + public function get_shortlink( $mod, $context = 'post', $allow_slugs = true ) {
1937 +
1938 + /*
1939 + * The $mod array argument is preferred but not required.
1940 + */
1941 + if ( ! is_array( $mod ) ) {
1942 +
1943 + if ( $this->p->debug->enabled ) {
1944 +
1945 + $this->p->debug->log( 'optional call to WpssoPage->get_mod()' );
1946 + }
1947 +
1948 + $mod = $this->p->page->get_mod( $mod );
1949 + }
1950 +
1951 + $shortlink = '';
1952 +
1953 + if ( $mod[ 'is_post' ] && $mod[ 'id' ] ) { // Just in case.
1954 +
1955 + $shortlink = wp_get_shortlink( $mod[ 'id' ], $context, $allow_slugs );
1956 +
1957 + if ( empty( $shortlink ) || ! is_string( $shortlink) || false === filter_var( $shortlink, FILTER_VALIDATE_URL ) ) {
1958 +
1959 + $shortlink = SucomUtilWP::raw_wp_get_shortlink( $mod[ 'id' ], $context, $allow_slugs );
1960 + }
1961 + }
1962 +
1963 + return $shortlink;
1964 + }
1965 +
1966 + /*
1967 + * Shorten URL using the selected shortening service.
1968 + */
1969 + public function shorten_url( $long_url, $mod = false ) {
1970 +
1971 + $shortener = isset( $this->p->options[ 'plugin_shortener' ] ) ? $this->p->options[ 'plugin_shortener' ] : 'none';
1972 +
1973 + if ( ! empty( $shortener ) && 'none' !== $shortener ) {
1974 +
1975 + $short_url = apply_filters( 'wpsso_get_short_url', $long_url, $shortener, $mod );
1976 +
1977 + if ( false !== filter_var( $short_url, FILTER_VALIDATE_URL ) ) { // Make sure the returned URL is valid.
1978 +
1979 + return $short_url;
1980 + }
1981 + }
1982 +
1983 + return $long_url;
1984 + }
1985 +
1986 + public function json_format( array $data, $options = 0, $depth = 32 ) {
1987 +
1988 + if ( 0 === $options ) {
1989 +
1990 + $options = JSON_UNESCAPED_SLASHES;
1991 + }
1992 +
1993 + if ( $this->is_json_pretty() ) {
1994 +
1995 + $options = $options|JSON_PRETTY_PRINT;
1996 + }
1997 +
1998 + return wp_json_encode( $data, $options, $depth );
1999 + }
2000 +
2001 + /*
2002 + * Get the sitemaps alternates array.
2003 + *
2004 + * Example:
2005 + *
2006 + * $alternates = array(
2007 + * array(
2008 + * 'href' => 'https://example.com/en/page-1/',
2009 + * 'hreflang' => 'en_US',
2010 + * ),
2011 + * array(
2012 + * 'href' => 'https://example.com/fr/page-1/',
2013 + * 'hreflang' => 'fr_FR',
2014 + * ),
2015 + * array(
2016 + * 'href' => 'https://example.com/es/page-1/',
2017 + * 'hreflang' => 'es_ES',
2018 + * ),
2019 + * );
2020 + */
2021 + public function get_sitemaps_alternates( array $mod ) {
2022 +
2023 + $alternates = apply_filters( 'wpsso_sitemaps_alternates', array(), $mod );
2024 +
2025 + if ( $this->p->debug->enabled ) {
2026 +
2027 + $this->p->debug->log_arr( 'alternates', $alternates );
2028 + }
2029 +
2030 + return $alternates;
2031 + }
2032 +
2033 + /*
2034 + * See https://developers.google.com/search/docs/crawling-indexing/sitemaps/image-sitemaps.
2035 + * See WpssoWpsmSitemapsFilters->wp_sitemaps_posts_entry().
2036 + * See WpssoWpsmSitemapsFilters->wp_sitemaps_taxonomies_entry().
2037 + * See WpssoWpsmSitemapsFilters->wp_sitemaps_users_entry().
2038 + */
2039 + public function get_sitemaps_images( array $mod, $num = 1 ) {
2040 +
2041 + $images = array();
2042 + $size_names = 'schema';
2043 + $md_pre = array( 'schema', 'og' );
2044 + $mt_images = $this->p->media->get_all_images( $num, $size_names, $mod, $md_pre );
2045 +
2046 + foreach ( $mt_images as $mt_single_image ) {
2047 +
2048 + if ( $image_url = SucomUtil::get_first_og_image_url( $mt_single_image ) ) {
2049 +
2050 + $images[] = array(
2051 + 'image:loc' => $image_url,
2052 + );
2053 + }
2054 + }
2055 +
2056 + return $images;
2057 + }
2058 +
2059 + /*
2060 + * See https://developers.google.com/search/docs/crawling-indexing/sitemaps/news-sitemap.
2061 + * See WpssoWpsmSitemapsFilters->wp_sitemaps_posts_entry().
2062 + */
2063 + public function get_sitemaps_news( array $mod, $news_pub_time, $news_pub_name ) {
2064 +
2065 + $news = array();
2066 +
2067 + $is_recent = $mod[ 'post_timestamp' ] > time() - $news_pub_time ? true : false;
2068 +
2069 + if ( $is_recent ) {
2070 +
2071 + $schema_lang = $this->p->schema->get_schema_lang( $mod, $trim_lang = true );
2072 + $schema_title = $this->p->page->get_title( $mod, $md_key = 'schema_title', $max_len = 'schema_title' );
2073 +
2074 + $news[] = array(
2075 + 'news:publication' => array(
2076 + array(
2077 + 'news:name' => $news_pub_name,
2078 + 'news:language' => $schema_lang,
2079 + ),
2080 + ),
2081 + 'news:title' => $schema_title,
2082 + 'news:publication_date' => $mod[ 'post_time' ],
2083 + );
2084 + }
2085 +
2086 + return $news;
2087 + }
2088 +
2089 + /*
2090 + * See https://developers.google.com/search/docs/crawling-indexing/sitemaps/video-sitemaps.
2091 + * See WpssoWpsmSitemapsFilters->wp_sitemaps_posts_entry().
2092 + */
2093 + public function get_sitemaps_videos( array $mod, $num = 1 ) {
2094 +
2095 + $videos = array();
2096 + $md_pre = array( 'schema', 'og' );
2097 + $max_nums = $this->p->util->get_max_nums( $mod );
2098 + $mt_videos = $this->p->media->get_all_videos( $max_nums[ 'og_vid_max' ], $mod, $md_pre, $force_prev = true );
2099 + $added_urls = array();
2100 +
2101 + foreach ( $mt_videos as $mt_single_video ) {
2102 +
2103 + $video_url = SucomUtil::get_first_mt_media_url( $mt_single_video, $media_pre = 'og:video',
2104 + $mt_suffixes = array( ':embed_url', ':stream_url' ) );
2105 +
2106 + if ( $video_url ) {
2107 +
2108 + if ( isset( $added_urls[ $video_url ] ) ) { // Prevent duplicates from 'text/html' and 'video/mp4' videos.
2109 +
2110 + continue;
2111 + }
2112 +
2113 + $added_urls[ $video_url ] = true;
2114 +
2115 + $image_url = SucomUtil::get_first_og_image_url( $mt_single_video );
2116 +
2117 + $video = array(
2118 + 'video:thumbnail_loc' => $image_url,
2119 + );
2120 +
2121 + WpssoSchema::add_data_itemprop_from_assoc( $video, $mt_single_video, array(
2122 + 'video:player_loc' => 'og:video:embed_url',
2123 + 'video:content_loc' => 'og:video:stream_url',
2124 + 'video:title' => 'og:video:title',
2125 + 'video:description' => 'og:video:description',
2126 + 'video:duration' => 'og:video:duration',
2127 + 'video:publication_date' => 'og:video:published_date',
2128 + 'video:family_friendly' => 'og:video:family_friendly',
2129 + 'video:restriction_allow' => 'og:video:regions_allowed',
2130 + ) );
2131 +
2132 + if ( isset( $video[ 'video:duration' ] ) ) {
2133 +
2134 + $video[ 'video:duration' ] = SucomUtil::maybe_iso8601_to_seconds( $video[ 'video:duration' ] );
2135 + }
2136 +
2137 + if ( isset( $video[ 'video:restriction_allow' ] ) ) {
2138 +
2139 + if ( is_array( $video[ 'video:restriction_allow' ] ) ) {
2140 +
2141 + $video[ 'video:restriction_allow' ] = implode( ' ', $video[ 'video:restriction_allow' ] );
2142 + }
2143 + }
2144 +
2145 + $videos[] = $video;
2146 + }
2147 + }
2148 +
2149 + return $videos;
2150 + }
2151 +
2152 + /*
2153 + * Shorten the canonical URL using the selected shortening service.
2154 + *
2155 + * $mod = true | false | post_id | $mod array ($mod array is preferred but not required).
2156 + *
2157 + * $md_key = 'canonical_url' | '' (empty value ignores custom URL).
2158 + */
2159 + public function get_canonical_short_url( $mod = false, $add_page = false, $md_key = 'canonical_url' ) {
2160 +
2161 + $url = $this->get_canonical_url( $mod, $add_page, $md_key );
2162 +
2163 + return $this->shorten_url( $url, $mod );
2164 + }
2165 +
2166 + /*
2167 + * $mod = true | false | post_id | $mod array ($mod array is preferred but not required).
2168 + *
2169 + * $md_key = 'canonical_url' | '' (empty value ignores custom URL)
2170 + */
2171 + public function get_canonical_url( $mod = false, $add_page = false, $md_key = 'canonical_url' ) {
2172 +
2173 + if ( $this->p->debug->enabled ) {
2174 +
2175 + $this->p->debug->log_args( array(
2176 + 'mod' => $mod,
2177 + 'add_page' => $add_page,
2178 + ) );
2179 + }
2180 +
2181 + /*
2182 + * The $mod array argument is preferred but not required.
2183 + */
2184 + if ( ! is_array( $mod ) ) {
2185 +
2186 + if ( $this->p->debug->enabled ) {
2187 +
2188 + $this->p->debug->log( 'optional call to WpssoPage->get_mod()' );
2189 + }
2190 +
2191 + $mod = $this->p->page->get_mod( $mod );
2192 + }
2193 +
2194 + /*
2195 + * Prevent loops and optimize by returning the URL from local cache if possible.
2196 + */
2197 + static $local_cache = array();
2198 +
2199 + $url = null;
2200 + $is_custom = false;
2201 + $cache_salt = false;
2202 +
2203 + if ( ! empty( $mod[ 'obj' ] ) && $mod[ 'id' ] ) {
2204 +
2205 + /*
2206 + * Note that the sort order, page number, locale, amp and embed checks are provided by
2207 + * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
2208 + */
2209 + $cache_salt = self::get_mod_salt( $mod ) . '_add_page:' . (string) $add_page;
2210 +
2211 + if ( $this->p->debug->enabled ) {
2212 +
2213 + $this->p->debug->log( 'cache salt = ' . $cache_salt );
2214 + }
2215 +
2216 + if ( isset( $local_cache[ $cache_salt ] ) ) {
2217 +
2218 + return $local_cache[ $cache_salt ];
2219 + }
2220 +
2221 + /*
2222 + * WpssoUtil->is_canonical_disabled() returns true if:
2223 + *
2224 + * - An SEO plugin is active.
2225 + * - The 'add_link_rel_canonical' option is unchecked.
2226 + * - The 'wpsso_add_link_rel_canonical' filter returns false.
2227 + * - The 'wpsso_canonical_disabled' filter returns true.
2228 + */
2229 + if ( $this->is_canonical_disabled() ) { // Just in case.
2230 +
2231 + if ( $this->p->debug->enabled ) {
2232 +
2233 + $this->p->debug->log( 'skipped custom canonical url: canonical is disabled' );
2234 + }
2235 +
2236 + } elseif ( ! empty( $md_key ) ) {
2237 +
2238 + $url = $mod[ 'obj' ]->get_options( $mod[ 'id' ], $md_key ); // Returns null if an index key is not found.
2239 +
2240 + if ( ! empty( $url ) ) {
2241 +
2242 + if ( $this->p->debug->enabled ) {
2243 +
2244 + $this->p->debug->log( 'custom canonical url = ' . $url );
2245 + }
2246 +
2247 + $is_custom = true;
2248 + }
2249 + }
2250 + }
2251 +
2252 + if ( ! $is_custom ) { // No custom canonical url from the post, term, or user meta.
2253 +
2254 + /*
2255 + * Similar module type logic can be found in the following methods:
2256 + *
2257 + * See WpssoOpenGraph->get_mod_og_type().
2258 + * See WpssoPage->get_description().
2259 + * See WpssoPage->get_the_title().
2260 + * See WpssoSchema->get_mod_schema_type().
2261 + * See WpssoUtil->get_canonical_url().
2262 + */
2263 + if ( $mod[ 'is_home' ] ) {
2264 +
2265 + $url = SucomUtilWP::get_home_url( $this->p->options, $mod );
2266 +
2267 + $url = apply_filters( 'wpsso_home_url', $url, $mod );
2268 +
2269 + } elseif ( $mod[ 'is_comment' ] ) {
2270 +
2271 + if ( $mod[ 'id' ] ) { // Just in case.
2272 +
2273 + $url = get_comment_link( $mod[ 'id' ] );
2274 +
2275 + if ( $this->p->debug->enabled ) {
2276 +
2277 + $this->p->debug->log( 'get_comment_link() returned "' . $url . '"' );
2278 + }
2279 +
2280 + $url = $this->is_string_url( $url, 'comment link' ); // Check for WP_Error.
2281 + }
2282 +
2283 + $url = apply_filters( 'wpsso_comment_url', $url, $mod );
2284 +
2285 + } elseif ( $mod[ 'is_post' ] ) {
2286 +
2287 + if ( $mod[ 'post_type' ] ) { // Just in case.
2288 +
2289 + if ( $mod[ 'is_post_type_archive' ] ) { // The post id may be 0.
2290 +
2291 + if ( $this->p->debug->enabled ) {
2292 +
2293 + $this->p->debug->log( 'post type is archive' );
2294 + }
2295 +
2296 + $url = get_post_type_archive_link( $mod[ 'post_type' ] );
2297 +
2298 + if ( $this->p->debug->enabled ) {
2299 +
2300 + $this->p->debug->log( 'get_post_type_archive_link() returned "' . $url . '"' );
2301 + }
2302 +
2303 + $url = $this->is_string_url( $url, 'post type archive link' ); // Check for WP_Error.
2304 +
2305 + } elseif ( $mod[ 'id' ] ) { // Just in case.
2306 +
2307 + /*
2308 + * Get the canonical URL of the published post.
2309 + */
2310 + if ( 'publish' !== $mod[ 'post_status' ] ) {
2311 +
2312 + if ( $this->p->debug->enabled ) {
2313 +
2314 + $this->p->debug->log( 'post status is not published' );
2315 + }
2316 +
2317 + if ( $mod[ 'wp_obj' ] ) { // Just in case.
2318 +
2319 + $mod[ 'wp_obj' ]->post_status = 'publish';
2320 +
2321 + if ( empty( $mod[ 'wp_obj' ]->post_name ) ) {
2322 +
2323 + $mod[ 'wp_obj' ]->post_name = sanitize_title( $mod[ 'wp_obj' ]->post_title );
2324 + }
2325 +
2326 + $url = get_permalink( $mod[ 'wp_obj' ] );
2327 +
2328 + if ( $this->p->debug->enabled ) {
2329 +
2330 + $this->p->debug->log( 'get_permalink() returned "' . $url . '"' );
2331 + }
2332 + }
2333 + }
2334 +
2335 + if ( empty( $url ) ) { // Just in case.
2336 +
2337 + if ( $this->p->debug->enabled ) {
2338 +
2339 + $this->p->debug->log( 'getting permalink for post id ' . $mod[ 'id' ] );
2340 + }
2341 +
2342 + $url = get_permalink( $mod[ 'id' ] );
2343 +
2344 + if ( $this->p->debug->enabled ) {
2345 +
2346 + $this->p->debug->log( 'get_permalink() returned "' . $url . '"' );
2347 + }
2348 + }
2349 +
2350 + $url = $this->is_string_url( $url, 'post permalink' ); // Check for WP_Error.
2351 +
2352 + } elseif ( $this->p->debug->enabled ) {
2353 +
2354 + $this->p->debug->log( 'no post id' );
2355 + }
2356 +
2357 + } elseif ( $this->p->debug->enabled ) {
2358 +
2359 + $this->p->debug->log( 'no post type' );
2360 + }
2361 +
2362 + $url = apply_filters( 'wpsso_post_url', $url, $mod );
2363 +
2364 + } elseif ( $mod[ 'is_term' ] ) {
2365 +
2366 + if ( $mod[ 'id' ] ) { // Just in case.
2367 +
2368 + $url = get_term_link( $mod[ 'id' ], $mod[ 'tax_slug' ] );
2369 +
2370 + if ( $this->p->debug->enabled ) {
2371 +
2372 + $this->p->debug->log( 'get_term_link() returned "' . $url . '"' );
2373 + }
2374 +
2375 + $url = $this->is_string_url( $url, 'term link' ); // Check for WP_Error.
2376 + }
2377 +
2378 + $url = apply_filters( 'wpsso_term_url', $url, $mod );
2379 +
2380 + } elseif ( $mod[ 'is_user' ] ) {
2381 +
2382 + if ( $mod[ 'id' ] ) { // Just in case.
2383 +
2384 + $url = get_author_posts_url( $mod[ 'id' ] );
2385 +
2386 + if ( $this->p->debug->enabled ) {
2387 +
2388 + $this->p->debug->log( 'get_author_posts_url() returned "' . $url . '"' );
2389 + }
2390 +
2391 + $url = $this->is_string_url( $url, 'author posts url' ); // Check for WP_Error.
2392 + }
2393 +
2394 + $url = apply_filters( 'wpsso_user_url', $url, $mod );
2395 +
2396 + } elseif ( $mod[ 'is_search' ] ) {
2397 +
2398 + $url = get_search_link( $mod[ 'query_vars' ][ 's' ] );
2399 +
2400 + if ( $this->p->debug->enabled ) {
2401 +
2402 + $this->p->debug->log( 'get_search_link() returned "' . $url . '"' );
2403 + }
2404 +
2405 + $url = $this->is_string_url( $url, 'search link' ); // Check for WP_Error.
2406 +
2407 + $url = apply_filters( 'wpsso_search_url', $url, $mod );
2408 +
2409 + } elseif ( $mod[ 'is_archive' ] ) {
2410 +
2411 + if ( $mod[ 'is_date' ] ) {
2412 +
2413 + if ( $mod[ 'is_year' ] ) {
2414 +
2415 + $url = get_year_link( $mod[ 'query_vars' ][ 'year' ] );
2416 +
2417 + if ( $this->p->debug->enabled ) {
2418 +
2419 + $this->p->debug->log( 'get_year_link() returned "' . $url . '"' );
2420 + }
2421 +
2422 + $url = $this->is_string_url( $url, 'year link' ); // Check for WP_Error.
2423 +
2424 + } elseif ( $mod[ 'is_month' ] ) {
2425 +
2426 + $url = get_month_link( $mod[ 'query_vars' ][ 'year' ], $mod[ 'query_vars' ][ 'monthnum' ] );
2427 +
2428 + if ( $this->p->debug->enabled ) {
2429 +
2430 + $this->p->debug->log( 'get_month_link() returned "' . $url . '"' );
2431 + }
2432 +
2433 + $url = $this->is_string_url( $url, 'month link' ); // Check for WP_Error.
2434 +
2435 + } elseif ( $mod[ 'is_day' ] ) {
2436 +
2437 + $url = get_day_link( $mod[ 'query_vars' ][ 'year' ], $mod[ 'query_vars' ][ 'monthnum' ], $mod[ 'query_vars' ][ 'day' ] );
2438 +
2439 + if ( $this->p->debug->enabled ) {
2440 +
2441 + $this->p->debug->log( 'get_day_link() returned "' . $url . '"' );
2442 + }
2443 +
2444 + $url = $this->is_string_url( $url, 'day link' ); // Check for WP_Error.
2445 + }
2446 + }
2447 +
2448 + $url = apply_filters( 'wpsso_archive_page_url', $url, $mod );
2449 + }
2450 +
2451 + /*
2452 + * Use the current URL as a fallback for themes and plugins that create public content and don't use the
2453 + * standard WordPress functions / variables and/or are not properly integrated with WordPress (ie. they do
2454 + * not use custom post types, taxonomies, terms, etc.).
2455 + */
2456 + if ( empty ( $url ) ) {
2457 +
2458 + if ( $this->p->debug->enabled ) {
2459 +
2460 + $this->p->debug->log( 'falling back to server request url' );
2461 + }
2462 +
2463 + $url = self::get_url( $remove_ignored_args = true ); // Uses a local cache.
2464 +
2465 + $url = apply_filters( 'wpsso_server_request_url', $url );
2466 + }
2467 + }
2468 +
2469 + /*
2470 + * Maybe enforce the FORCE_SSL constant.
2471 + */
2472 + if ( strpos( $url, '://' ) ) { // Only check URLs with a protocol.
2473 +
2474 + if ( self::get_const( 'FORCE_SSL' ) && ! self::is_https( $url ) ) {
2475 +
2476 + $url = set_url_scheme( $url, 'https' );
2477 + }
2478 + }
2479 +
2480 + $url = $this->get_url_paged( $url, $mod, $add_page );
2481 +
2482 + $url = apply_filters( 'wpsso_canonical_url', $url, $mod, $add_page, $is_custom );
2483 +
2484 + if ( ! empty( $cache_salt ) ) {
2485 +
2486 + $local_cache[ $cache_salt ] = $url;
2487 + }
2488 +
2489 + return $url;
2490 + }
2491 +
2492 + public function get_oembed_url( $mod = false, $format = 'json' ) {
2493 +
2494 + if ( $this->p->debug->enabled ) {
2495 +
2496 + $this->p->debug->mark();
2497 + }
2498 +
2499 + $url = '';
2500 +
2501 + /*
2502 + * The $mod array argument is preferred but not required.
2503 + */
2504 + if ( ! is_array( $mod ) ) {
2505 +
2506 + if ( $this->p->debug->enabled ) {
2507 +
2508 + $this->p->debug->log( 'optional call to WpssoPage->get_mod()' );
2509 + }
2510 +
2511 + $mod = $this->p->page->get_mod( $mod );
2512 + }
2513 +
2514 + if ( function_exists( 'get_oembed_endpoint_url' ) ) {
2515 +
2516 + if ( $mod[ 'is_post' ] && $mod[ 'id' ] ) { // Just in case.
2517 +
2518 + $url = $this->get_canonical_url( $mod );
2519 +
2520 + $url = get_oembed_endpoint_url( $url, $format );
2521 + }
2522 + }
2523 +
2524 + return apply_filters( 'wpsso_oembed_url', $url, $mod, $format );
2525 + }
2526 +
2527 + public function get_oembed_data( $mod = false, $width = '600' ) {
2528 +
2529 + if ( $this->p->debug->enabled ) {
2530 +
2531 + $this->p->debug->mark();
2532 + }
2533 +
2534 + $data = false;
2535 +
2536 + /*
2537 + * The $mod array argument is preferred but not required.
2538 + */
2539 + if ( ! is_array( $mod ) ) {
2540 +
2541 + if ( $this->p->debug->enabled ) {
2542 +
2543 + $this->p->debug->log( 'optional call to WpssoPage->get_mod()' );
2544 + }
2545 +
2546 + $mod = $this->p->page->get_mod( $mod );
2547 + }
2548 +
2549 + if ( function_exists( 'get_oembed_response_data' ) ) {
2550 +
2551 + if ( $mod[ 'is_post' ] && $mod[ 'id' ] ) { // Just in case.
2552 +
2553 + $data = get_oembed_response_data( $mod[ 'id' ], $width ); // Returns false on error.
2554 + }
2555 + }
2556 +
2557 + return apply_filters( 'wpsso_oembed_data', $data, $mod, $width );
2558 + }
2559 +
2560 + /*
2561 + * $mixed = true | false | post_id | $mod array ($mod array is preferred but not required).
2562 + *
2563 + * $md_key = 'redirect_url' | '' (empty value ignores custom URL).
2564 + */
2565 + public function get_redirect_url( $mixed, $mod_id = null, $md_key = 'redirect_url' ) {
2566 +
2567 + if ( $this->p->debug->enabled ) {
2568 +
2569 + $this->p->debug->mark();
2570 + }
2571 +
2572 + $mod = false;
2573 +
2574 + if ( ! empty( $mixed[ 'obj' ] ) ) {
2575 +
2576 + $mod =& $mixed;
2577 +
2578 + } elseif ( is_string( $mixed ) && isset( $this->p->$mixed ) && $mod_id ) { // Just in case.
2579 +
2580 + $mod = $this->p->$mixed->get_mod( $mod_id );
2581 +
2582 + } elseif ( ! is_array( $mod ) ) {
2583 +
2584 + if ( $this->p->debug->enabled ) {
2585 +
2586 + $this->p->debug->log( 'optional call to WpssoPage->get_mod()' );
2587 + }
2588 +
2589 + $mod = $this->p->page->get_mod( $mixed );
2590 + }
2591 +
2592 + $url = null;
2593 + $is_custom = false;
2594 +
2595 + if ( ! empty( $mod[ 'obj' ] ) && $mod[ 'id' ] ) {
2596 +
2597 + /*
2598 + * WpssoUtil->is_redirect_disabled() returns true if:
2599 + *
2600 + * - An SEO plugin is active.
2601 + * - The 'wpsso_redirect_disabled' filter returns true.
2602 + */
2603 + if ( $this->is_redirect_disabled() ) { // Just in case.
2604 +
2605 + if ( $this->p->debug->enabled ) {
2606 +
2607 + $this->p->debug->log( 'skipped custom redirect url: redirect is disabled' );
2608 + }
2609 +
2610 + } elseif ( ! empty( $md_key ) ) {
2611 +
2612 + $url = $mod[ 'obj' ]->get_options( $mod[ 'id' ], $md_key ); // Returns null if an index key is not found.
2613 +
2614 + if ( ! empty( $url ) ) {
2615 +
2616 + if ( $this->p->debug->enabled ) {
2617 +
2618 + $this->p->debug->log( 'custom redirect url = ' . $url );
2619 + }
2620 +
2621 + $is_custom = true;
2622 + }
2623 + }
2624 + }
2625 +
2626 + $url = apply_filters( 'wpsso_redirect_url', $url, $mod, $is_custom );
2627 +
2628 + return $url;
2629 + }
2630 +
2631 + public function get_sharing_url( $mod = false, $add_page = false, $atts = array() ) {
2632 +
2633 + if ( $this->p->debug->enabled ) {
2634 +
2635 + $this->p->debug->log_args( array(
2636 + 'mod' => $mod,
2637 + 'add_page' => $add_page,
2638 + 'atts' => $atts,
2639 + ) );
2640 + }
2641 +
2642 + $url = $this->get_canonical_url( $mod, $add_page );
2643 +
2644 + $utm = array();
2645 +
2646 + foreach ( array(
2647 + 'utm_medium',
2648 + 'utm_source',
2649 + 'utm_campaign',
2650 + 'utm_content',
2651 + 'utm_term',
2652 + ) as $q ) {
2653 +
2654 + /*
2655 + * Ignore 0, false, null, and '' (empty string) values.
2656 + */
2657 + $utm[ $q ] = empty( $atts[ $q ] ) ? false : $atts[ $q ];
2658 + }
2659 +
2660 + $utm = apply_filters( 'wpsso_sharing_utm_args', $utm, $mod );
2661 +
2662 + /*
2663 + * To add UTM tracking query arguments we need at least 'utm_source', 'utm_medium', and 'utm_campaign'.
2664 + */
2665 + if ( ! empty( $utm[ 'utm_source' ] ) && ! empty( $utm[ 'utm_medium' ] ) && ! empty( $utm[ 'utm_campaign' ] ) ) {
2666 +
2667 + $url = add_query_arg( array(
2668 + 'utm_medium' => $utm[ 'utm_medium' ], // Example: 'social'.
2669 + 'utm_source' => $utm[ 'utm_source' ], // Example: 'facebook'.
2670 + 'utm_campaign' => $utm[ 'utm_campaign' ], // Example: 'book-launch'
2671 + 'utm_content' => $utm[ 'utm_content' ],
2672 + 'utm_term' => $utm[ 'utm_term' ],
2673 + ), $url );
2674 + }
2675 +
2676 + return apply_filters( 'wpsso_sharing_url', $url, $mod, $add_page );
2677 + }
2678 +
2679 + /*
2680 + * Shorten the sharing URL using the selected shortening service.
2681 + */
2682 + public function get_sharing_short_url( $mod = false, $add_page = false, $atts = array() ) {
2683 +
2684 + if ( $this->p->debug->enabled ) {
2685 +
2686 + $this->p->debug->mark();
2687 + }
2688 +
2689 + $url = $this->get_sharing_url( $mod, $add_page, $atts );
2690 +
2691 + return $this->shorten_url( $url, $mod );
2692 + }
2693 +
2694 + public function get_url_paged( $url, array $mod, $add_page = false ) {
2695 +
2696 + if ( $this->p->debug->enabled ) {
2697 +
2698 + $this->p->debug->mark();
2699 + }
2700 +
2701 + if ( empty( $url ) ) { // Just in case.
2702 +
2703 + if ( $this->p->debug->enabled ) {
2704 +
2705 + $this->p->debug->log( 'exiting early: url is empty' );
2706 + }
2707 +
2708 + return $url;
2709 +
2710 + } elseif ( ! $add_page ) { // Just in case.
2711 +
2712 + if ( $this->p->debug->enabled ) {
2713 +
2714 + $this->p->debug->log( 'exiting early: add_page is false' );
2715 + }
2716 +
2717 + return $url;
2718 + }
2719 +
2720 + global $wp_rewrite;
2721 +
2722 + if ( $this->p->debug->enabled ) {
2723 +
2724 + $this->p->debug->log( 'pagination base = ' . $wp_rewrite->pagination_base );
2725 + }
2726 +
2727 + $using_permalinks = $wp_rewrite->using_permalinks();
2728 + $have_query_args = false === strpos( $url, '?' ) ? false : true;
2729 + $page_number = $this->get_page_number( $mod, $add_page );
2730 +
2731 + if ( $mod[ 'is_archive' ] || $mod[ 'is_home_posts' ] ) {
2732 +
2733 + if ( $this->p->debug->enabled ) {
2734 +
2735 + $this->p->debug->log( 'is archive or home posts page' );
2736 + }
2737 +
2738 + if ( $page_number > 1 ) {
2739 +
2740 + if ( ! $using_permalinks || $have_query_args ) {
2741 +
2742 + $url = add_query_arg( 'paged', $page_number, $url );
2743 +
2744 + } else {
2745 +
2746 + $url = user_trailingslashit( trailingslashit( $url ) . trailingslashit( $wp_rewrite->pagination_base ) . $page_number );
2747 + }
2748 + }
2749 +
2750 + } elseif ( ! $using_permalinks || $have_query_args ) {
2751 +
2752 + if ( $this->p->debug->enabled ) {
2753 +
2754 + $this->p->debug->log( 'not using permalinks or have query args' );
2755 + }
2756 +
2757 + /*
2758 + * Note that the singular page query argument is named 'page' not 'paged'.
2759 + */
2760 + if ( $page_number > 1 ) {
2761 +
2762 + $url = add_query_arg( 'page', $page_number, $url );
2763 + }
2764 +
2765 + if ( $mod[ 'comment_paged' ] > 1 ) {
2766 +
2767 + $url = add_query_arg( 'cpage', $mod[ 'comment_paged' ], $url );
2768 + }
2769 +
2770 + } else {
2771 +
2772 + if ( $page_number > 1 ) {
2773 +
2774 + $url = user_trailingslashit( trailingslashit( $url ) . $page_number );
2775 + }
2776 +
2777 + if ( $mod[ 'comment_paged' ] > 1 ) {
2778 +
2779 + $url = user_trailingslashit( trailingslashit( $url ) . $wp_rewrite->comments_pagination_base . '-' . $mod[ 'comment_paged' ] );
2780 + }
2781 + }
2782 +
2783 + if ( $this->p->debug->enabled ) {
2784 +
2785 + $this->p->debug->log( 'get url paged = ' . $url );
2786 + }
2787 +
2788 + return $url;
2789 + }
2790 +
2791 + /*
2792 + * See WpssoUtil->get_url_paged().
2793 + * See WpssoUtilInline->get_defaults().
2794 + */
2795 + public function get_page_number( array $mod, $add_page = false ) {
2796 +
2797 + $page_number = 1;
2798 +
2799 + if ( is_numeric( $add_page ) ) {
2800 +
2801 + $page_number = $add_page > 1 ? $add_page : 1;
2802 +
2803 + } else $page_number = $mod[ 'paged' ]; // False or a number.
2804 +
2805 + return $page_number;
2806 + }
2807 +
2808 + /*
2809 + * Returns for example "#sso-post-123", #sso-term-123-tax-faq-category with a $mod array or "#sso-" without.
2810 + *
2811 + * Called by WpssoFaqShortcodeFaq->do_shortcode().
2812 + * Called by WpssoFaqShortcodeQuestion->do_shortcode().
2813 + * Called by WpssoJsonTypeThing->filter_json_data_https_schema_org_thing().
2814 + */
2815 + public function get_fragment_anchor( $mod = null ) {
2816 +
2817 + return '#sso-' . ( $mod ? SucomUtilWP::get_mod_css_id( $mod ) : '' );
2818 + }
2819 +
2820 + /*
2821 + * Called by scheduled tasks to check the user ID value and possibly load a different textdomain language.
2822 + *
2823 + * See WpssoUser->schedule_add_person_role().
2824 + * See WpssoUser->add_person_role().
2825 + * See WpssoUser->schedule_remove_person_role().
2826 + * See WpssoUser->remove_person_role().
2827 + * See WpssoUtilCache->show_refresh_pending().
2828 + * See WpssoUtilCache->schedule_refresh().
2829 + * See WpssoUtilCache->refresh().
2830 + */
2831 + public function maybe_change_user_id( $user_id ) {
2832 +
2833 + $current_user_id = get_current_user_id(); // Always returns an integer.
2834 + $user_id = is_numeric( $user_id ) ? (int) $user_id : $current_user_id; // User ID can be true, false, null, or a number.
2835 +
2836 + if ( empty( $user_id ) ) { // User ID is 0 (cron user, for example).
2837 +
2838 + return $user_id;
2839 +
2840 + } elseif ( $user_id === $current_user_id ) { // Nothing to do.
2841 +
2842 + return $user_id;
2843 + }
2844 +
2845 + /*
2846 + * The user ID is different than the current / effective user ID, so check if the user locale is different
2847 + * to the current WordPress locale and load the user locale if required.
2848 + */
2849 + $current_locale = get_locale(); // Use the WordPress locale.
2850 + $user_locale = get_metadata( 'user', $user_id, 'locale', $single = true );
2851 +
2852 + $this->maybe_load_textdomain( $current_locale, $user_locale, $plugin_slug = 'wpsso' );
2853 +
2854 + return $user_id;
2855 + }
2856 +
2857 + public function maybe_load_textdomain( $current_locale, $new_locale, $plugin_slug ) {
2858 +
2859 + static $local_cache = array();
2860 +
2861 + if ( isset( $local_cache[ $new_locale ][ $plugin_slug ] ) ) {
2862 +
2863 + return $local_cache[ $new_locale ][ $plugin_slug ];
2864 + }
2865 +
2866 + if ( $new_locale && $new_locale !== $current_locale ) {
2867 +
2868 + $rel_lang_path = $plugin_slug . '/languages/';
2869 + $mofile = $plugin_slug . '-' . $new_locale . '.mo';
2870 + $wp_mopath = defined( 'WP_LANG_DIR' ) ? WP_LANG_DIR . '/plugins/' . $mofile : false;
2871 + $mu_plugin_mopath = defined( 'WPMU_PLUGIN_DIR' ) ? WPMU_PLUGIN_DIR . '/' . $rel_lang_path . $mofile : false;
2872 + $plugin_mopath = defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR . '/' . $rel_lang_path . $mofile : false;
2873 +
2874 + /*
2875 + * Try to load from the WordPress languages directory first.
2876 + */
2877 + if ( ( $wp_mopath && load_textdomain( $plugin_slug, $wp_mopath ) ) ||
2878 + ( $mu_plugin_mopath && load_textdomain( $plugin_slug, $mu_plugin_mopath ) ) ||
2879 + ( $plugin_mopath && load_textdomain( $plugin_slug, $plugin_mopath ) ) ) {
2880 +
2881 + $this->p->notice->set_label_transl(); // Update the notice label.
2882 +
2883 + return $local_cache[ $new_locale ][ $plugin_slug ] = true;
2884 + }
2885 + }
2886 +
2887 + return $local_cache[ $new_locale ][ $plugin_slug ] = false;
2888 + }
2889 +
2890 + /*
2891 + * Used by WpssoMedia get_content_images() and get_attachment_image_src().
2892 + */
2893 + public function fix_relative_url( $url ) {
2894 +
2895 + if ( empty( $url ) || false !== strpos( $url, '://' ) ) {
2896 +
2897 + return $url;
2898 + }
2899 +
2900 + if ( $this->p->debug->enabled ) {
2901 +
2902 + $this->p->debug->log( 'relative url found = ' . $url );
2903 + }
2904 +
2905 + if ( 0 === strpos( $url, '//' ) ) { // Example: //host.com/dir/file/
2906 +
2907 + $url = self::get_prot() . ':' . $url;
2908 +
2909 + } elseif ( 0 === strpos( $url, '/' ) ) { // Example: /dir/file/
2910 +
2911 + $url = home_url( $url );
2912 +
2913 + } else { // Example: file/
2914 +
2915 + $base = self::get_url( $remove_ignored_args = true ); // Uses a local cache.
2916 +
2917 + if ( false !== strpos( $base, '?' ) ) {
2918 +
2919 + $base_parts = explode( '?', $base );
2920 +
2921 + $base = reset( $base_parts );
2922 + }
2923 +
2924 + $url = trailingslashit( $base, false ) . $url;
2925 + }
2926 +
2927 + if ( $this->p->debug->enabled ) {
2928 +
2929 + $this->p->debug->log( 'relative url fixed = ' . $url );
2930 + }
2931 +
2932 + return $url;
2933 + }
2934 +
2935 + /*
2936 + * WpssoUtil->is_canonical_disabled() returns true if:
2937 + *
2938 + * - An SEO plugin is active.
2939 + * - The 'add_link_rel_canonical' option is unchecked.
2940 + * - The 'wpsso_add_link_rel_canonical' filter returns false.
2941 + * - The 'wpsso_canonical_disabled' filter returns true.
2942 + */
2943 + public function is_canonical_disabled() {
2944 +
2945 + $disabled = false;
2946 +
2947 + if ( ! empty( $this->p->avail[ 'seo' ][ 'any' ] ) ) {
2948 +
2949 + $disabled = true;
2950 +
2951 + } elseif ( empty( $this->p->options[ 'add_link_rel_canonical' ] ) ) {
2952 +
2953 + $disabled = true;
2954 + }
2955 +
2956 + $disabled = apply_filters( 'wpsso_add_link_rel_canonical', ( $disabled ? false : true ) ) ? false : true;
2957 +
2958 + $disabled = apply_filters( 'wpsso_canonical_disabled', $disabled );
2959 +
2960 + return $disabled;
2961 + }
2962 +
2963 + /*
2964 + * WpssoUtil->is_redirect_disabled() returns true if:
2965 + *
2966 + * - An SEO plugin is active.
2967 + * - The 'wpsso_redirect_disabled' filter returns true.
2968 + */
2969 + public function is_redirect_disabled() {
2970 +
2971 + $disabled = false;
2972 +
2973 + if ( ! empty( $this->p->avail[ 'seo' ][ 'any' ] ) ) {
2974 +
2975 + $disabled = true;
2976 + }
2977 +
2978 + $disabled = apply_filters( 'wpsso_redirect_disabled', $disabled );
2979 +
2980 + return $disabled;
2981 + }
2982 +
2983 + /*
2984 + * WpssoUtil->is_shortlink_disabled() returns true if:
2985 + *
2986 + * - The 'add_link_rel_shortlink' option is unchecked.
2987 + * - The 'wpsso_add_link_rel_shortlink' filter returns false.
2988 + * - The 'wpsso_shortlink_disabled' filter returns true.
2989 + */
2990 + public function is_shortlink_disabled() {
2991 +
2992 + $disabled = false;
2993 +
2994 + if ( empty( $this->p->options[ 'add_link_rel_shortlink' ] ) ) {
2995 +
2996 + $disabled = true;
2997 + }
2998 +
2999 + $disabled = apply_filters( 'wpsso_add_link_rel_shortlink', ( $disabled ? false : true ) ) ? false : true;
3000 +
3001 + $disabled = apply_filters( 'wpsso_shortlink_disabled', $disabled );
3002 +
3003 + return $disabled;
3004 + }
3005 +
3006 + /*
3007 + * WpssoUtil->is_title_tag_disabled() returns true if:
3008 + *
3009 + * - The theme does not support the 'title-tag' feature.
3010 + * - The WPSSO_TITLE_TAG_DISABLE constant is true.
3011 + *
3012 + * See WpssoMessages->maybe_doc_title_disabled().
3013 + */
3014 + public function is_title_tag_disabled() {
3015 +
3016 + $disabled = false;
3017 +
3018 + if ( ! current_theme_supports( 'title-tag' ) ) {
3019 +
3020 + $disabled = true;
3021 +
3022 + } elseif ( SucomUtil::get_const( 'WPSSO_TITLE_TAG_DISABLE' ) ) {
3023 +
3024 + $disabled = true;
3025 + }
3026 +
3027 + return $disabled;
3028 + }
3029 +
3030 + /*
3031 + * WpssoUtil->is_seo_title_disabled() returns true if:
3032 + *
3033 + * - An SEO plugin is active.
3034 + * - The theme does not support the 'title-tag' feature.
3035 + * - The WPSSO_TITLE_TAG_DISABLE constant is true.
3036 + * - The 'plugin_title_tag' option is not 'seo_title'.
3037 + */
3038 + public function is_seo_title_disabled() {
3039 +
3040 + $disabled = false;
3041 +
3042 + if ( ! empty( $this->p->avail[ 'seo' ][ 'any' ] ) ) {
3043 +
3044 + $disabled = true;
3045 +
3046 + /*
3047 + * WpssoUtil->is_title_tag_disabled() returns true if:
3048 + *
3049 + * - The theme does not support the 'title-tag' feature.
3050 + * - The WPSSO_TITLE_TAG_DISABLE constant is true.
3051 + */
3052 + } elseif ( $this->is_title_tag_disabled() ) {
3053 +
3054 + $disabled = true;
3055 +
3056 + } elseif ( 'seo_title' !== $this->p->options[ 'plugin_title_tag' ] ) {
3057 +
3058 + $disabled = true;
3059 + }
3060 +
3061 + return $disabled;
3062 + }
3063 +
3064 + /*
3065 + * WpssoUtil->is_seo_desc_disabled() returns true if:
3066 + *
3067 + * - An SEO plugin is active.
3068 + * - The 'add_meta_name_description' option is unchecked.
3069 + */
3070 + public function is_seo_desc_disabled() {
3071 +
3072 + $disabled = false;
3073 +
3074 + if ( ! empty( $this->p->avail[ 'seo' ][ 'any' ] ) ) {
3075 +
3076 + $disabled = true;
3077 +
3078 + } elseif ( empty( $this->p->options[ 'add_meta_name_description' ] ) ) {
3079 +
3080 + $disabled = true;
3081 + }
3082 +
3083 + return $disabled;
3084 + }
3085 +
3086 + /*
3087 + * WpssoUtilRobots->is_disabled() returns true if:
3088 + *
3089 + * - An SEO plugin is active.
3090 + * - The 'add_meta_name_robots' option is unchecked.
3091 + * - The 'wpsso_robots_disabled' filter returns true.
3092 + */
3093 + public function is_robots_disabled() {
3094 +
3095 + return $this->robots->is_disabled();
3096 + }
3097 +
3098 + public function is_schema_disabled() {
3099 +
3100 + $disabled = false;
3101 +
3102 + /*
3103 + * Array element is false when the WPSSO_SCHEMA_MARKUP_DISABLE constant is true.
3104 + *
3105 + * See WpssoCheck->get_avail().
3106 + */
3107 + if ( isset( $this->p->avail[ 'p' ][ 'schema' ] ) && empty( $this->p->avail[ 'p' ][ 'schema' ] ) ) {
3108 +
3109 + $disabled = true;
3110 + }
3111 +
3112 + return $disabled;
3113 + }
3114 +
3115 + public function is_sitemaps_disabled() {
3116 +
3117 + return apply_filters( 'wp_sitemaps_enabled', true ) ? false : true;
3118 + }
3119 +
3120 + public function is_pin_img_disabled() {
3121 +
3122 + return empty( $this->p->options[ 'pin_add_img_html' ] ) ? true : false;
3123 + }
3124 +
3125 + public function is_json_pretty() {
3126 +
3127 + if ( $this->p->debug->enabled ) { // Always pretty JSON when debug is enabled.
3128 +
3129 + return true;
3130 + }
3131 +
3132 + $is_pretty = empty( $this->p->options[ 'plugin_schema_json_min' ] ) ? true : false;
3133 +
3134 + $is_pretty = (bool) apply_filters( 'wpsso_json_pretty_print', $is_pretty );
3135 +
3136 + return $is_pretty ? true : false;
3137 + }
3138 +
3139 + /*
3140 + * Note that WebP is only supported since PHP v7.1.
3141 + */
3142 + public function is_image_url( $image_url ) {
3143 +
3144 + if ( $this->p->debug->enabled ) {
3145 +
3146 + $this->p->debug->mark();
3147 + }
3148 +
3149 + /*
3150 + * Example $image_info:
3151 + *
3152 + * Array (
3153 + * [0] => 2048
3154 + * [1] => 2048
3155 + * [2] => 3
3156 + * [3] => width="2048" height="2048"
3157 + * [bits] => 8
3158 + * [mime] => image/png
3159 + * )
3160 + */
3161 + $image_info = $this->get_image_url_info( $image_url );
3162 +
3163 + if ( empty( $image_info[ 2 ] ) ) { // Make sure we have an image type integer.
3164 +
3165 + return false;
3166 + }
3167 +
3168 + return true;
3169 + }
3170 +
3171 + public function is_string_url( $url, $context ) {
3172 +
3173 + if ( is_string( $url ) ) {
3174 +
3175 + if ( $this->p->debug->enabled ) {
3176 +
3177 + $this->p->debug->log( $context . ' url = ' . $url );
3178 + }
3179 +
3180 + return $url; // Stop here.
3181 + }
3182 +
3183 + if ( $this->p->debug->enabled ) {
3184 +
3185 + $this->p->debug->log( $context . ' url is ' . gettype( $url ) );
3186 +
3187 + if ( is_wp_error( $url ) ) {
3188 +
3189 + $this->p->debug->log( $context . ' url error: ' . $url->get_error_message() );
3190 + }
3191 + }
3192 +
3193 + return false;
3194 + }
3195 +
3196 + public function clear_uniq_urls( $image_sizes = 'default', $mod = false ) {
3197 +
3198 + $cleared = 0; // Default value to return.
3199 +
3200 + if ( ! is_array( $image_sizes ) ) {
3201 +
3202 + $image_sizes = $this->get_image_size_names( $image_sizes, $sanitize = false ); // Always returns an array.
3203 + }
3204 +
3205 + /*
3206 + * Note that the sort order, page number, locale, amp and embed checks are provided by
3207 + * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
3208 + */
3209 + $cache_salt = SucomUtil::get_mod_salt( $mod );
3210 +
3211 + foreach ( $image_sizes as $num => $uniq_context ) {
3212 +
3213 + $uniq_context = preg_replace( '/-[0-9]+x[0-9]+$/', '', $uniq_context ); // Change 'wpsso-schema-1x1' to 'wpsso-schema'.
3214 +
3215 + $image_sizes[ $num ] = $cache_salt ? $cache_salt . '_' . $uniq_context : $uniq_context;
3216 + }
3217 +
3218 + foreach ( array_unique( $image_sizes ) as $uniq_context ) {
3219 +
3220 + if ( isset( $this->cache_uniq_urls[ $uniq_context ] ) ) { // Array to detect duplicate image URLs.
3221 +
3222 + $cleared += count( $this->cache_uniq_urls[ $uniq_context ] );
3223 + }
3224 +
3225 + $this->cache_uniq_urls[ $uniq_context ] = array();
3226 +
3227 + if ( $this->p->debug->enabled ) {
3228 +
3229 + $this->p->debug->log( 'cleared uniq url cache for context ' . $uniq_context );
3230 + }
3231 + }
3232 +
3233 + return $cleared;
3234 + }
3235 +
3236 + public function is_dupe_url( $url, $image_sizes = 'default', $mod = false ) {
3237 +
3238 + return $this->is_uniq_url( $url, $image_sizes, $mod ) ? false : true;
3239 + }
3240 +
3241 + public function is_uniq_url( $url, $image_sizes = 'default', $mod = false ) {
3242 +
3243 + $is_uniq = true; // Default value to return.
3244 +
3245 + if ( empty( $url ) ) return false; // Just in case.
3246 +
3247 + $url = $this->fix_relative_url( $url ); // Just in case.
3248 +
3249 + if ( ! is_array( $image_sizes ) ) $image_sizes = array( $image_sizes );
3250 +
3251 + /*
3252 + * Note that the sort order, page number, locale, amp and embed checks are provided by
3253 + * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
3254 + */
3255 + $cache_salt = SucomUtil::get_mod_salt( $mod );
3256 +
3257 + foreach ( $image_sizes as $num => $uniq_context ) {
3258 +
3259 + $uniq_context = preg_replace( '/-[0-9]+x[0-9]+$/', '', $uniq_context ); // Change 'wpsso-schema-1x1' to 'wpsso-schema'.
3260 +
3261 + $image_sizes[ $num ] = $cache_salt ? $cache_salt . '_' . $uniq_context : $uniq_context;
3262 + }
3263 +
3264 + foreach ( array_unique( $image_sizes ) as $uniq_context ) {
3265 +
3266 + if ( isset( $this->cache_uniq_urls[ $uniq_context ][ $url ] ) ) { // Array to detect duplicate image URLs.
3267 +
3268 + if ( $this->p->debug->enabled ) {
3269 +
3270 + $this->p->debug->log( 'duplicate url found for context ' . $uniq_context . ': ' . $url );
3271 + }
3272 +
3273 + $is_uniq = false; // Duplicate found.
3274 +
3275 + } else {
3276 +
3277 + if ( $this->p->debug->enabled ) {
3278 +
3279 + $this->p->debug->log( 'unique url saved for context ' . $uniq_context . ': ' . $url );
3280 + }
3281 +
3282 + $this->cache_uniq_urls[ $uniq_context ][ $url ] = true;
3283 + }
3284 + }
3285 +
3286 + return $is_uniq;
3287 + }
3288 +
3289 + public function is_maxed( &$arr, $num = 0 ) {
3290 +
3291 + if ( ! is_array( $arr ) ) {
3292 +
3293 + return false;
3294 + }
3295 +
3296 + if ( $num > 0 && count( $arr ) >= $num ) {
3297 +
3298 + return true;
3299 + }
3300 +
3301 + return false;
3302 + }
3303 +
3304 + public function merge_max( &$dst, &$src, $num = 0 ) {
3305 +
3306 + if ( ! is_array( $dst ) || ! is_array( $src ) ) {
3307 +
3308 + return false;
3309 + }
3310 +
3311 + if ( ! empty( $src ) && array_filter( $src ) ) {
3312 +
3313 + $dst = array_merge( $dst, $src );
3314 + }
3315 +
3316 + return $this->slice_max( $dst, $num ); // Returns true or false.
3317 + }
3318 +
3319 + public function push_max( &$dst, &$src, $num = 0 ) {
3320 +
3321 + if ( ! is_array( $dst ) || ! is_array( $src ) ) {
3322 +
3323 + return false;
3324 + }
3325 +
3326 + if ( ! empty( $src ) && array_filter( $src ) ) {
3327 +
3328 + array_push( $dst, $src );
3329 + }
3330 +
3331 + return $this->slice_max( $dst, $num ); // Returns true or false.
3332 + }
3333 +
3334 + public function slice_max( &$arr, $num = 0 ) {
3335 +
3336 + if ( ! is_array( $arr ) ) {
3337 +
3338 + return false;
3339 + }
3340 +
3341 + $has_count = count( $arr );
3342 +
3343 + if ( $num > 0 ) {
3344 +
3345 + if ( $has_count == $num ) {
3346 +
3347 + if ( $this->p->debug->enabled ) {
3348 +
3349 + $this->p->debug->log( 'max values reached (' . $has_count . ' == ' . $num . ')' );
3350 + }
3351 +
3352 + return true;
3353 +
3354 + } elseif ( $has_count > $num ) {
3355 +
3356 + if ( $this->p->debug->enabled ) {
3357 +
3358 + $this->p->debug->log( 'max values reached (' . $has_count . ' > ' . $num . ') - slicing array' );
3359 + }
3360 +
3361 + $arr = array_slice( $arr, 0, $num );
3362 +
3363 + return true;
3364 + }
3365 + }
3366 +
3367 + return false;
3368 + }
3369 +
3370 + /*
3371 + * Get maximum media values from custom meta or plugin settings.
3372 + */
3373 + public function get_max_nums( array $mod, $opt_prefix = 'og' ) {
3374 +
3375 + $max_nums = array();
3376 + $max_opt_keys = array( $opt_prefix . '_vid_max', $opt_prefix . '_img_max' );
3377 +
3378 + foreach ( $max_opt_keys as $opt_key ) {
3379 +
3380 + if ( ! empty( $mod[ 'obj' ] ) && $mod[ 'id' ] ) {
3381 +
3382 + $max_val = $mod[ 'obj' ]->get_options( $mod[ 'id' ], $opt_key ); // Returns null if an index key is not found.
3383 +
3384 + } else $max_val = null; // Default value if index key is missing.
3385 +
3386 + /*
3387 + * Quick sanitation of returned value.
3388 + */
3389 + if ( $max_val !== null & is_numeric( $max_val ) && $max_val >= 0 ) {
3390 +
3391 + $max_nums[ $opt_key ] = $max_val;
3392 +
3393 + if ( $this->p->debug->enabled ) {
3394 +
3395 + $this->p->debug->log( 'found custom meta ' . $opt_key . ' = ' . $max_val );
3396 + }
3397 +
3398 + } else $max_nums[ $opt_key ] = isset( $this->p->options[ $opt_key ] ) ? $this->p->options[ $opt_key ] : 0;
3399 + }
3400 +
3401 + return $max_nums;
3402 + }
3403 +
3404 + public function safe_apply_filters( array $args, array $mod = array(), $mtime_max = 0, $use_bfo = false ) {
3405 +
3406 + if ( $this->p->debug->enabled ) {
3407 +
3408 + $this->p->debug->mark();
3409 + }
3410 +
3411 + /*
3412 + * Check for required apply_filters() arguments.
3413 + */
3414 + if ( empty( $args[ 0 ] ) ) {
3415 +
3416 + if ( $this->p->debug->enabled ) {
3417 +
3418 + $this->p->debug->log( 'exiting early: filter name missing from parameter array' );
3419 + }
3420 +
3421 + return '';
3422 +
3423 + } elseif ( ! isset( $args[ 1 ] ) ) {
3424 +
3425 + if ( $this->p->debug->enabled ) {
3426 +
3427 + $this->p->debug->log( 'exiting early: filter value missing from parameter array' );
3428 + }
3429 +
3430 + return '';
3431 + }
3432 +
3433 + $filter_name = $args[ 0 ];
3434 + $filter_value = $args[ 1 ];
3435 +
3436 + if ( false === has_filter( $filter_name ) ) { // Skip if no filters.
3437 +
3438 + if ( $this->p->debug->enabled ) {
3439 +
3440 + $this->p->debug->log( 'exiting early: ' . $filter_name . ' has no filter hooks' );
3441 + }
3442 +
3443 + return $filter_value;
3444 + }
3445 +
3446 + /*
3447 + * Prevent recursive loops - the global variable is defined before applying the filters.
3448 + */
3449 + if ( ! empty( $GLOBALS[ 'wpsso_doing_filter_' . $filter_name ] ) ) {
3450 +
3451 + if ( $this->p->debug->enabled ) {
3452 +
3453 + $this->p->debug->log( 'exiting early: global variable "wpsso_doing_filter_' . $filter_name . '" is true' );
3454 + }
3455 +
3456 + return $filter_value;
3457 + }
3458 +
3459 + /*
3460 + * Hooked by some modules to perform actions before/after filtering the content.
3461 + */
3462 + do_action( 'wpsso_pre_apply_filters_text', $filter_name );
3463 +
3464 + /*
3465 + * Load the Block Filter Output (BFO) filters to block and show an error for incorrectly coded filters.
3466 + */
3467 + if ( $use_bfo ) {
3468 +
3469 + $classname = apply_filters( 'wpsso_load_lib', false, 'com/bfo', 'SucomBFO' );
3470 +
3471 + if ( is_string( $classname ) && class_exists( $classname ) ) {
3472 +
3473 + $bfo_obj = new $classname( $this->p );
3474 +
3475 + $bfo_obj->add_start_hooks( array( $filter_name ) );
3476 + }
3477 + }
3478 +
3479 + /*
3480 + * Save original post object, in case some filters modify the global $post.
3481 + */
3482 + global $post, $wp_query;
3483 +
3484 + if ( $this->p->debug->enabled ) {
3485 +
3486 + $this->p->debug->log( 'saving original post object ' . ( isset( $post->ID ) ? 'id ' . $post->ID : '(no post id)' ) );
3487 + }
3488 +
3489 + $post_pre_filter = $post; // Save original global post object.
3490 + $wp_query_pre_filter = $wp_query; // Save original global wp_query.
3491 +
3492 + /*
3493 + * Make sure the $post object is correct before filtering.
3494 + */
3495 + if ( ! empty( $mod[ 'is_post' ] ) ) {
3496 +
3497 + if ( ! empty( $mod[ 'id' ] ) ) { // Just in case.
3498 +
3499 + if ( ! isset( $post->ID ) || (int) $post->ID !== (int) $mod[ 'id' ] ) {
3500 +
3501 + if ( $this->p->debug->enabled ) {
3502 +
3503 + $this->p->debug->log( 'resetting post object from mod id ' . $mod[ 'id' ] );
3504 + }
3505 +
3506 + $post = SucomUtilWP::get_post_object( $mod[ 'id' ] ); // Redefine the $post global.
3507 +
3508 + } elseif ( $this->p->debug->enabled ) {
3509 +
3510 + $this->p->debug->log( 'post object id matches the post mod id' );
3511 + }
3512 + }
3513 + }
3514 +
3515 + if ( $this->p->debug->enabled ) {
3516 +
3517 + $this->p->debug->log( 'setting post data for template functions' );
3518 + }
3519 +
3520 + setup_postdata( $post );
3521 +
3522 + /*
3523 + * Prevent recursive loops and signal to other methods that the content filter is being applied to create a
3524 + * description text - this avoids the addition of unnecessary HTML which will be removed anyway (social
3525 + * sharing buttons, for example).
3526 + */
3527 + if ( $this->p->debug->enabled ) {
3528 +
3529 + $this->p->debug->log( 'setting global wpsso_doing_filter_' . $filter_name );
3530 + }
3531 +
3532 + $GLOBALS[ 'wpsso_doing_filter_' . $filter_name ] = true; // Prevent recursive loops.
3533 +
3534 + /*
3535 + * Apply the filters.
3536 + */
3537 + if ( $this->p->debug->enabled ) {
3538 +
3539 + $this->p->debug->mark( 'applying filters "' . $filter_name . '"' ); // Begin timer.
3540 + }
3541 +
3542 + $mtime_start = microtime( $get_float = true );
3543 + $filter_value = call_user_func_array( 'apply_filters', $args );
3544 + $mtime_total = microtime( $get_float = true ) - $mtime_start;
3545 +
3546 + if ( $this->p->debug->enabled ) {
3547 +
3548 + $this->p->debug->mark( 'applying filters "' . $filter_name . '"' ); // End timer.
3549 + }
3550 +
3551 + /*
3552 + * Unset the recursive loop check.
3553 + */
3554 + if ( $this->p->debug->enabled ) {
3555 +
3556 + $this->p->debug->log( 'unsetting global wpsso_doing_filter_' . $filter_name );
3557 + }
3558 +
3559 + unset( $GLOBALS[ 'wpsso_doing_filter_' . $filter_name ] ); // Un-prevent recursive loops.
3560 +
3561 + /*
3562 + * Issue warning for slow filter performance.
3563 + */
3564 + if ( $mtime_max > 0 && $mtime_total > $mtime_max ) {
3565 +
3566 + $is_wp_filter = false;
3567 +
3568 + switch ( $filter_name ) {
3569 +
3570 + case 'get_the_excerpt':
3571 + case 'the_content':
3572 + case 'the_excerpt':
3573 + case 'wp_title':
3574 +
3575 + $is_wp_filter = true;
3576 +
3577 + break;
3578 + }
3579 +
3580 + $current_url = isset( $_SERVER[ 'REQUEST_URI' ] ) ? $_SERVER[ 'REQUEST_URI' ] : '';
3581 + $wp_ref_url = $is_wp_filter ? sprintf( __( 'https://developer.wordpress.org/reference/hooks/%s/', 'wpsso' ), $filter_name ) : '';
3582 + $wp_ref_link = sprintf( '<a href="%1$s">%2$s</a>', $wp_ref_url, $filter_name );
3583 + $error_pre = sprintf( __( '%s warning:', 'wpsso' ), __METHOD__ );
3584 + $rec_max_msg = sprintf( __( 'longer than recommended max of %1$.3f secs', 'wpsso' ), $mtime_max );
3585 + $notice_msg = sprintf( __( 'Slow filter hook(s) detected - WordPress took %1$.3f secs to execute the "%2$s" filter (%3$s).', 'wpsso' ),
3586 + $mtime_total, $filter_name, $rec_max_msg );
3587 +
3588 + if ( $wp_ref_url ) $notice_msg .= ' ' . sprintf( __( 'See %s for more information.', 'wpsso' ), $wp_ref_url );
3589 + if ( $current_url ) $notice_msg .= ' ' . sprintf( __( 'Server request URI = %s', 'wpsso' ), $current_url );
3590 +
3591 + self::safe_error_log( $error_pre . ' ' . $notice_msg );
3592 +
3593 + if ( $this->p->debug->enabled ) {
3594 +
3595 + $this->p->debug->log( sprintf( 'slow filter hook(s) detected - WordPress took %1$.3f secs to execute the "%2$s" filter',
3596 + $mtime_total, $filter_name ) );
3597 + }
3598 +
3599 + if ( $this->p->notice->is_admin_pre_notices() ) {
3600 +
3601 + /*
3602 + * If this is a known WordPress filter, show a different and more complete notification message.
3603 + */
3604 + if ( $is_wp_filter ) {
3605 +
3606 + $qm_plugin_link = '<a href="https://wordpress.org/plugins/query-monitor/">Query Monitor</a>';
3607 + $option_label = _x( 'Disable Cache for Debugging', 'option label', 'wpsso' );
3608 + $option_link = $this->p->util->get_admin_url( 'advanced#sucom-tabset_plugin-tab_settings', $option_label );
3609 +
3610 + /*
3611 + * Start a new notice message.
3612 + */
3613 + $notice_msg = sprintf( __( 'Slow filter hook(s) detected - the WordPress %1$s filter took %2$.3f seconds to execute.', 'wpsso' ), $wp_ref_link, $mtime_total ) . ' ';
3614 +
3615 + $notice_msg .= sprintf( __( 'This is longer than the recommended maximum of %1$.3f seconds and may affect page load time.', 'wpsso' ), $mtime_max ) . ' ';
3616 +
3617 + $notice_msg .= sprintf( __( 'You should consider reviewing active plugin and theme functions hooked into the WordPress %1$s filter for slow and/or sub-optimal PHP code.', 'wpsso' ), $wp_ref_link ) . ' ';
3618 +
3619 + $notice_msg .= sprintf( __( 'Activating the %1$s plugin and enabling the the %2$s option (to apply the filter consistently) may provide more information on the specific hooks or PHP code affecting performance.', 'wpsso' ), $qm_plugin_link, $option_link );
3620 + }
3621 +
3622 + $notice_key = 'slow-filter-hooks-detected-' . $filter_name;
3623 +
3624 + $this->p->notice->warn( $notice_msg, null, $notice_key, $dismiss_time = DAY_IN_SECONDS );
3625 + }
3626 + }
3627 +
3628 + /*
3629 + * Restore original post object.
3630 + */
3631 + if ( $this->p->debug->enabled ) {
3632 +
3633 + $this->p->debug->log( 'restoring original post object ' . ( isset( $post_pre_filter->ID ) ? 'id ' . $post_pre_filter->ID : '(no post id)' ) );
3634 + }
3635 +
3636 + $post = $post_pre_filter; // Restore original global post object.
3637 + $wp_query = $wp_query_pre_filter; // Restore original global wp_query.
3638 +
3639 + if ( $this->p->debug->enabled ) {
3640 +
3641 + $this->p->debug->log( 'restoring post data for template functions' );
3642 + }
3643 +
3644 + setup_postdata( $post );
3645 +
3646 + /*
3647 + * Remove the Block Filter Output (BFO) filters.
3648 + */
3649 + if ( $use_bfo ) {
3650 +
3651 + $bfo_obj->remove_all_hooks( array( $filter_name ) );
3652 + }
3653 +
3654 + /*
3655 + * Hooked by some modules to perform actions before/after filtering the content.
3656 + */
3657 + do_action( 'wpsso_after_apply_filters_text', $filter_name );
3658 +
3659 + if ( $this->p->debug->enabled ) {
3660 +
3661 + $this->p->debug->log( 'returning filtered value' );
3662 + }
3663 +
3664 + return $filter_value;
3665 + }
3666 +
3667 + public function get_admin_url( $menu_id = '', $link_text = '', $menu_lib = '' ) {
3668 +
3669 + $hash = '';
3670 + $query = '';
3671 + $admin_url = '';
3672 + $current_url = isset( $_SERVER[ 'REQUEST_URI' ] ) ? $_SERVER[ 'REQUEST_URI' ] : '';
3673 +
3674 + /*
3675 + * $menu_id may start with a hash or query, so parse before checking its value.
3676 + */
3677 + if ( false !== strpos( $menu_id, '#' ) ) {
3678 +
3679 + list( $menu_id, $hash ) = explode( '#', $menu_id );
3680 + }
3681 +
3682 + if ( false !== strpos( $menu_id, '?' ) ) {
3683 +
3684 + list( $menu_id, $query ) = explode( '?', $menu_id );
3685 + }
3686 +
3687 + if ( empty( $menu_id ) ) {
3688 +
3689 + if ( preg_match( '/^.*\?page=wpsso-([^&]*).*$/', $current_url, $match ) ) {
3690 +
3691 + $menu_id = $match[ 1 ];
3692 +
3693 + } else $menu_id = key( $this->p->cf[ '*' ][ 'lib' ][ 'submenu' ] ); // Default to first submenu.
3694 + }
3695 +
3696 + /*
3697 + * Find the menu_lib value for this menu_id.
3698 + */
3699 + if ( empty( $menu_lib ) ) {
3700 +
3701 + foreach ( $this->p->cf[ '*' ][ 'lib' ] as $menu_lib => $menu ) {
3702 +
3703 + if ( isset( $menu[ $menu_id ] ) ) {
3704 +
3705 + break;
3706 +
3707 + } else {
3708 +
3709 + $menu_lib = '';
3710 + }
3711 + }
3712 + }
3713 +
3714 + if ( empty( $menu_lib ) || empty( $this->p->cf[ 'wp' ][ 'admin' ][ $menu_lib ][ 'page' ] ) ) {
3715 +
3716 + return;
3717 + }
3718 +
3719 + $parent_slug = $this->p->cf[ 'wp' ][ 'admin' ][ $menu_lib ][ 'page' ] . '?page=wpsso-' . $menu_id;
3720 +
3721 + switch ( $menu_lib ) {
3722 +
3723 + case 'sitesubmenu':
3724 +
3725 + $admin_url = network_admin_url( $parent_slug );
3726 +
3727 + break;
3728 +
3729 + default:
3730 +
3731 + $admin_url = admin_url( $parent_slug );
3732 +
3733 + break;
3734 + }
3735 +
3736 + if ( ! empty( $query ) ) {
3737 +
3738 + $admin_url .= '&' . $query;
3739 + }
3740 +
3741 + if ( ! empty( $hash ) ) {
3742 +
3743 + /*
3744 + * If we have anchor text, force a page reload for the anchor, in case we're on the same page.
3745 + *
3746 + * Use the same random query for all admin URLs during a single page load so the SucomNotice "show
3747 + * once" feature will work correctly.
3748 + */
3749 + static $rand_arg = null;
3750 +
3751 + if ( null === $rand_arg ) {
3752 +
3753 + $rand_arg = wp_rand( 100000, 999999 );
3754 + }
3755 +
3756 + $admin_url .= '&' . $rand_arg;
3757 +
3758 + $admin_url .= '#' . $hash;
3759 + }
3760 +
3761 + if ( empty( $link_text ) ) {
3762 +
3763 + return $admin_url;
3764 +
3765 + }
3766 +
3767 + $html = '<a href="' . $admin_url . '">' . $link_text . '</a>';
3768 +
3769 + return $html;
3770 + }
3771 +
3772 + /*
3773 + * Rename options array keys, preserving the option modifiers (ie. '_[0-9]', ':disabled', ':use', and '#.*').
3774 + */
3775 + public function rename_options_by_ext( array $opts, array $version_keys ) {
3776 +
3777 + foreach ( $this->p->cf[ 'plugin' ] as $ext => $info ) {
3778 +
3779 + if ( ! isset( $version_keys[ $ext ] ) ) { // Nothing to do.
3780 +
3781 + continue;
3782 +
3783 + } elseif ( ! is_array( $version_keys[ $ext ] ) ) { // Must be an array of option keys.
3784 +
3785 + continue;
3786 +
3787 + } elseif ( ! isset( $info[ 'opt_version' ] ) ) { // Nothing to compare to.
3788 +
3789 + continue;
3790 +
3791 + } elseif ( empty( $opts[ 'opt_versions' ][ $ext ] ) ) { // No previous options.
3792 +
3793 + continue;
3794 + }
3795 +
3796 + $prev_version = $opts[ 'opt_versions' ][ $ext ];
3797 +
3798 + foreach ( $version_keys[ $ext ] as $max_version => $opt_keys ) {
3799 +
3800 + if ( is_numeric( $max_version ) && is_array( $opt_keys ) && $prev_version <= $max_version ) {
3801 +
3802 + $opts = $this->rename_options_keys( $opts, $opt_keys );
3803 +
3804 + $opts[ 'opt_versions' ][ $ext ] = $info[ 'opt_version' ]; // Mark as current.
3805 + }
3806 + }
3807 + }
3808 +
3809 + return $opts;
3810 + }
3811 +
3812 + public function rename_options_keys( array $opts, array $opt_keys ) {
3813 +
3814 + foreach ( $opt_keys as $old_key => $new_key ) {
3815 +
3816 + if ( empty( $old_key ) ) { // Just in case.
3817 +
3818 + continue;
3819 + }
3820 +
3821 + $old_key_preg = '/^' . $old_key . '(:disabled|:use|#.*|_[0-9]+)?$/';
3822 +
3823 + foreach ( preg_grep( $old_key_preg, array_keys( $opts ) ) as $old_key_matched ) {
3824 +
3825 + if ( ! empty( $new_key ) ) { // Can be empty to remove the option.
3826 +
3827 + $new_key_matched = preg_replace( $old_key_preg, $new_key . '$1', $old_key_matched );
3828 +
3829 + $opts[ $new_key_matched ] = $opts[ $old_key_matched ]; // Preserve the old option value.
3830 + }
3831 +
3832 + unset( $opts[ $old_key_matched ] );
3833 + }
3834 + }
3835 +
3836 + return $opts;
3837 + }
3838 +
3839 + /*
3840 + * limit_text_length() uses PHP's multibyte functions (mb_strlen and mb_substr) for UTF8.
3841 + */
3842 + public function limit_text_length( $text, $maxlen = 300, $trailing = '', $cleanup_html = true ) {
3843 +
3844 + static $charset = null;
3845 +
3846 + if ( null === $charset ) {
3847 +
3848 + $charset = get_bloginfo( $show = 'charset', $filter = 'raw' );
3849 + }
3850 +
3851 + if ( $cleanup_html ) {
3852 +
3853 + $text = $this->cleanup_html_tags( $text ); // Remove any remaining html tags.
3854 + }
3855 +
3856 + $text = html_entity_decode( self::decode_utf8( $text ), ENT_QUOTES, $charset );
3857 +
3858 + if ( $maxlen > 0 && function_exists( 'mb_strlen' ) && function_exists( 'mb_substr' ) ) {
3859 +
3860 + if ( mb_strlen( $trailing ) > $maxlen ) { // Just in case.
3861 +
3862 + $trailing = mb_substr( $trailing, 0, $maxlen ); // Trim the trailing string, if too long.
3863 + }
3864 +
3865 + if ( mb_strlen( $text ) > $maxlen ) {
3866 +
3867 + $adj_max_len = $maxlen - mb_strlen( $trailing );
3868 +
3869 + $text = mb_substr( $text, 0, $adj_max_len );
3870 + $text = trim( preg_replace( '/[^ ]*$/', '', $text ) ); // Remove trailing bits of words.
3871 + $text = preg_replace( '/[,\.]*$/', '', $text ); // Remove trailing puntuation.
3872 +
3873 + } else {
3874 +
3875 + $trailing = ''; // Truncate trailing string if text is less than maxlen.
3876 + }
3877 +
3878 + $text = $text . $trailing; // Trim and add trailing string (if provided).
3879 + }
3880 +
3881 + $text = preg_replace( '/&nbsp;/', ' ', $text ); // Just in case.
3882 +
3883 + return $text;
3884 + }
3885 +
3886 + public function cleanup_html_tags( $text, $strip_tags = true, $use_img_alt = false ) {
3887 +
3888 + $text = SucomUtil::strip_shortcodes( $text ); // Remove any remaining shortcodes.
3889 + $text = preg_replace( '/[\s\n\r]+/s', ' ', $text ); // Put everything on one line.
3890 + $text = preg_replace( '/<\?.*\?'.'>/U', ' ', $text ); // Remove php.
3891 + $text = preg_replace( '/<script\b[^>]*>(.*)<\/script>/Ui', ' ', $text ); // Remove javascript.
3892 + $text = preg_replace( '/<style\b[^>]*>(.*)<\/style>/Ui', ' ', $text ); // Remove inline stylesheets.
3893 +
3894 + /*
3895 + * Maybe remove text between ignore markers.
3896 + */
3897 + if ( false !== strpos( $text, 'wpsso-ignore' ) ) {
3898 +
3899 + $text = preg_replace( '/<!-- *wpsso-ignore *-->.*<!-- *\/wpsso-ignore *-->/U', ' ', $text );
3900 + }
3901 +
3902 + /*
3903 + * Similar to SucomUtil::strip_html(), but includes image alt tags.
3904 + */
3905 + if ( $strip_tags ) {
3906 +
3907 + /*
3908 + * Add missing dot to buttons, headers, lists, etc.
3909 + */
3910 + $text = preg_replace( '/([\w])<\/(button|dt|h[0-9]+|li|th)>/i', '$1. ', $text );
3911 +
3912 + /*
3913 + * Replace list and paragraph tags with a space.
3914 + */
3915 + $text = preg_replace( '/(<li[^>]*>|<p[^>]*>|<\/p>)/i', ' ', $text );
3916 +
3917 + /*
3918 + * Remove remaining html tags.
3919 + */
3920 + $text_stripped = trim( strip_tags( $text ) );
3921 +
3922 + /*
3923 + * Possibly use img alt strings if no text.
3924 + */
3925 + if ( '' === $text_stripped && $use_img_alt && false !== strpos( $text, '<img ' ) ) {
3926 +
3927 + $alt_text = '';
3928 + $alt_prefix = $this->p->opt->get_text( 'plugin_img_alt_prefix' );
3929 +
3930 + if ( preg_match_all( '/<img [^>]*alt=["\']([^"\'>]*)["\']/Ui', $text, $all_matches, PREG_PATTERN_ORDER ) ) {
3931 +
3932 + foreach ( $all_matches[ 1 ] as $alt ) {
3933 +
3934 + $alt = trim( $alt );
3935 +
3936 + if ( ! empty( $alt ) ) {
3937 +
3938 + $alt = empty( $alt_prefix ) ? $alt : $alt_prefix . ' ' . $alt;
3939 +
3940 + /*
3941 + * Maybe add a period after the image alt text.
3942 + */
3943 + $alt_text .= ( strpos( $alt, '.' ) + 1 ) === strlen( $alt ) ? $alt . ' ' : $alt . '. ';
3944 + }
3945 + }
3946 +
3947 + if ( $this->p->debug->enabled ) {
3948 +
3949 + $this->p->debug->log( 'img alt text: ' . $alt_text );
3950 + }
3951 + }
3952 +
3953 + $text = $alt_text;
3954 +
3955 + } else {
3956 +
3957 + $text = $text_stripped;
3958 + }
3959 + }
3960 +
3961 + /*
3962 + * Replace 1+ spaces to a single space.
3963 + */
3964 + $text = preg_replace( '/(\xC2\xA0|\s)+/s', ' ', $text );
3965 +
3966 + return trim( $text );
3967 + }
3968 +
3969 + public function get_validators( array $mod, $form = null ) {
3970 +
3971 + /*
3972 + * We do not want to validate settings pages in the back-end, so validators are only provided for known
3973 + * modules (comment, post, term, and user). If we're on the front-end, validating the current webpage URL
3974 + * is fine.
3975 + */
3976 + if ( is_admin() ) {
3977 +
3978 + if ( empty( $mod[ 'obj' ] ) ) {
3979 +
3980 + return array();
3981 + }
3982 + }
3983 +
3984 + $can_crawl_url = true;
3985 + $canonical_url = $this->p->util->get_canonical_url( $mod );
3986 +
3987 + if ( empty( $canonical_url ) ) {
3988 +
3989 + $can_crawl_url = false;
3990 +
3991 + } elseif ( ! $mod[ 'is_public' ] ) {
3992 +
3993 + $can_crawl_url = false;
3994 +
3995 + } elseif ( $mod[ 'is_post' ] ) {
3996 +
3997 + if ( 'publish' !== $mod[ 'post_status' ] ) {
3998 +
3999 + $can_crawl_url = false;
4000 + }
4001 + }
4002 +
4003 + $have_amp = $mod[ 'is_post' ] && $mod[ 'id' ] && function_exists( 'amp_get_permalink' ) ? true : false;
4004 + $have_schema = $this->p->avail[ 'p' ][ 'schema' ] ? true : false;
4005 + $amp_url_enc = $have_amp ? urlencode( amp_get_permalink( $mod[ 'id' ] ) ) : '';
4006 + $canonical_url_enc = urlencode( $canonical_url );
4007 +
4008 + $validators = array(
4009 + 'facebook-debugger' => array(
4010 + 'title' => _x( 'Facebook Sharing Debugger', 'option label', 'wpsso' ),
4011 + 'type' => _x( 'Open Graph', 'validator type', 'wpsso' ),
4012 + 'url' => 'https://developers.facebook.com/tools/debug/?q=' . $canonical_url_enc,
4013 + ),
4014 + 'facebook-microdata' => array(
4015 + 'title' => _x( 'Facebook Microdata Debug Tool', 'option label', 'wpsso' ),
4016 + 'type' => _x( 'Microdata', 'validator type', 'wpsso' ),
4017 + 'url' => 'https://business.facebook.com/ads/microdata/debug?url=' . $canonical_url_enc,
4018 + ),
4019 + 'google-amp' => array(
4020 + 'title' => _x( 'Google AMP Test', 'option label', 'wpsso' ),
4021 + 'type' => _x( 'AMP Markup', 'validator type', 'wpsso' ) . ( $have_amp ? '' : ' *' ),
4022 + 'url' => $have_amp ? 'https://search.google.com/test/amp/result?url=' . $amp_url_enc : '',
4023 + ),
4024 + 'google-page-speed' => array(
4025 + 'title' => _x( 'Google PageSpeed Insights', 'option label', 'wpsso' ),
4026 + 'type' => _x( 'PageSpeed', 'validator type', 'wpsso' ),
4027 + 'url' => 'https://pagespeed.web.dev/report?url=' . $canonical_url_enc,
4028 + ),
4029 + 'google-rich-results' => array(
4030 + 'title' => _x( 'Google Rich Results Test', 'option label', 'wpsso' ),
4031 + 'type' => _x( 'Rich Results', 'validator type', 'wpsso' ) . ( $have_schema ? '' : ' **' ),
4032 + 'url' => $have_schema ? 'https://search.google.com/test/rich-results?url=' . $canonical_url_enc : '',
4033 + ),
4034 + 'linkedin' => array(
4035 + 'title' => _x( 'LinkedIn Post Inspector', 'option label', 'wpsso' ),
4036 + 'type' => _x( 'oEmbed Data', 'validator type', 'wpsso' ),
4037 + 'url' => 'https://www.linkedin.com/post-inspector/inspect/' . $canonical_url_enc,
4038 + ),
4039 + 'pinterest' => array(
4040 + 'title' => _x( 'Pinterest Rich Pins Validator', 'option label', 'wpsso' ),
4041 + 'type' => _x( 'Rich Pins', 'validator type', 'wpsso' ),
4042 + 'url' => 'https://developers.pinterest.com/tools/url-debugger/?link=' . $canonical_url_enc,
4043 + ),
4044 + 'schema-markup-validator' => array(
4045 + 'title' => _x( 'Schema Markup Validator', 'option label', 'wpsso' ),
4046 + 'type' => _x( 'Schema Markup', 'validator type', 'wpsso' ) . ( $have_schema ? '' : ' *' ),
4047 + 'url' => $have_schema ? 'https://validator.schema.org/#url=' . $canonical_url_enc : '',
4048 + ),
4049 + 'twitter' => array(
4050 + 'title' => _x( 'X (Twitter) Card Validator', 'option label', 'wpsso' ),
4051 + 'type' => _x( 'X (Twitter) Card', 'validator type', 'wpsso' ),
4052 + 'url' => is_object( $form ) ? 'https://cards-dev.twitter.com/validator' : '',
4053 + 'extra_msg' => is_object( $form ) ? $form->get_no_input_clipboard( $canonical_url ) : '',
4054 + ),
4055 + 'w3c' => array(
4056 + 'title' => _x( 'W3C Markup Validator', 'option label', 'wpsso' ),
4057 + 'type' => _x( 'HTML Markup', 'validator type', 'wpsso' ),
4058 + 'url' => 'https://validator.w3.org/nu/?doc=' . $canonical_url_enc,
4059 + ),
4060 + );
4061 +
4062 + if ( ! $can_crawl_url ) {
4063 +
4064 + foreach ( $validators as $key => $arr ) {
4065 +
4066 + $validators[ $key ][ 'url' ] = '';
4067 + }
4068 + }
4069 +
4070 + return $validators;
4071 + }
4072 +
4073 + /*
4074 + * Maybe set the notice reference URL and translated message.
4075 + *
4076 + * Example messages, depending on the $mod array:
4077 + *
4078 + * adding schema organization
4079 + * adding schema organization for this page
4080 + * adding schema organization for page ID 123
4081 + */
4082 + public function maybe_set_ref( $canonical_url = null, $mod = false, $msg_transl = '' ) {
4083 +
4084 + static $is_admin = null;
4085 +
4086 + if ( null === $is_admin ) {
4087 +
4088 + $is_admin = is_admin();
4089 + }
4090 +
4091 + if ( ! $is_admin ) {
4092 +
4093 + return false;
4094 + }
4095 +
4096 + if ( empty( $canonical_url ) ) {
4097 +
4098 + $canonical_url = $this->get_canonical_url( $mod );
4099 + }
4100 +
4101 + if ( empty( $msg_transl ) ) {
4102 +
4103 + return $this->p->notice->set_ref( $canonical_url, $mod );
4104 + }
4105 +
4106 + if ( empty( $mod[ 'id' ] ) ) {
4107 +
4108 + return $this->p->notice->set_ref( $canonical_url, $mod, $msg_transl );
4109 + }
4110 +
4111 + if ( $mod[ 'is_post' ] && $mod[ 'post_type_label_single' ] ) {
4112 +
4113 + $name_transl = mb_strtolower( $mod[ 'post_type_label_single' ] );
4114 +
4115 + } elseif ( $mod[ 'is_term' ] && $mod[ 'tax_label_single' ] ) {
4116 +
4117 + $name_transl = mb_strtolower( $mod[ 'tax_label_single' ] );
4118 +
4119 + } else {
4120 +
4121 + $name_transl = $mod[ 'name_transl' ]; // Translated module name.
4122 + }
4123 +
4124 + if ( SucomUtilWP::is_mod_screen_obj( $mod ) ) {
4125 +
4126 + // translators: %1$s is an action message, %2$s is the module or post type name.
4127 + $msg_transl = sprintf( __( '%1$s for this %2$s', 'wpsso' ), $msg_transl, $name_transl );
4128 +
4129 + /*
4130 + * Exclude the $mod array to avoid adding an 'Edit' link to the notice message.
4131 + */
4132 + return $this->p->notice->set_ref( $canonical_url, false, $msg_transl );
4133 +
4134 + }
4135 +
4136 + // translators: %1$s is an action message, %2$s is the module or post type name and %3$d is the object ID.
4137 + $msg_transl = sprintf( __( '%1$s for %2$s ID %3$d', 'wpsso' ), $msg_transl, $name_transl, $mod[ 'id' ] );
4138 +
4139 + return $this->p->notice->set_ref( $canonical_url, $mod, $msg_transl );
4140 + }
4141 +
4142 + public function maybe_unset_ref( $canonical_url ) {
4143 +
4144 + static $is_admin = null;
4145 +
4146 + if ( null === $is_admin ) {
4147 +
4148 + $is_admin = is_admin();
4149 + }
4150 +
4151 + if ( ! $is_admin ) {
4152 +
4153 + return false;
4154 + }
4155 +
4156 + return $this->p->notice->unset_ref( $canonical_url );
4157 + }
4158 +
4159 + public function count_cron_jobs() {
4160 +
4161 + $count = 0;
4162 +
4163 + $cron_jobs = get_option( 'cron' );
4164 +
4165 + if ( is_array( $cron_jobs ) ) {
4166 +
4167 + foreach ( $cron_jobs as $cron_id => $cron_el ) {
4168 +
4169 + if ( is_array( $cron_el ) ) {
4170 +
4171 + foreach ( $cron_el as $sched_id => $sched_el ) {
4172 +
4173 + $count++;
4174 + }
4175 + }
4176 + }
4177 + }
4178 +
4179 + return $count;
4180 + }
4181 +
4182 + /*
4183 + * See WpssoCmcfXml->get().
4184 + * See WpssoGmfXml->get().
4185 + * See WpssoAdmin->settings_sanitation().
4186 + * See WpssoAdmin->show_metabox_cache_status().
4187 + * See WpssoHead->get_head_array().
4188 + * See WpssoMessagesTooltipPlugin->get().
4189 + * See WpssoPage->get_the_content().
4190 + * See WpssoProMediaFacebook->filter_video_details().
4191 + * See WpssoProMediaSlideshare->filter_video_details().
4192 + * See WpssoProMediaVimeo->filter_video_details().
4193 + * See WpssoProMediaWistia->filter_video_details().
4194 + * See WpssoProMediaYoutube->filter_video_details().
4195 + * See WpssoProReviewJudgeme->filter_og().
4196 + * See WpssoProReviewShopperApproved->filter_og().
4197 + * See WpssoProReviewStamped->filter_og().
4198 + * See WpssoProUtilShorten->get_short_url().
4199 + * See WpssoSchema->get_schema_types().
4200 + * See WpssoSchema->get_schema_type_child_family().
4201 + * See WpssoSchema->get_schema_type_children().
4202 + * See WpssoSchema->get_schema_type_row_class().
4203 + * See WpssoUtil->get_image_url_info().
4204 + */
4205 + public function get_cache_exp_secs( $cache_key, $cache_type = 'transient', $mod = false ) {
4206 +
4207 + return $this->cache->get_cache_exp_secs( $cache_key, $cache_type, $mod );
4208 + }
4209 + }
4210 +}