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

util.php in WPSSO Core – Complete Schema Markup and Meta Tags trunk, at lib/com/util.php

2,703 lines 71.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * License: GPLv3
4 * License URI: https://www.gnu.org/licenses/gpl.txt
5 * Copyright 2012-2026 Jean-Sebastien Morisset (https://surniaulula.com/)
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9
10 die( 'These aren\'t the droids you\'re looking for.' );
11 }
12
13 if ( ! class_exists( 'SucomUtil' ) ) {
14
15 class SucomUtil {
16
17 protected static $url_clean_query = array(
18
19 // Age Verify plugin:
20 'age-verified',
21
22 // Autoptimize:
23 'ao_noptimize',
24
25 // AMP:
26 'usqp',
27
28 // Cookie Notice:
29 'cn-reloaded',
30
31 // ShareASale:
32 'sscid',
33
34 // Adobe Advertising Cloud:
35 'ef_id',
36
37 // Adobe Analytics:
38 's_kwcid',
39
40 // Bronto:
41 '_bta_tid',
42 '_bta_c',
43
44 // Dotdigital:
45 'dm_i',
46
47 // Facebook:
48 'fb_action_ids',
49 'fb_action_types',
50 'fb_source',
51 'fbclid',
52
53 // Google Analytics and Ads:
54 'utm_source',
55 'utm_campaign',
56 'utm_medium',
57 'utm_expid',
58 'utm_term',
59 'utm_content',
60 '_ga',
61 'gclid',
62 'campaignid',
63 'adgroupid',
64 'adid',
65 'gbraid',
66 'wbraid',
67
68 // Google Web Stories:
69 '_gl',
70
71 // Google DoubleClick:
72 'gclsrc',
73
74 // GoDataFeed:
75 'gdfms',
76 'gdftrk',
77 'gdffi',
78
79 // Klaviyo
80 '_ke',
81
82 // Listrak:
83 'trk_contact',
84 'trk_msg',
85 'trk_module',
86 'trk_sid',
87
88 // Mailchimp:
89 'mc_cid',
90 'mc_eid',
91
92 // Marin:
93 'mkwid',
94 'pcrid',
95
96 // Matomo:
97 'mtm_source',
98 'mtm_medium',
99 'mtm_campaign',
100 'mtm_keyword',
101 'mtm_cid',
102 'mtm_content',
103
104 // Microsoft Advertising:
105 'msclkid',
106
107 // Pinterest:
108 'epik',
109 'pp',
110
111 // Piwik Pro:
112 'pk_source',
113 'pk_medium',
114 'pk_campaign',
115 'pk_keyword',
116 'pk_cid',
117 'pk_content',
118
119 // Springbot:
120 'redirect_log_mongo_id',
121 'redirect_mongo_id',
122 'sb_referer_host',
123
124 // WordPress:
125 'doing_wp_cron',
126 );
127
128 protected static $publisher_languages = array(
129
130 /*
131 * https://developers.facebook.com/docs/messenger-platform/messenger-profile/supported-locales
132 */
133 'facebook' => array(
134 'af_ZA' => 'Afrikaans',
135 'ar_AR' => 'Arabic',
136 'as_IN' => 'Assamese',
137 'az_AZ' => 'Azerbaijani',
138 'be_BY' => 'Belarusian',
139 'bg_BG' => 'Bulgarian',
140 'bn_IN' => 'Bengali',
141 'br_FR' => 'Breton',
142 'bs_BA' => 'Bosnian',
143 'ca_ES' => 'Catalan',
144 'cb_IQ' => 'Sorani Kurdish',
145 'co_FR' => 'Corsican',
146 'cs_CZ' => 'Czech',
147 'cx_PH' => 'Cebuano',
148 'cy_GB' => 'Welsh',
149 'da_DK' => 'Danish',
150 'de_DE' => 'German',
151 'el_GR' => 'Greek',
152 'en_GB' => 'English (UK)',
153 'en_UD' => 'English (Upside Down)',
154 'en_US' => 'English (US)',
155 'es_ES' => 'Spanish (Spain)',
156 'es_LA' => 'Spanish',
157 'et_EE' => 'Estonian',
158 'eu_ES' => 'Basque',
159 'fa_IR' => 'Persian',
160 'ff_NG' => 'Fulah',
161 'fi_FI' => 'Finnish',
162 'fo_FO' => 'Faroese',
163 'fr_CA' => 'French (Canada)',
164 'fr_FR' => 'French (France)',
165 'fy_NL' => 'Frisian',
166 'ga_IE' => 'Irish',
167 'gl_ES' => 'Galician',
168 'gn_PY' => 'Guarani',
169 'gu_IN' => 'Gujarati',
170 'ha_NG' => 'Hausa',
171 'he_IL' => 'Hebrew',
172 'hi_IN' => 'Hindi',
173 'hr_HR' => 'Croatian',
174 'hu_HU' => 'Hungarian',
175 'hy_AM' => 'Armenian',
176 'id_ID' => 'Indonesian',
177 'is_IS' => 'Icelandic',
178 'it_IT' => 'Italian',
179 'ja_JP' => 'Japanese',
180 'ja_KS' => 'Japanese (Kansai)',
181 'jv_ID' => 'Javanese',
182 'ka_GE' => 'Georgian',
183 'kk_KZ' => 'Kazakh',
184 'km_KH' => 'Khmer',
185 'kn_IN' => 'Kannada',
186 'ko_KR' => 'Korean',
187 'ku_TR' => 'Kurdish (Kurmanji)',
188 'lt_LT' => 'Lithuanian',
189 'lv_LV' => 'Latvian',
190 'mg_MG' => 'Malagasy',
191 'mk_MK' => 'Macedonian',
192 'ml_IN' => 'Malayalam',
193 'mn_MN' => 'Mongolian',
194 'mr_IN' => 'Marathi',
195 'ms_MY' => 'Malay',
196 'mt_MT' => 'Maltese',
197 'my_MM' => 'Burmese',
198 'nb_NO' => 'Norwegian (bokmal)',
199 'ne_NP' => 'Nepali',
200 'nl_BE' => 'Dutch (België)',
201 'nl_NL' => 'Dutch',
202 'nn_NO' => 'Norwegian (nynorsk)',
203 'or_IN' => 'Oriya',
204 'pa_IN' => 'Punjabi',
205 'pl_PL' => 'Polish',
206 'ps_AF' => 'Pashto',
207 'pt_BR' => 'Portuguese (Brazil)',
208 'pt_PT' => 'Portuguese (Portugal)',
209 'ro_RO' => 'Romanian',
210 'ru_RU' => 'Russian',
211 'rw_RW' => 'Kinyarwanda',
212 'sc_IT' => 'Sardinian',
213 'si_LK' => 'Sinhala',
214 'sk_SK' => 'Slovak',
215 'sl_SI' => 'Slovenian',
216 'so_SO' => 'Somali',
217 'sq_AL' => 'Albanian',
218 'sr_RS' => 'Serbian',
219 'sv_SE' => 'Swedish',
220 'sw_KE' => 'Swahili',
221 'sz_PL' => 'Silesian',
222 'ta_IN' => 'Tamil',
223 'te_IN' => 'Telugu',
224 'tg_TJ' => 'Tajik',
225 'th_TH' => 'Thai',
226 'tl_PH' => 'Filipino',
227 'tr_TR' => 'Turkish',
228 'tz_MA' => 'Tamazight',
229 'uk_UA' => 'Ukrainian',
230 'ur_PK' => 'Urdu',
231 'uz_UZ' => 'Uzbek',
232 'vi_VN' => 'Vietnamese',
233 'zh_CN' => 'Simplified Chinese (China)',
234 'zh_HK' => 'Traditional Chinese (Hong Kong)',
235 'zh_TW' => 'Traditional Chinese (Taiwan)',
236 ),
237
238 /*
239 * https://developers.google.com/+/web/api/supported-languages
240 */
241 'google' => array(
242 'af' => 'Afrikaans',
243 'am' => 'Amharic',
244 'ar' => 'Arabic',
245 'eu' => 'Basque',
246 'bn' => 'Bengali',
247 'bg' => 'Bulgarian',
248 'ca' => 'Catalan',
249 'zh-CN' => 'Chinese (Simplified)',
250 'zh-HK' => 'Chinese (Hong Kong)',
251 'zh-TW' => 'Chinese (Traditional)',
252 'hr' => 'Croatian',
253 'cs' => 'Czech',
254 'da' => 'Danish',
255 'nl' => 'Dutch',
256 'en-GB' => 'English (UK)',
257 'en-US' => 'English (US)',
258 'et' => 'Estonian',
259 'fil' => 'Filipino',
260 'fi' => 'Finnish',
261 'fr' => 'French',
262 'fr-CA' => 'French (Canadian)',
263 'gl' => 'Galician',
264 'de' => 'German',
265 'el' => 'Greek',
266 'gu' => 'Gujarati',
267 'iw' => 'Hebrew',
268 'hi' => 'Hindi',
269 'hu' => 'Hungarian',
270 'is' => 'Icelandic',
271 'id' => 'Indonesian',
272 'it' => 'Italian',
273 'ja' => 'Japanese',
274 'kn' => 'Kannada',
275 'ko' => 'Korean',
276 'lv' => 'Latvian',
277 'lt' => 'Lithuanian',
278 'ms' => 'Malay',
279 'ml' => 'Malayalam',
280 'mr' => 'Marathi',
281 'no' => 'Norwegian',
282 'fa' => 'Persian',
283 'pl' => 'Polish',
284 'pt-BR' => 'Portuguese (Brazil)',
285 'pt-PT' => 'Portuguese (Portugal)',
286 'ro' => 'Romanian',
287 'ru' => 'Russian',
288 'sr' => 'Serbian',
289 'sk' => 'Slovak',
290 'sl' => 'Slovenian',
291 'es' => 'Spanish',
292 'es-419' => 'Spanish (Latin America)',
293 'sw' => 'Swahili',
294 'sv' => 'Swedish',
295 'ta' => 'Tamil',
296 'te' => 'Telugu',
297 'th' => 'Thai',
298 'tr' => 'Turkish',
299 'uk' => 'Ukrainian',
300 'ur' => 'Urdu',
301 'vi' => 'Vietnamese',
302 'zu' => 'Zulu',
303 ),
304 'pinterest' => array(
305 'en' => 'English',
306 'ja' => 'Japanese',
307 ),
308
309 /*
310 * https://dev.twitter.com/web/overview/languages
311 */
312 'twitter' => array(
313 'ar' => 'Arabic',
314 'bn' => 'Bengali',
315 'zh-tw' => 'Chinese (Traditional)',
316 'zh-cn' => 'Chinese (Simplified)',
317 'cs' => 'Czech',
318 'da' => 'Danish',
319 'en' => 'English',
320 'de' => 'German',
321 'el' => 'Greek',
322 'fi' => 'Finnish',
323 'fil' => 'Filipino',
324 'fr' => 'French',
325 'he' => 'Hebrew',
326 'hi' => 'Hindi',
327 'hu' => 'Hungarian',
328 'id' => 'Indonesian',
329 'it' => 'Italian',
330 'ja' => 'Japanese',
331 'ko' => 'Korean',
332 'msa' => 'Malay',
333 'nl' => 'Dutch',
334 'no' => 'Norwegian',
335 'fa' => 'Persian',
336 'pl' => 'Polish',
337 'pt' => 'Portuguese',
338 'ro' => 'Romanian',
339 'ru' => 'Russian',
340 'es' => 'Spanish',
341 'sv' => 'Swedish',
342 'th' => 'Thai',
343 'tr' => 'Turkish',
344 'uk' => 'Ukrainian',
345 'ur' => 'Urdu',
346 'vi' => 'Vietnamese',
347 )
348 );
349
350 public function __construct() {}
351
352 /*
353 * ARRAY HANDLING METHODS:
354 *
355 * add_after_key()
356 * add_before_key()
357 * add_multi_values()
358 * array_count_diff()
359 * array_flatten()
360 * array_implode()
361 * array_insert_element()
362 * array_key_last()
363 * array_map_recursive()
364 * array_maybe_unserialize()
365 * array_merge_recursive_distinct()
366 * array_slice_fifo()
367 * array_to_hashtags()
368 * array_to_keywords()
369 * get_array_or_element()
370 * get_array_pretty()
371 * get_assoc_salt()
372 * get_multi_values()
373 * move_to_front()
374 * move_to_end()
375 * natasort()
376 * natksort()
377 * next_key()
378 * preg_grep_keys()
379 * unset_keys()
380 * unset_numeric_keys()
381 *
382 * Modify the referenced array and return true or false.
383 */
384 public static function add_after_key( array &$arr, $match_key, $arr_or_key, $value = null ) {
385
386 return self::array_insert_element( $arr, $insert = 'after', $match_key, $arr_or_key, $value );
387 }
388
389 /*
390 * Modify the referenced array and return true or false.
391 */
392 public static function add_before_key( array &$arr, $match_key, $arr_or_key, $value = null ) {
393
394 return self::array_insert_element( $arr, $insert = 'before', $match_key, $arr_or_key, $value );
395 }
396
397 /*
398 * Add an array of values that have a common numbered prefix key.
399 */
400 public static function add_multi_values( array &$opts, $opt_pre, $new_key = null ) {
401
402 if ( null === $new_key ) $new_key = $opt_pre;
403
404 $opts[ $new_key ] = self::get_multi_values( $opts, $opt_pre );
405 }
406
407 public static function array_count_diff( array $arr, $max = 0 ) {
408
409 $diff = 0;
410
411 if ( $max > 0 && $max >= count( $arr ) ) {
412
413 $diff = $max - count( $arr );
414 }
415
416 return $diff;
417 }
418
419 /*
420 * Convert a multi-dimentional array to a single dimension array.
421 */
422 public static function array_flatten( array $arr ) {
423
424 $flattened = array();
425
426 foreach ( $arr as $key => $value ) {
427
428 if ( is_array( $value ) ) {
429
430 $flattened = array_merge( $flattened, self::array_flatten( $value ) );
431
432 } else $flattened[ $key ] = $value;
433 }
434
435 return $flattened;
436 }
437
438 /*
439 * Implode multi-dimentional array.
440 */
441 public static function array_implode( array $arr, $glue = ' ' ) {
442
443 $imploded = '';
444
445 foreach ( $arr as $value ) {
446
447 if ( is_array( $value ) ) {
448
449 $imploded .= self::array_implode( $value, $glue ) . $glue;
450
451 } else $imploded .= $value . $glue;
452 }
453
454 return strlen( $glue ) ? rtrim( $imploded, $glue ) : $glue;
455 }
456
457 /*
458 * Modify the referenced array and return true or false.
459 */
460 public static function array_insert_element( array &$arr, $insert, $match_key, $arr_or_key, $value = null ) {
461
462 $found_match = false;
463
464 if ( array_key_exists( $match_key, $arr ) ) {
465
466 $new_arr = array();
467
468 foreach ( $arr as $key => $val ) {
469
470 if ( 'after' === $insert ) {
471
472 $new_arr[ $key ] = $val;
473 }
474
475 /*
476 * Add new value before/after the matched key.
477 *
478 * Replace the matched key by default (no test required).
479 */
480 if ( $key === $match_key ) {
481
482 if ( is_array( $arr_or_key ) ) {
483
484 $new_arr = array_merge( $new_arr, $arr_or_key );
485
486 } elseif ( is_string( $arr_or_key ) ) {
487
488 $new_arr[ $arr_or_key ] = $value;
489
490 } else $new_arr[] = $value;
491
492 $found_match = true;
493 }
494
495 if ( 'before' === $insert ) {
496
497 $new_arr[ $key ] = $val;
498 }
499 }
500
501 $arr = $new_arr;
502
503 unset( $new_arr );
504 }
505
506 return $found_match;
507 }
508
509 public static function array_key_last( array $array ) {
510
511 if ( function_exists( 'array_key_last' ) ) {
512
513 return array_key_last( $array ); // Since PHP v7.3.
514 }
515
516 return key( array_slice( $array, -1, 1, true ) );
517 }
518
519 public static function array_map_recursive( $func, array $arr ) {
520
521 foreach ( $arr as $key => $el ) {
522
523 $arr[ $key ] = is_array( $el ) ? self::array_map_recursive( $func, $el ) : $func( $el );
524 }
525
526 return $arr;
527 }
528
529 public static function array_maybe_unserialize( array $arr ) {
530
531 return self::array_map_recursive( 'maybe_unserialize', $arr );
532 }
533
534 /*
535 * PHP's array_merge_recursive() merges arrays, but it converts values with duplicate keys to arrays rather than
536 * overwriting the value in the first array with the duplicate value in the second array, as array_merge does. The
537 * following method does not change the datatypes of the values in the arrays. Matching key values in the second
538 * array overwrite those in the first array, as is the case with array_merge().
539 */
540 public static function array_merge_recursive_distinct( array &$arr1, array &$arr2 ) {
541
542 $merged = $arr1;
543
544 foreach ( $arr2 as $key => &$value ) {
545
546 if ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
547
548 $merged[ $key ] = self::array_merge_recursive_distinct( $merged[ $key ], $value );
549
550 } else $merged[ $key ] = $value;
551 }
552
553 return $merged;
554 }
555
556 /*
557 * Maybe limit the number of array elements.
558 */
559 public static function array_slice_fifo( array $array, $max = 1 ) {
560
561 if ( ! empty( $array ) && is_numeric( $max ) && $max > 0 && count( $array ) > $max ) {
562
563 return array_slice( $array, -$max, $length = null, $preserve_keys = true );
564 }
565
566 return $array;
567 }
568
569 public static function array_to_hashtags( array $arr = array() ) {
570
571 $hashtags = self::sanitize_hashtags( $arr );
572 $hashtags = array_filter( $hashtags ); // Removes empty array elements.
573 $hashtags = trim( implode( $glue = ' ', $hashtags ) );
574
575 return $hashtags;
576 }
577
578 /*
579 * Convert an array to a comma delimited text string.
580 */
581 public static function array_to_keywords( array $arr = array() ) {
582
583 $keywords = array_map( 'sanitize_text_field', $arr );
584 $keywords = trim( implode( $glue = ', ', $keywords ) );
585
586 return $keywords;
587 }
588
589 /*
590 * Convert an array to an HTML list.
591 */
592 public static function array_to_list_html( array $arr, $type = 'ul' ) {
593
594 return '<' . $type . '><li>' . implode( $glue = '</li> <li>', $arr ) . '</li></' . $type . '> ';
595 }
596
597 public static function get_array_or_element( $arr, $key = false, $add_none = false ) {
598
599 if ( null === $key ) {
600
601 // Nothing to do.
602
603 } elseif ( false === $key ) {
604
605 // Nothing to do.
606
607 } elseif ( true === $key ) { // Sort elements.
608
609 asort( $arr ); // Sort an array in ascending order and maintain index association.
610
611 } elseif ( isset( $arr[ $key ] ) ) { // Return a specific array element.
612
613 return $arr[ $key ];
614
615 } else return null; // Array key not found - return null.
616
617 if ( true === $add_none ) { // Prefix array with 'none'.
618
619 /*
620 * The union operator (+) gives priority to values in the first array, while
621 * array_replace() gives priority to values in the the second array.
622 */
623 $arr = array( 'none' => 'none' ) + $arr; // Maintains numeric index.
624 }
625
626 return $arr;
627 }
628
629 public static function get_array_parents( array $arr, $parent_key = '', $gparent_key = '', &$parents = array() ) {
630
631 foreach ( $arr as $child_key => $value ) {
632
633 if ( is_array( $value ) ) {
634
635 self::get_array_parents( $value, $child_key, $parent_key, $parents );
636
637 } elseif ( $parent_key && $child_key !== $parent_key ) {
638
639 $parents[ $child_key ][] = $parent_key;
640
641 } elseif ( $gparent_key && $child_key === $parent_key ) {
642
643 $parents[ $child_key ][] = $gparent_key;
644 }
645 }
646
647 return $parents;
648 }
649
650 public static function get_array_pretty( $mixed, $flatten = false ) {
651
652 $pretty = '';
653
654 if ( is_array( $mixed ) ) {
655
656 foreach ( $mixed as $key => $val ) {
657
658 $val = self::get_array_pretty( $val, $flatten );
659
660 if ( $flatten ) {
661
662 $pretty .= $key . '=' . $val.', ';
663
664 } else {
665
666 if ( is_object( $mixed[ $key ] ) ) {
667
668 unset( $mixed[ $key ] ); // Dereference the object first.
669 }
670
671 $mixed[ $key ] = $val;
672 }
673 }
674
675 $pretty = $flatten ? '(' . trim( $pretty, ', ' ) . ')' : $mixed;
676
677 } elseif ( false === $mixed ) {
678
679 $pretty = 'false';
680
681 } elseif ( true === $mixed ) {
682
683 $pretty = 'true';
684
685 } elseif ( null === $mixed ) {
686
687 $pretty = 'null';
688
689 } elseif ( '' === $mixed ) {
690
691 $pretty = '\'\'';
692
693 } elseif ( is_object( $mixed ) ) {
694
695 $pretty = 'object ' . get_class( $mixed );
696
697 } else $pretty = $mixed;
698
699 return $pretty;
700 }
701
702 public static function get_assoc_salt( array $assoc ) {
703
704 $assoc_salt = '';
705
706 foreach ( $assoc as $key => $val ) {
707
708 $assoc_salt .= '-' . $key . ':' . (string) $val;
709 }
710
711 $assoc_salt = ltrim( $assoc_salt, '-' );
712
713 return $assoc_salt;
714 }
715
716 /*
717 * Return a indexed array of values that have a common prefix key.
718 */
719 public static function get_multi_values( array &$opts, $opt_pre ) {
720
721 $values = self::preg_grep_keys( '/^' . $opt_pre . '_([0-9]+)$/', $opts, $invert = false, $replace = '$1' );
722
723 foreach ( $values as $num => $val ) {
724
725 if ( ! self::is_valid_option_value( $val ) ) { // Allow for 0 but not empty string.
726
727 unset( $values[ $num ] );
728 }
729 }
730
731 return $values;
732 }
733
734 /*
735 * Move an array element to the front.
736 */
737 public static function move_to_front( array &$arr, $key ) {
738
739 if ( array_key_exists( $key, $arr ) ) {
740
741 $val = $arr[ $key ];
742
743 unset( $arr[ $key ] );
744
745 $arr = array_merge( array( $key => $val ), $arr );
746 }
747
748 return $arr;
749 }
750
751 /*
752 * Move an array element to the end.
753 */
754 public static function move_to_end( array &$arr, $key ) {
755
756 if ( array_key_exists( $key, $arr ) ) {
757
758 $val = $arr[ $key ];
759
760 unset( $arr[ $key ] );
761
762 $arr[ $key ] = $val;
763 }
764
765 return $arr;
766 }
767
768 public static function natasort( array &$arr ) {
769
770 if ( function_exists( 'remove_accents' ) ) { // WordPress function.
771
772 return uasort( $arr, array( __CLASS__, 'strnatcasecmp_remove_accents' ) );
773 }
774
775 return uasort( $arr, 'strnatcasecmp' );
776 }
777
778 public static function natksort( array &$arr ) {
779
780 if ( function_exists( 'remove_accents' ) ) { // WordPress function.
781
782 return uksort( $arr, array( __CLASS__, 'strnatcasecmp_remove_accents' ) );
783 }
784
785 return uksort( $arr, 'strnatcasecmp' );
786 }
787
788 public static function next_key( $needle, array &$arr, $loop_around = true ) {
789
790 $keys = array_keys( $arr );
791 $pos = array_search( $needle, $keys );
792
793 if ( false !== $pos ) {
794
795 if ( isset( $keys[ $pos + 1 ] ) ) {
796
797 return $keys[ $pos + 1 ];
798
799 } elseif ( true === $loop_around ) {
800
801 return $keys[ 0 ];
802 }
803 }
804
805 return false;
806 }
807
808 /*
809 * $keys_preg must be a string.
810 *
811 * $replace can be a string or an associative array of 'pattern' => 'replacement'.
812 */
813 public static function preg_grep_keys( $keys_preg, $in_array, $invert = false, $replace = false ) {
814
815 if ( ! is_array( $in_array ) || empty( $in_array ) ) { // Just in case.
816
817 return array();
818 }
819
820 $in_array_keys = array_keys( $in_array );
821 $matched_keys = preg_grep( $keys_preg, $in_array_keys, $invert ? PREG_GREP_INVERT : 0 );
822
823 if ( empty( $matched_keys ) && $invert ) { // Nothing to do.
824
825 return $in_array;
826 }
827
828 /*
829 * The $replace value can be a string or an associative array of 'pattern' => 'replacement'.
830 */
831 if ( is_array( $replace ) ) {
832
833 $patterns = array_keys( $replace );
834 $replacements = array_values( $replace );
835
836 } else {
837
838 $patterns = $keys_preg;
839 $replacements = $replace;
840 }
841
842 $out_array = array();
843
844 foreach ( $matched_keys as $key ) {
845
846 if ( false === $replace ) { // Element key remains unchanged.
847
848 $out_array[ $key ] = $in_array[ $key ];
849
850 } else {
851
852 $fixed = preg_replace( $patterns, $replacements, $key );
853
854 $out_array[ $fixed ] = $in_array[ $key ];
855 }
856 }
857
858 return $out_array;
859 }
860
861 public static function unset_keys( &$arr1, $arr2 ) {
862
863 foreach ( array_keys( $arr2 ) as $key ) {
864
865 unset( $arr1[ $key ] );
866 }
867 }
868
869 public static function unset_numeric_keys( &$arr ) {
870
871 foreach ( array_keys( $arr ) as $key ) {
872
873 if ( is_numeric( $key ) ) {
874
875 unset( $arr[ $key ] );
876 }
877 }
878 }
879
880 /*
881 * COUNTRY METHODS:
882 *
883 * get_alpha2_countries()
884 * get_alpha2_country_name()
885 */
886 public static function get_alpha2_countries() {
887
888 if ( ! class_exists( 'SucomCountryCodes' ) ) {
889
890 require_once dirname( __FILE__ ) . '/country-codes.php';
891 }
892
893 return SucomCountryCodes::get( 'alpha2' );
894 }
895
896 public static function get_alpha2_country_name( $country_code, $default_code = false ) {
897
898 if ( empty( $country_code ) || 'none' === $country_code ) {
899
900 return false;
901 }
902
903 if ( ! class_exists( 'SucomCountryCodes' ) ) {
904
905 require_once dirname( __FILE__ ) . '/country-codes.php';
906 }
907
908 $countries = SucomCountryCodes::get( 'alpha2' );
909
910 if ( ! isset( $countries[ $country_code ] ) ) {
911
912 if ( false === $default_code || ! isset( $countries[ $default_code ] ) ) {
913
914 return false;
915 }
916
917 return $countries[ $default_code ];
918 }
919
920 return $countries[ $country_code ];
921 }
922
923 /*
924 * CURRENCY METHODS:
925 *
926 * get_currencies()
927 * get_currencies_abbrev()
928 * get_currency_symbol_abbrev()
929 *
930 * Used by WpssoSubmenuGeneral->get_table_rows() for 'Default Currency' option.
931 */
932 public static function get_currencies( $key = false, $add_none = false, $format = '%2$s (%1$s)' ) {
933
934 if ( ! class_exists( 'SucomCurrencies' ) ) {
935
936 require_once dirname( __FILE__ ) . '/currencies.php';
937 }
938
939 if ( is_string( $key ) ) {
940
941 return SucomCurrencies::get( $key, $format );
942 }
943
944 return self::get_array_or_element( SucomCurrencies::get( null, $format ), $key, $add_none );
945 }
946
947 public static function get_currencies_abbrev() {
948
949 $currencies_by_abbrev = self::get_currencies( null, $add_none = false, $format = '%1$s' );
950
951 asort( $currencies_by_abbrev ); // Sort by abbreviation.
952
953 return $currencies_by_abbrev;
954 }
955
956 public static function get_currency_symbol_abbrev( $symbol = false, $default = 'USD', $decode = true ) {
957
958 if ( $decode ) $symbol = self::decode_html( $symbol );
959
960 if ( $symbol === '$' ) return 'USD';
961
962 if ( ! class_exists( 'SucomCurrencies' ) ) {
963
964 require_once dirname( __FILE__ ) . '/currencies.php';
965 }
966
967 $currencies_by_abbrev = SucomCurrencies::get_symbols( $key = null, $decode );
968
969 $currencies_by_symbol = array_flip( $currencies_by_abbrev ); // Index by symbol.
970
971 unset( $currencies_by_abbrev );
972
973 return isset( $currencies_by_symbol[ $symbol ] ) ? $currencies_by_symbol[ $symbol ] : false;
974 }
975
976 /*
977 * DASHICON METHODS:
978 */
979 public static function get_dashicons( $key = true, $add_none = false ) {
980
981 if ( ! class_exists( 'SucomDashicons' ) ) {
982
983 require_once dirname( __FILE__ ) . '/dashicons.php';
984 }
985
986 if ( is_numeric( $key ) || is_string( $key ) ) {
987
988 return SucomDashicons::get( $key );
989 }
990
991 return self::get_array_or_element( SucomDashicons::get(), $key, $add_none );
992 }
993
994 /*
995 * DATE AND TIME METHODS:
996 *
997 * format_tz_offset()
998 * get_default_timezone()
999 * get_formatted_timezone()
1000 * get_hours()
1001 * get_hours_range()
1002 * get_timezones()
1003 * get_timezone_abbr()
1004 * get_timezone_offset_secs()
1005 * get_timezone_offset_hours()
1006 * get_tz_name()
1007 * iso8601_to_seconds()
1008 * maybe_iso8601_to_seconds()
1009 * sprintf_date_time()
1010 */
1011 public static function format_human_bytes( $bytes, $dec = 2, $sep = ' ' ) {
1012
1013 $size = array( 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' );
1014
1015 $factor = floor( ( strlen( $bytes ) - 1 ) / 3 );
1016
1017 return sprintf( "%.{$dec}f{$sep}%s", $bytes / pow( 1024, $factor ), $size[ $factor ] );
1018 }
1019
1020 /*
1021 * Returns a date( 'P' ) formatted timezone value (ie. -07:00).
1022 */
1023 public static function format_tz_offset( $offset ) {
1024
1025 $hours = (int) $offset;
1026 $minutes = ( $offset - $hours );
1027 $sign = ( $offset < 0 ) ? '-' : '+';
1028 $abs_hour = abs( $hours );
1029 $abs_mins = abs( $minutes * 60 );
1030 $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins );
1031
1032 return $tz_offset;
1033 }
1034
1035 /*
1036 * May return a timezone string (ie. 'Africa/Abidjan'), 'UTC', or an offset (ie. '-07:00').
1037 */
1038 public static function get_default_timezone() {
1039
1040 static $local_cache = null;
1041
1042 if ( null !== $local_cache ) {
1043
1044 return $local_cache;
1045 }
1046
1047 if ( function_exists( wp_timezone_string() ) ) {
1048
1049 return $local_cache = wp_timezone_string();
1050 }
1051
1052 $timezone_string = get_option( 'timezone_string' );
1053
1054 if ( $timezone_string ) {
1055
1056 return $local_cache = $timezone_string;
1057 }
1058
1059 $offset = (float) get_option( 'gmt_offset' );
1060
1061 $tz_offset = self::format_tz_offset( $offset );
1062
1063 return $local_cache = $tz_offset;
1064 }
1065
1066 public static function get_formatted_timezone( $tz_name, $format ) {
1067
1068 static $local_cache = array();
1069
1070 if ( isset( $local_cache[ $tz_name ][ $format ] ) ) {
1071
1072 return $local_cache[ $tz_name ][ $format ];
1073 }
1074
1075 $dt_obj = new DateTime();
1076
1077 $dt_obj->setTimeZone( new DateTimeZone( $tz_name ) );
1078
1079 return $local_cache[ $tz_name ][ $format ] = $dt_obj->format( $format );
1080 }
1081
1082 public static function get_hours( $step_secs = 3600, $label_format = 'H:i' ) {
1083
1084 return self::get_hours_range( $start_secs = 0, $end_secs = 86400, $step_secs, $label_format );
1085 }
1086
1087 /*
1088 * Returns an associative array.
1089 *
1090 * Example time formats: 'H:i' (default), 'g:i a'.
1091 */
1092 public static function get_hours_range( $start_secs = 0, $end_secs = 86400, $step_secs = 3600, $label_format = 'H:i' ) {
1093
1094 $times = array();
1095
1096 foreach ( range( $start_secs, $end_secs, $step_secs ) as $ts ) {
1097
1098 $value = gmdate( 'H:i', $ts );
1099
1100 if ( 'H:i' !== $label_format ) {
1101
1102 $times[ $value ] = gmdate( $label_format, $ts );
1103
1104 } else $times[ $value ] = $value;
1105 }
1106
1107 return $times;
1108 }
1109
1110 /*
1111 * Returns an associative array of timezone strings (ie. 'Africa/Abidjan'), 'UTC', and offsets (ie. '-07:00').
1112 */
1113 public static function get_timezones() {
1114
1115 /*
1116 * See https://www.php.net/manual/en/function.timezone-identifiers-list.php.
1117 */
1118 $timezones = timezone_identifiers_list();
1119 $timezones = array_combine( $timezones, $timezones ); // Create an associative array.
1120 $offset_range = array( -12, -11.5, -11, -10.5, -10, -9.5, -9, -8.5, -8, -7.5, -7, -6.5, -6, -5.5, -5, -4.5,
1121 -4, -3.5, -3, -2.5, -2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 5.75, 6,
1122 6.5, 7, 7.5, 8, 8.5, 8.75, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.75, 13, 13.75, 14 );
1123
1124 /*
1125 * Create date( 'P' ) formatted timezone values (ie. -07:00).
1126 */
1127 foreach ( $offset_range as $offset ) {
1128
1129 $offset_value = self::format_tz_offset( $offset );
1130
1131 $offset_name = 'UTC' . $offset_value;
1132
1133 $timezones[ $offset_value ] = $offset_name;
1134 }
1135
1136 return $timezones;
1137 }
1138
1139 /*
1140 * Get timezone abbreviation (ie. 'EST', 'MDT', etc.).
1141 */
1142 public static function get_timezone_abbr( $tz_name ) {
1143
1144 return self::get_formatted_timezone( $tz_name, 'T' );
1145 }
1146
1147 /*
1148 * Get timezone offset in seconds (west of UTC is negative, and east of UTC is positive).
1149 */
1150 public static function get_timezone_offset_secs( $tz_name ) {
1151
1152 return self::get_formatted_timezone( $tz_name, 'Z' );
1153 }
1154
1155 /*
1156 * Timezone difference to UTC with colon between hours and minutes.
1157 */
1158 public static function get_timezone_offset_hours( $tz_name ) {
1159
1160 /*
1161 * See https://www.php.net/manual/en/datetime.format.php.
1162 *
1163 * P Difference to Greenwich time (GMT) with colon between hours and minutes.
1164 * p The same as P, but returns Z instead of +00:00.
1165 *
1166 * Unfortunately 'p' is only available since PHP v8, so we must use 'P' and check for '+00:00'.
1167 */
1168 $offset = self::get_formatted_timezone( $tz_name, 'P' );
1169
1170 if ( '+00:00' == $offset ) {
1171
1172 $offset = 'Z';
1173 }
1174
1175 return $offset;
1176 }
1177
1178 /*
1179 * "tz" is used in the method name to hint that the argument is an abbreviation.
1180 */
1181 public static function get_tz_name( $tz_abbr ) {
1182
1183 return timezone_name_from_abbr( $tz_abbr );
1184 }
1185
1186 /*
1187 * Convert ISO 8601 value (like P2DT15M33S) to seconds.
1188 */
1189 public static function iso8601_to_seconds( $iso8601 ) {
1190
1191 $interval = new \DateInterval( $iso8601 );
1192
1193 return ( $interval->d * 24 * 60 * 60 ) + ( $interval->h * 60 * 60 ) + ( $interval->i * 60 ) + $interval->s;
1194 }
1195
1196 public static function maybe_iso8601_to_seconds( $mixed ) {
1197
1198 if ( 0 === strpos( $mixed, 'P' ) ) {
1199
1200 return self::iso8601_to_seconds( $mixed );
1201 }
1202
1203 return $mixed;
1204 }
1205
1206 /*
1207 * See WpssoUser->add_person_role().
1208 * See WpssoUser->remove_person_role().
1209 * See WpssoUtilCache->refresh().
1210 */
1211 public static function sprintf_date_time( $fmt = '%1$s %2$s' ) {
1212
1213 $wp_timezone = wp_timezone();
1214 $wp_date_fmt = get_option( 'date_format' );
1215 $wp_time_fmt = get_option( 'time_format' );
1216
1217 $now = date_create_immutable( $datetime = 'now', $wp_timezone );
1218
1219 $date_val = $now->format( $wp_date_fmt );
1220 $time_val = $now->format( $wp_time_fmt );
1221
1222 $date_time_val = sprintf( $fmt, $date_val, $time_val );
1223
1224 return $date_time_val;
1225 }
1226
1227 /*
1228 * IS CHECK METHODS:
1229 *
1230 * is_assoc()
1231 * is_https()
1232 * is_md5()
1233 * is_non_assoc()
1234 * is_true()
1235 * is_valid_option_value()
1236 *
1237 * Note that an empty array is not an associative array (ie. returns false for an empty array).
1238 */
1239 public static function is_assoc( $mixed ) {
1240
1241 $is_assoc = false;
1242
1243 if ( ! empty( $mixed ) ) { // Optimize.
1244
1245 if ( is_array( $mixed ) ) { // Just in case.
1246
1247 if ( ! is_numeric( implode( array_keys( $mixed ) ) ) ) {
1248
1249 $is_assoc = true;
1250 }
1251 }
1252 }
1253
1254 return $is_assoc;
1255 }
1256
1257 public static function is_https( $url = '' ) {
1258
1259 static $local_cache = array();
1260
1261 if ( isset( $local_cache[ $url ] ) ) {
1262
1263 return $local_cache[ $url ];
1264 }
1265
1266 if ( strpos( $url, '://' ) ) {
1267
1268 if ( 'https' === wp_parse_url( $url, PHP_URL_SCHEME ) ) {
1269
1270 return $local_cache[ $url ] = true;
1271 }
1272
1273 return $local_cache[ $url ] = false;
1274
1275 } elseif ( is_ssl() ) {
1276
1277 return $local_cache[ $url ] = true;
1278
1279 } elseif ( isset( $_SERVER[ 'HTTP_X_FORWARDED_PROTO' ] ) && 'https' === strtolower( $_SERVER[ 'HTTP_X_FORWARDED_PROTO' ] ) ) {
1280
1281 return $local_cache[ $url ] = true;
1282
1283 } elseif ( isset( $_SERVER[ 'HTTP_X_FORWARDED_SSL' ] ) && 'on' === strtolower( $_SERVER[ 'HTTP_X_FORWARDED_SSL' ] ) ) {
1284
1285 return $local_cache[ $url ] = true;
1286 }
1287
1288 return $local_cache[ $url ] = false;
1289 }
1290
1291 public static function is_md5( $md5 ) {
1292
1293 return strlen( $md5 ) === 32 && preg_match( '/^[a-f0-9]+$/', $md5 ) ? true : false;
1294 }
1295
1296 /*
1297 * Note that an empty array is not an associative array (ie. returns false for an empty array).
1298 */
1299 public static function is_non_assoc( $mixed ) {
1300
1301 $is_non_assoc = false;
1302
1303 if ( is_array( $mixed ) ) { // Just in case.
1304
1305 if ( empty( $mixed ) ) { // Optimize.
1306
1307 $is_non_assoc = true;
1308
1309 } elseif ( is_numeric( implode( array_keys( $mixed ) ) ) ) {
1310
1311 $is_non_assoc = true;
1312 }
1313 }
1314
1315 return $is_non_assoc;
1316 }
1317
1318 public static function is_true( $mixed, $allow_null = false ) {
1319
1320 $is_true = is_string( $mixed ) ? filter_var( $mixed, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : (bool) $mixed;
1321
1322 return null === $is_true && ! $allow_null ? false : $is_true;
1323 }
1324
1325 /*
1326 * Check that the option value is not true, false, null, empty string, or 'none'.
1327 */
1328 public static function is_valid_option_value( $value ) {
1329
1330 if ( true === $value ) {
1331
1332 return false;
1333
1334 } elseif ( empty( $value ) && ! is_numeric( $value ) ) { // False, null, or empty string.
1335
1336 return false;
1337
1338 } elseif ( 'none' === $value ) { // Disabled option.
1339
1340 return false;
1341 }
1342
1343 return true;
1344 }
1345
1346 /*
1347 * OPEN GRAPH AND META TAG METHODS:
1348 *
1349 * get_mt_og_seed()
1350 * get_mt_image_seed()
1351 * get_mt_product_seed()
1352 * get_mt_video_seed()
1353 * get_first_og_image_id()
1354 * get_first_og_image_url()
1355 * get_first_og_video_url()
1356 * get_first_mt_media_id()
1357 * get_first_mt_media_url()
1358 * get_publisher_languages()
1359 * merge_mt_og()
1360 */
1361 public static function get_mt_og_seed() {
1362
1363 return array(
1364 'fb:admins' => null,
1365 'fb:app_id' => null,
1366 'og:time' => null, // Internal meta tag.
1367 'og:type' => null,
1368 'og:url' => null,
1369 'og:redirect_url' => null, // Internal meta tag.
1370 'og:locale' => null,
1371 'og:site_name' => null,
1372 'og:title' => null,
1373 'og:description' => null,
1374 'og:updated_time' => null,
1375 'og:video' => null,
1376 'og:image' => null,
1377 'schema:language' => null, // Internal meta tag.
1378 'schema:type:id' => null, // Internal meta tag.
1379 'schema:type:url' => null, // Internal meta tag.
1380 'schema:type:context' => null, // Internal meta tag.
1381 'schema:type:name' => null, // Internal meta tag.
1382 'schema:type:path' => null, // Internal meta tag.
1383 'schema:review:rating' => null, // Internal meta tag.
1384 );
1385 }
1386
1387 /*
1388 * Pre-define the array key order for the list() construct.
1389 */
1390 public static function get_mt_image_seed( $mt_pre = 'og', array $mt_og = array() ) {
1391
1392 $mt_ret = array(
1393 $mt_pre . ':image:secure_url' => null,
1394 $mt_pre . ':image:url' => null,
1395 $mt_pre . ':image:width' => null,
1396 $mt_pre . ':image:height' => null,
1397 $mt_pre . ':image:cropped' => null, // Internal meta tag.
1398 $mt_pre . ':image:id' => null, // Internal meta tag.
1399 $mt_pre . ':image:alt' => null,
1400 $mt_pre . ':image:size_name' => null, // Internal meta tag.
1401 );
1402
1403 return self::merge_mt_og( $mt_ret, $mt_og );
1404 }
1405
1406 /*
1407 * This method is used by e-Commerce modules to pre-define and pre-sort the product meta tags.
1408 *
1409 * Use null values so WpssoOpengraph->add_data_og_type_md() can load metadata default values.
1410 */
1411 public static function get_mt_product_seed( $mt_pre = 'product', array $mt_og = array() ) {
1412
1413 $mt_ret = array(
1414
1415 /*
1416 * Product part numbers.
1417 */
1418 $mt_pre . ':retailer_item_id' => null, // Product ID.
1419 $mt_pre . ':retailer_part_no' => null, // Product SKU.
1420 $mt_pre . ':mfr_part_no' => null, // Product MPN.
1421 $mt_pre . ':item_group_id' => null, // Product variant group ID.
1422 $mt_pre . ':ean' => null, // aka EAN, EAN-13, GTIN-13.
1423 $mt_pre . ':gtin14' => null, // Internal meta tag.
1424 $mt_pre . ':gtin13' => null, // Internal meta tag.
1425 $mt_pre . ':gtin12' => null, // Internal meta tag.
1426 $mt_pre . ':gtin8' => null, // Internal meta tag.
1427 $mt_pre . ':gtin' => null, // Internal meta tag.
1428 $mt_pre . ':isbn' => null,
1429 $mt_pre . ':upc' => null, // Aka the UPC, UPC-A, UPC, GTIN-12.
1430
1431 /*
1432 * Product attributes and descriptions.
1433 */
1434 $mt_pre . ':url' => null, // Internal meta tag.
1435 $mt_pre . ':title' => null, // Internal meta tag.
1436 $mt_pre . ':description' => null, // Internal meta tag.
1437 $mt_pre . ':updated_time' => null, // Internal meta tag.
1438 $mt_pre . ':adult_type' => null, // Internal meta tag.
1439 $mt_pre . ':age_group' => null,
1440 $mt_pre . ':quantity' => null, // Internal meta tag.
1441 $mt_pre . ':availability' => null,
1442 $mt_pre . ':brand' => null,
1443 $mt_pre . ':category' => null, // The product category according to the Google product taxonomy.
1444 $mt_pre . ':retailer_category' => null, // Internal meta tag.
1445 $mt_pre . ':awards' => array(), // Internal meta tag.
1446 $mt_pre . ':condition' => null,
1447 $mt_pre . ':energy_efficiency:value' => null, // Internal meta tag.
1448 $mt_pre . ':energy_efficiency:min_value' => null, // Internal meta tag.
1449 $mt_pre . ':energy_efficiency:max_value' => null, // Internal meta tag.
1450 $mt_pre . ':expiration_time' => null,
1451 $mt_pre . ':color' => null,
1452 $mt_pre . ':material' => null,
1453 $mt_pre . ':mrp_id' => null, // Internal meta tag.
1454 $mt_pre . ':pattern' => null,
1455 $mt_pre . ':purchase_limit' => null,
1456 $mt_pre . ':eligible_quantity:value' => null, // Internal meta tag.
1457 $mt_pre . ':eligible_quantity:min_value' => null, // Internal meta tag.
1458 $mt_pre . ':eligible_quantity:max_value' => null, // Internal meta tag.
1459 $mt_pre . ':eligible_quantity:unit_code' => null, // Internal meta tag.
1460 $mt_pre . ':eligible_quantity:unit_text' => null, // Internal meta tag.
1461 $mt_pre . ':target_gender' => null,
1462 $mt_pre . ':size' => null,
1463 $mt_pre . ':size_group' => null, // Internal meta tag.
1464 $mt_pre . ':size_system' => null, // Internal meta tag.
1465 $mt_pre . ':is_virtual' => null, // Internal meta tag.
1466
1467 /*
1468 * Product net dimensions and weight.
1469 */
1470 $mt_pre . ':length:value' => null, // Internal meta tag.
1471 $mt_pre . ':length:units' => null, // Internal meta tag (units after value).
1472 $mt_pre . ':width:value' => null, // Internal meta tag.
1473 $mt_pre . ':width:units' => null, // Internal meta tag (units after value).
1474 $mt_pre . ':height:value' => null, // Internal meta tag.
1475 $mt_pre . ':height:units' => null, // Internal meta tag (units after value).
1476 $mt_pre . ':weight:value' => null,
1477 $mt_pre . ':weight:units' => null,
1478 $mt_pre . ':fluid_volume:value' => null, // Internal meta tag.
1479 $mt_pre . ':fluid_volume:units' => null, // Internal meta tag (units after value).
1480
1481 /*
1482 * Product prices.
1483 */
1484 $mt_pre . ':min_advert_price:amount' => null, // Internal meta tag.
1485 $mt_pre . ':min_advert_price:currency' => null, // Internal meta tag.
1486 $mt_pre . ':original_price:type' => null, // Internal meta tag.
1487 $mt_pre . ':original_price:amount' => null,
1488 $mt_pre . ':original_price:currency' => null,
1489 $mt_pre . ':pretax_price:amount' => null,
1490 $mt_pre . ':pretax_price:currency' => null,
1491 $mt_pre . ':price:type' => null, // Internal meta tag.
1492 $mt_pre . ':price:amount' => null,
1493 $mt_pre . ':price:currency' => null,
1494 $mt_pre . ':price:vat_included' => null, // Internal meta tag.
1495 $mt_pre . ':sale_price:type' => null, // Internal meta tag.
1496 $mt_pre . ':sale_price:amount' => null,
1497 $mt_pre . ':sale_price:currency' => null,
1498 $mt_pre . ':sale_price_dates:start' => null,
1499 $mt_pre . ':sale_price_dates:start_date' => null, // Internal meta tag.
1500 $mt_pre . ':sale_price_dates:start_time' => null, // Internal meta tag.
1501 $mt_pre . ':sale_price_dates:start_timezone' => null, // Internal meta tag.
1502 $mt_pre . ':sale_price_dates:start_iso' => null, // Internal meta tag.
1503 $mt_pre . ':sale_price_dates:end' => null,
1504 $mt_pre . ':sale_price_dates:end_date' => null, // Internal meta tag.
1505 $mt_pre . ':sale_price_dates:end_time' => null, // Internal meta tag.
1506 $mt_pre . ':sale_price_dates:end_timezone' => null, // Internal meta tag.
1507 $mt_pre . ':sale_price_dates:end_iso' => null, // Internal meta tag.
1508 $mt_pre . ':offers' => array(), // Internal meta tag.
1509
1510 /*
1511 * Product shipping.
1512 */
1513 $mt_pre . ':shipping_cost:amount' => null,
1514 $mt_pre . ':shipping_cost:currency' => null,
1515 $mt_pre . ':shipping_length:value' => null, // Internal meta tag.
1516 $mt_pre . ':shipping_length:units' => null, // Internal meta tag (units after value).
1517 $mt_pre . ':shipping_width:value' => null, // Internal meta tag.
1518 $mt_pre . ':shipping_width:units' => null, // Internal meta tag (units after value).
1519 $mt_pre . ':shipping_height:value' => null, // Internal meta tag.
1520 $mt_pre . ':shipping_height:units' => null, // Internal meta tag (units after value).
1521 $mt_pre . ':shipping_weight:value' => null,
1522 $mt_pre . ':shipping_weight:units' => null,
1523 $mt_pre . ':shipping_class_id' => null, // Internal meta tag.
1524 $mt_pre . ':shipping_offers' => array(), // Internal meta tag.
1525
1526 /*
1527 * Product ratings and reviews.
1528 */
1529 $mt_pre . ':rating:average' => null, // Internal meta tag.
1530 $mt_pre . ':rating:count' => null, // Internal meta tag.
1531 $mt_pre . ':rating:worst' => null, // Internal meta tag.
1532 $mt_pre . ':rating:best' => null, // Internal meta tag.
1533 $mt_pre . ':review:count' => null, // Internal meta tag.
1534 $mt_pre . ':reviews' => array(), // Internal meta tag.
1535 );
1536
1537 if ( ! empty( $mt_og[ 'og:type' ] ) && 'product' === $mt_og[ 'og:type' ] ) {
1538
1539 $mt_ret = array_merge( $mt_ret, array(
1540 $mt_pre . ':variants' => array(), // Internal meta tag.
1541 ) );
1542 }
1543
1544 return self::merge_mt_og( $mt_ret, $mt_og );
1545 }
1546
1547 public static function get_mt_video_seed( $mt_pre = 'og', array $mt_og = array() ) {
1548
1549 $mt_ret = array(
1550 $mt_pre . ':video:secure_url' => null,
1551 $mt_pre . ':video:url' => null,
1552 $mt_pre . ':video:title' => null, // Internal meta tag.
1553 $mt_pre . ':video:description' => null, // Internal meta tag.
1554 $mt_pre . ':video:family_friendly' => null, // Internal meta tag.
1555 $mt_pre . ':video:regions_allowed' => array(), // Internal meta tag.
1556 $mt_pre . ':video:type' => null, // Example: 'application/x-shockwave-flash', 'text/html', 'video/mp4', etc.
1557 $mt_pre . ':video:width' => null,
1558 $mt_pre . ':video:height' => null,
1559 $mt_pre . ':video:tag' => array(),
1560 $mt_pre . ':video:duration' => null, // Internal meta tag.
1561 $mt_pre . ':video:published_date' => null, // Internal meta tag.
1562 $mt_pre . ':video:upload_date' => null, // Internal meta tag.
1563 $mt_pre . ':video:thumbnail_url' => null, // Internal meta tag.
1564 $mt_pre . ':video:embed_url' => null, // Internal meta tag.
1565 $mt_pre . ':video:stream_url' => null, // Internal meta tag. VideoObject contentUrl.
1566 $mt_pre . ':video:stream_size' => null, // Internal meta tag. VideoObject contentSize.
1567 $mt_pre . ':video:has_image' => false, // Internal meta tag.
1568 $mt_pre . ':video:iphone_name' => null, // Internal meta tag for X (Twitter) player card.
1569 $mt_pre . ':video:iphone_id' => null, // Internal meta tag for X (Twitter) player card.
1570 $mt_pre . ':video:iphone_url' => null, // Internal meta tag for X (Twitter) player card.
1571 $mt_pre . ':video:ipad_name' => null, // Internal meta tag for X (Twitter) player card.
1572 $mt_pre . ':video:ipad_id' => null, // Internal meta tag for X (Twitter) player card.
1573 $mt_pre . ':video:ipad_url' => null, // Internal meta tag for X (Twitter) player card.
1574 $mt_pre . ':video:googleplay_name' => null, // Internal meta tag for X (Twitter) player card.
1575 $mt_pre . ':video:googleplay_id' => null, // Internal meta tag for X (Twitter) player card.
1576 $mt_pre . ':video:googleplay_url' => null, // Internal meta tag for X (Twitter) player card.
1577 );
1578
1579 $mt_ret += self::get_mt_image_seed( $mt_pre );
1580
1581 /*
1582 * Facebook applink meta tags.
1583 */
1584 if ( $mt_pre === 'og' ) {
1585
1586 $mt_ret += array(
1587 'al:ios:app_name' => null,
1588 'al:ios:app_store_id' => null,
1589 'al:ios:url' => null,
1590 'al:android:app_name' => null,
1591 'al:android:package' => null,
1592 'al:android:url' => null,
1593 'al:web:url' => null,
1594 'al:web:should_fallback' => 'false',
1595 );
1596 }
1597
1598 return self::merge_mt_og( $mt_ret, $mt_og );
1599 }
1600
1601 public static function get_first_og_image_id( array $assoc ) {
1602
1603 return self::get_first_mt_media_id( $assoc, $media_pre = 'og:image' );
1604 }
1605
1606 public static function get_first_og_image_url( array $assoc ) {
1607
1608 return self::get_first_mt_media_url( $assoc, $media_pre = 'og:image', $mt_suffixes = array( ':secure_url', ':url', '' ) );
1609 }
1610
1611 public static function get_first_og_video_url( array $assoc ) {
1612
1613 return self::get_first_mt_media_url( $assoc, $media_pre = 'og:video' );
1614 }
1615
1616 public static function get_first_mt_media_id( array $assoc, $media_pre = 'og:image' ) {
1617
1618 return self::get_first_mt_media_url( $assoc, $media_pre, $mt_suffixes = array( ':id' ) );
1619 }
1620
1621 /*
1622 * Return the first URL from the associative array (og:image:secure_url, og:image:url, og:image).
1623 */
1624 public static function get_first_mt_media_url( array $assoc, $media_pre = 'og:image', $mt_suffixes = null ) {
1625
1626 /*
1627 * Check for two dimensional arrays and keep following the first array element.
1628 *
1629 * Prefer the $media_pre array key (if it's available).
1630 */
1631 if ( isset( $assoc[ $media_pre ] ) && is_array( $assoc[ $media_pre ] ) ) {
1632
1633 $first_el = reset( $assoc[ $media_pre ] );
1634
1635 } else $first_el = reset( $assoc ); // Can be array or string.
1636
1637 /*
1638 * If the first element isn't a string (ie. non-array value), then recurse until we hit bottom.
1639 */
1640 if ( is_array( $first_el ) ) {
1641
1642 return self::get_first_mt_media_url( $first_el, $media_pre, $mt_suffixes );
1643 }
1644
1645 if ( ! is_array( $mt_suffixes ) ) { // Array of meta tag suffixes to use.
1646
1647 $mt_suffixes = array( ':secure_url', ':url', '', ':embed_url', ':stream_url' );
1648 }
1649
1650 /*
1651 * First element is a text string, so check the array keys.
1652 */
1653 foreach ( $mt_suffixes as $mt_suffix ) {
1654
1655 if ( ! empty( $assoc[ $media_pre . $mt_suffix ] ) ) {
1656
1657 return $assoc[ $media_pre . $mt_suffix ]; // Return first match.
1658 }
1659 }
1660
1661 return ''; // Empty string.
1662 }
1663
1664 public static function get_publisher_languages( $publisher = '' ) {
1665
1666 switch ( $publisher ) {
1667
1668 case 'facebook':
1669 case 'fb':
1670
1671 return self::$publisher_languages[ 'facebook' ];
1672
1673 case 'google':
1674
1675 return self::$publisher_languages[ 'google' ];
1676
1677 case 'pinterest':
1678 case 'pin':
1679 case 'rp':
1680
1681 return self::$publisher_languages[ 'pinterest' ];
1682
1683 default:
1684
1685 if ( isset( self::$publisher_languages[ $publisher ] ) ) {
1686
1687 return self::$publisher_languages[ $publisher ];
1688 }
1689
1690 return array();
1691 }
1692 }
1693
1694 /*
1695 * Protected method used by get_mt_image_seed(), get_mt_product_seed(), and get_mt_video_seed().
1696 */
1697 protected static function merge_mt_og( array $mt_ret, array $mt_og ) {
1698
1699 if ( empty( $mt_og ) ) { // Nothing to merge.
1700
1701 return $mt_ret;
1702 }
1703
1704 /*
1705 * Always keep the 'og:type' meta tag top-most.
1706 *
1707 * Use array_key_exists() to allow for null value.
1708 */
1709 if ( array_key_exists( 'og:type', $mt_og ) ) {
1710
1711 return array_merge( array( 'og:type' => $mt_og[ 'og:type' ] ), $mt_ret, $mt_og );
1712 }
1713
1714 return array_merge( $mt_ret, $mt_og );
1715 }
1716
1717 /*
1718 * SANITATION METHODS:
1719 *
1720 * sanitize_classname()
1721 * sanitize_css_class()
1722 * sanitize_css_id()
1723 * sanitize_file_name()
1724 * sanitize_file_path()
1725 * sanitize_hashtags()
1726 * sanitize_hookname()
1727 * sanitize_input_name()
1728 * sanitize_int()
1729 * sanitize_key()
1730 * sanitize_locale()
1731 * sanitize_meta_key()
1732 * sanitize_tag()
1733 * sanitize_twitter_name()
1734 * sanitize_use_post()
1735 *
1736 * Used by WpssoConfig::load_lib() to sanitize file names to class names.
1737 *
1738 * See https://www.php.net/manual/en/language.variables.basics.php.
1739 */
1740 public static function sanitize_classname( $classname, $allow_underscore = true ) {
1741
1742 $classname = preg_replace( '/^[^a-zA-Z_\x80-\xff]+/', '', $classname ); # Cannot start with numeric characters.
1743 $classname = preg_replace( '/[^a-zA-Z0-9_\x80-\xff]/', '', $classname );
1744
1745 if ( ! $allow_underscore ) $classname = preg_replace( '/_/', '', $classname ); # Remove underscores.
1746
1747 return $classname;
1748 }
1749
1750 public static function sanitize_css_class( $css_class, $sep = '-' ) {
1751
1752 $css_class = preg_replace( '/[^a-zA-Z0-9_\- ]+/', $sep, $css_class ); // Spaces allowed.
1753 $css_class = trim( $css_class, $sep . ' ' ); // Trim separator and space characters.
1754
1755 return $css_class;
1756 }
1757
1758 /*
1759 * See sucomChangeHideUnhideRows() in jquery-metabox.js.
1760 * See sucomSanitizeCssId() in jquery-admin-page.js.
1761 */
1762 public static function sanitize_css_id( $css_id, $sep = '-' ) {
1763
1764 $css_id = preg_replace( '/[^a-zA-Z0-9_\-]+/', $sep, $css_id ); // Spaces not allowed.
1765 $css_id = trim( $css_id, $sep );
1766
1767 return $css_id;
1768 }
1769
1770 public static function sanitize_file_name( $file_name ) {
1771
1772 if ( '' === $file_name ) { // Just in case.
1773
1774 return $file_name;
1775 }
1776
1777 $special_chars = array( '?', '[', ']', '/', '\\', '=', '<', '>', ':', ';', ',', '\'', '"', '&', '$', '#', '*', '(', ')', '|', '~', '`', '!', '{', '}', '%', '+', chr( 0 ) );
1778
1779 $file_name = preg_replace( '#\x{00a0}#siu', ' ', $file_name );
1780 $file_name = str_replace( $special_chars, '', $file_name );
1781 $file_name = str_replace( array( '%20', '+' ), '-', $file_name );
1782 $file_name = preg_replace( '/[\r\n\t -]+/', '-', $file_name );
1783 $file_name = trim( $file_name, '.-_' );
1784
1785 return $file_name;
1786 }
1787
1788 public static function sanitize_file_path( $file_path ) {
1789
1790 if ( empty( $file_path ) ) { // Just in case.
1791
1792 return false;
1793 }
1794
1795 $file_path = implode( $glue = '/', array_map( array( __CLASS__, 'sanitize_file_name' ), explode( '/', $file_path ) ) );
1796
1797 return $file_path;
1798 }
1799
1800 /*
1801 * Note that hashtags cannot begin with a number. This method truncates tags that begin with a number.
1802 */
1803 public static function sanitize_hashtags( array $tags = array() ) {
1804
1805 return preg_replace( array( '/^[0-9].*/', '/[ \[\]#!\$\?\\\\\/\*\+\.\-\^]/', '/^.+/' ), array( '', '', '#$0' ), $tags );
1806 }
1807
1808 /*
1809 * See sucomSanitizeHookname() in jquery-admin-page.js.
1810 */
1811 public static function sanitize_hookname( $hookname ) {
1812
1813 $hookname = preg_replace( '/[\/\-\. \[\]:#]+/', '_', $hookname );
1814 $hookname = rtrim( $hookname, '_' ); // Only trim right side underscores to allow for '__return_false'.
1815
1816 return self::sanitize_key( $hookname, $allow_upper = false );
1817 }
1818
1819 /*
1820 * See WpssoConfig::$cf[ 'opt' ][ 'defaults' ] for example input names.
1821 *
1822 * A colon can be used for qualifiers (example, ':disabled', ':width', ':height', etc.).
1823 *
1824 * A hashtag can be used for the locale (language).
1825 *
1826 * Example sanitation:
1827 *
1828 * 'mrp_method_https://schema.org/ReturnByMail' -> 'mrp_method_https_schema_org_ReturnByMail'
1829 */
1830 public static function sanitize_input_name( $input_name, $sep = '_' ) {
1831
1832 $input_name = preg_replace( '/:\/\//', $sep, $input_name );
1833 $input_name = preg_replace( '/[^a-zA-Z0-9_\-:#]+/', $sep, $input_name );
1834 $input_name = trim( $input_name, $sep );
1835
1836 return $input_name;
1837 }
1838
1839 /*
1840 * There is no WordPress function to sanitize post IDs and integers.
1841 *
1842 * Returns an integer or null.
1843 */
1844 public static function sanitize_int( $value ) {
1845
1846 if ( is_numeric( $value ) ) {
1847
1848 return (int) $value;
1849 }
1850
1851 return null;
1852 }
1853
1854 /*
1855 * Sanitize an option array key.
1856 *
1857 * Unlike the WordPress sanitize_key() function, this method allows for colon and hash characters and (optionally)
1858 * upper case characters.
1859 *
1860 * See sucomSanitizeKey() in wpsso/js/com/jquery-admin-page.js
1861 * See wordpress/wp-includes/formatting.php.
1862 */
1863 public static function sanitize_key( $key, $allow_upper = false ) {
1864
1865 if ( is_scalar( $key ) ) { // Int, float, string or bool.
1866
1867 if ( ! $allow_upper ) $key = strtolower( $key ); // Convert upper case characters to lower case.
1868
1869 return preg_replace( '/[^a-zA-Z0-9_\-:#]/', '', $key );
1870 }
1871
1872 return '';
1873 }
1874
1875 public static function sanitize_schema_id( $key ) {
1876
1877 if ( is_scalar( $key ) ) { // Int, float, string or bool.
1878
1879 $key = strtolower( $key ); // Convert upper case characters to lower case.
1880
1881 $key = preg_replace( '/[_\-]/', '.', $key ); // Convert underscores and hyphens to periods.
1882
1883 return preg_replace( '/[^a-z\.]/', '', $key ); // Keep only alphabetic and period characters.
1884 }
1885
1886 return '';
1887 }
1888
1889 public static function sanitize_locale( $locale ) {
1890
1891 $locale = str_replace( '-', '_', $locale ); // Convert 'en-US' to 'en_US'.
1892 $locale = preg_replace( '/[^a-zA-Z_]/', '', $locale );
1893
1894 return $locale;
1895 }
1896
1897 public static function sanitize_meta_key( $meta_key ) {
1898
1899 $meta_key = self::decode_html( $meta_key );
1900 $meta_key = self::strip_html( $meta_key );
1901
1902 return $meta_key;
1903 }
1904
1905 public static function sanitize_tag( $tag ) {
1906
1907 $tag = sanitize_title_with_dashes( $tag, '', 'display' );
1908 $tag = urldecode( $tag );
1909
1910 return $tag;
1911 }
1912
1913 public static function sanitize_twitter_name( $twitter_name, $prefix_at = true ) {
1914
1915 if ( '' !== $twitter_name ) {
1916
1917 $twitter_name = substr( preg_replace( array( '/^.*\//', '/[^a-zA-Z0-9_]/' ), '', $twitter_name ), 0, 15 );
1918
1919 if ( ! empty( $twitter_name ) && $prefix_at ) {
1920
1921 $twitter_name = '@' . $twitter_name;
1922 }
1923 }
1924
1925 return $twitter_name;
1926 }
1927
1928 /*
1929 * Note that an empty string or a null is sanitized as false.
1930 *
1931 * Used by the wpssorrssb_get_sharing_buttons() function and the WpssoRrssbShortcodeSharing->do_shortcode() method.
1932 */
1933 public static function sanitize_use_post( $mixed, $default = true ) {
1934
1935 if ( is_array( $mixed ) ) {
1936
1937 $use_post = isset( $mixed[ 'use_post' ] ) ? $mixed[ 'use_post' ] : $default;
1938
1939 } elseif ( is_object( $mixed ) ) {
1940
1941 $use_post = isset( $mixed->use_post ) ? $mixed->use_post : $default;
1942
1943 } else $use_post = $mixed;
1944
1945 if ( empty( $use_post ) || 'false' === $use_post ) { // 0, false, or 'false'
1946
1947 return false;
1948
1949 } elseif ( is_numeric( $use_post ) ) {
1950
1951 return (int) $use_post;
1952 }
1953
1954 return $default;
1955 }
1956
1957 /*
1958 * TEXT HANDLING METHODS:
1959 *
1960 * append_url_fragment()
1961 * decode_html()
1962 * decode_utf8()
1963 * encode_html_emoji()
1964 * decode_url_add_query()
1965 * encode_utf8()
1966 * esc_url_encode()
1967 * explode_csv()
1968 * get_bool()
1969 * get_bool_string()
1970 * get_const()
1971 * get_file_path_locale()
1972 * get_json_scripts()
1973 * get_mod_css_id()
1974 * get_mod_salt()
1975 * get_prot()
1976 * get_stripped_php()
1977 * get_request_value()
1978 * get_url()
1979 * get_use_post_string()
1980 * insert_html_tag_attributes()
1981 * mb_convert_encoding_ucs2_utf8()
1982 * mb_decode_numericentity_utf8()
1983 * minify_css()
1984 * replace_unicode_escape()
1985 * restore_checkboxes()
1986 * safe_error_log()
1987 * strip_html()
1988 * strip_shortcodes()
1989 * strnatcasecmp_remove_accents()
1990 * unparse_url()
1991 * unquote_csv_value()
1992 * update_prot()
1993 *
1994 * Add a fragment to a URL.
1995 */
1996 public static function append_url_fragment( $url, $fragment ) {
1997
1998 return preg_replace( '/#.*/', '', $url ) . ( empty( $fragment ) ? '' : '#' . self::sanitize_css_id( $fragment ) );
1999 }
2000
2001 /*
2002 * Decode HTML entities and UTF8 encoding.
2003 */
2004 public static function decode_html( $encoded ) {
2005
2006 if ( false === strpos( $encoded, '&' ) ) {
2007
2008 return $encoded;
2009 }
2010
2011 static $charset = null;
2012
2013 if ( null === $charset ) {
2014
2015 if ( function_exists( 'get_bloginfo' ) ) {
2016
2017 $charset = get_bloginfo( $show = 'charset', $filter = 'raw' );
2018 }
2019 }
2020
2021 return html_entity_decode( self::decode_utf8( $encoded ), ENT_QUOTES, $charset );
2022 }
2023
2024 public static function decode_url_add_query( $url, array $args ) {
2025
2026 if ( false === filter_var( $url, FILTER_VALIDATE_URL ) ) { // Invalid URL.
2027
2028 return false;
2029 }
2030
2031 $parsed_url = wp_parse_url( self::decode_html( urldecode( $url ) ) );
2032
2033 if ( empty( $parsed_url ) ) {
2034
2035 return false;
2036 }
2037
2038 if ( empty( $parsed_url[ 'query' ] ) ) {
2039
2040 $parsed_url[ 'query' ] = http_build_query( $args );
2041
2042 } else $parsed_url[ 'query' ] .= '&' . http_build_query( $args );
2043
2044 $url = self::unparse_url( $parsed_url );
2045
2046 return $url;
2047 }
2048
2049 public static function decode_utf8( $encoded ) {
2050
2051 if ( false === strpos( $encoded, '&#' ) ) {
2052
2053 return $encoded;
2054 }
2055
2056 $encoded = preg_replace( '/&#8230;/', '...', $encoded );
2057
2058 if ( function_exists( 'mb_decode_numericentity' ) ) { // Just in case.
2059
2060 return preg_replace_callback( '/&#\d{2,5};/u', array( __CLASS__, 'mb_decode_numericentity_utf8' ), $encoded );
2061 }
2062
2063 return $encoded;
2064 }
2065
2066 public static function encode_html_emoji( $content ) {
2067
2068 static $charset = null;
2069
2070 if ( null === $charset ) {
2071
2072 if ( function_exists( 'get_bloginfo' ) ) {
2073
2074 $charset = get_bloginfo( $show = 'charset', $filter = 'raw' );
2075 }
2076 }
2077
2078 $content = htmlentities( $content, ENT_QUOTES, $charset, $double_encode = false );
2079
2080 if ( function_exists( 'wp_encode_emoji' ) ) {
2081
2082 $content = wp_encode_emoji( $content );
2083 }
2084
2085 return $content;
2086 }
2087
2088 public static function encode_utf8( $decoded ) {
2089
2090 $encoded = $decoded;
2091
2092 if ( function_exists( 'mb_detect_encoding' ) ) { // Just in case.
2093
2094 if ( mb_detect_encoding( $decoded, 'UTF-8') !== 'UTF-8' ) {
2095
2096 if ( function_exists( 'utf8_encode' ) ) {
2097
2098 $encoded = utf8_encode( $decoded );
2099 }
2100 }
2101 }
2102
2103 return $encoded;
2104 }
2105
2106 public static function esc_url_encode( $url, $esc_url = true ) {
2107
2108 $decoded_url = self::decode_html( $url ); // Decode HTML entities.
2109 $encoded_url = urlencode( $esc_url ? esc_url_raw( $decoded_url ) : $decoded_url );
2110 $replace = array( '%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%2B', '%24', '%2C', '%2F',
2111 '%3F', '%25', '%23', '%5B', '%5D' );
2112 $allowed = array( '!', '*', '\'', '(', ')', ';', ':', '@', '&', '=', '+', '$', ',', '/', '?', '%', '#', '[', ']' );
2113
2114 return str_replace( $replace, $allowed, $encoded_url );
2115 }
2116
2117 public static function explode_csv( $str ) {
2118
2119 if ( empty( $str ) ) {
2120
2121 return array();
2122 }
2123
2124 return array_map( array( __CLASS__, 'unquote_csv_value' ), explode( ',', $str ) );
2125 }
2126
2127 /*
2128 * Maybe convert string to boolean.
2129 *
2130 * See WpssoFaqShortcodeQuestion->do_shortcode().
2131 */
2132 public static function get_bool( $mixed ) {
2133
2134 return is_string( $mixed ) ? filter_var( $mixed, FILTER_VALIDATE_BOOLEAN ) : (bool) $mixed;
2135 }
2136
2137 public static function get_bool_string( $mixed ) {
2138
2139 if ( false === $mixed ) {
2140
2141 return 'false';
2142
2143 } elseif ( true === $mixed ) {
2144
2145 return 'true';
2146
2147 } elseif ( null === $mixed ) {
2148
2149 return 'null';
2150 }
2151
2152 return (string) $mixed;
2153 }
2154
2155 /*
2156 * Return a constant value, or $undef if not defined.
2157 */
2158 public static function get_const( $const, $undef = null ) {
2159
2160 return defined( $const ) ? constant( $const ) : $undef;
2161 }
2162
2163 /*
2164 * Check for a localized file name.
2165 */
2166 public static function get_file_path_locale( $file_path ) {
2167
2168 if ( ! class_exists( 'SucomUtilWP' ) ) { // Just in case.
2169
2170 require_once dirname( __FILE__ ) . '/util-wp.php';
2171 }
2172
2173 if ( preg_match( '/^(.*)(\.[a-z0-9]+)$/', $file_path, $matches ) ) {
2174
2175 if ( ! empty( $matches[ 2 ] ) ) { // Just in case.
2176
2177 $file_path_locale = $matches[ 1 ] . '-' . SucomUtilWP::get_locale() . $matches[ 2 ];
2178
2179 if ( file_exists( $file_path_locale ) ) {
2180
2181 $file_path = $file_path_locale;
2182 }
2183 }
2184 }
2185
2186 return $file_path;
2187 }
2188
2189 public static function get_json_scripts( $html, $do_decode = true ) {
2190
2191 /*
2192 * Remove containers that should not include json scripts.
2193 *
2194 * i = Letters in the pattern match both upper and lower case letters.
2195 * m = The "^" and "$" constructs match newlines and the complete subject string.
2196 * s = A dot metacharacter in the pattern matches all characters, including newlines.
2197 * U = Invert greediness of quantifiers, so they are NOT greedy by default, but become greedy if followed by ?.
2198 *
2199 * See http://php.net/manual/en/reference.pcre.pattern.modifiers.php.
2200 */
2201 $html = preg_replace( '/<!--.*-->/Ums', '', $html );
2202 $html = preg_replace( '/<pre[ >].*<\/pre>/Uims', '', $html );
2203 $html = preg_replace( '/<textarea[ >].*<\/textarea>/Uims', '', $html );
2204
2205 $json_scripts = array();
2206
2207 /*
2208 * i = Letters in the pattern match both upper and lower case letters.
2209 * s = A dot metacharacter in the pattern matches all characters, including newlines.
2210 * U = Inverts the "greediness" of quantifiers so that they are not greedy by default.
2211 *
2212 * See http://php.net/manual/en/reference.pcre.pattern.modifiers.php.
2213 */
2214 if ( preg_match_all( '/(<script\b[^>]*type=["\']application\/ld\+json["\'][^>]*>)(.+)(<\/script>)/Uis', $html, $all_matches, PREG_SET_ORDER ) ) {
2215
2216 foreach ( $all_matches as $num => $matches ) {
2217
2218 $json_data = json_decode( $matches[ 2 ], $assoc = true );
2219
2220 if ( preg_match( '/ id=["\']([^"\']+)["\']/', $matches[ 1 ], $m ) ) {
2221
2222 $single_id = 'id:' . $m[ 1 ];
2223
2224 } else $single_id = 'md5:' . md5( serialize( $json_data ) ); // md5() input must be a string.
2225
2226 if ( $do_decode ) { // Return the decoded json data.
2227
2228 if ( is_array( $json_data ) ) {
2229
2230 $json_scripts[ $single_id ] = $json_data;
2231
2232 } else {
2233
2234 $error_pre = sprintf( '%s error:', __METHOD__ );
2235
2236 $error_msg = sprintf( 'Error decoding json script: %s', print_r( $matches[ 2 ], true ) );
2237
2238 self::safe_error_log( $error_pre . ' ' . $error_msg );
2239 }
2240
2241 } else { // Return the json script instead.
2242
2243 $json_scripts[ $single_id ] = $matches[ 0 ];
2244 }
2245 }
2246 }
2247
2248 return $json_scripts;
2249 }
2250
2251 public static function get_mod_css_id( array $mod, $sep = '-' ) {
2252
2253 /*
2254 * Note that the sort order, page number, locale, amp and embed checks are provided by
2255 * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
2256 *
2257 * Example cache salts:
2258 *
2259 * 'post:123_type:page'
2260 * 'post:123_type:product_is_pta' // WooCommerce shop page.
2261 * 'term:123_tax:product_cat' // WooCommerce category page.
2262 */
2263 $css_id = self::get_mod_salt( $mod, $canonical_url = false, $sep );
2264 $css_id = self::sanitize_css_id( $css_id );
2265
2266 return $css_id;
2267 }
2268
2269 /*
2270 * A cache salt string based on the $mod array. If $mod is not an array, then use the canonical URL value.
2271 *
2272 * Note that the sort order, page number, locale, amp and embed checks are provided by
2273 * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
2274 *
2275 * Example cache salts:
2276 *
2277 * 'post:123_type:page'
2278 * 'post:123_type:product_is_pta' // WooCommerce shop page.
2279 * 'term:123_tax:product_cat' // WooCommerce category page.
2280 *
2281 * 'is_home_url:https_example_com' // When a canonical URL is provided.
2282 * 'post:123_type:page_url:https_example_com_page-slug' // When a canonical URL is provided.
2283 * 'url:https_example_com_2022_01' // When a canonical URL is provided.
2284 */
2285 public static function get_mod_salt( $mod = false, $canonical_url = false, $sep = '_' ) {
2286
2287 $mod_salt = '';
2288
2289 if ( is_array( $mod ) ) {
2290
2291 if ( ! empty( $mod[ 'name' ] ) ) { // Add the module name and ID.
2292
2293 $mod_salt .= $sep . $mod[ 'name' ];
2294
2295 if ( is_bool( $mod[ 'id' ] ) ) {
2296
2297 $mod_salt .= ':' . ( $mod[ 'id' ] ? 'true' : 'false' );
2298
2299 } elseif ( is_numeric( $mod[ 'id' ] ) ) { // Just in case.
2300
2301 $mod_salt .= ':' . $mod[ 'id' ];
2302 }
2303 }
2304
2305 if ( ! empty( $mod[ 'post_type' ] ) ) { // Add the post type name.
2306
2307 $mod_salt .= $sep . 'type:' . ( is_string( $mod[ 'post_type' ] ) ?
2308 $mod[ 'post_type' ] : print_r( $mod[ 'post_type' ], true ) );
2309
2310 if ( ! empty( $mod[ 'is_post_type_archive' ] ) ) { // Post type archive page.
2311
2312 $mod_salt .= $sep . 'is_pta';
2313 }
2314
2315 } elseif ( ! empty( $mod[ 'tax_slug' ] ) ) { // Add the taxonomy name.
2316
2317 $mod_salt .= $sep . 'tax:' . $mod[ 'tax_slug' ];
2318 }
2319
2320 if ( ! empty( $mod[ 'is_home' ] ) ) { // Home page (static or blog archive).
2321
2322 $mod_salt .= $sep . 'is_home';
2323 }
2324 }
2325
2326 if ( ! empty( $canonical_url ) ) {
2327
2328 $mod_salt .= $sep . 'url:' . self::sanitize_input_name( $canonical_url, $sep );
2329 }
2330
2331 $mod_salt = trim( $mod_salt, $sep );
2332
2333 return apply_filters( 'sucom_mod_salt', $mod_salt, $canonical_url );
2334 }
2335
2336 public static function get_prot( $url = '' ) {
2337
2338 if ( $url ) {
2339
2340 return self::is_https( $url ) ? 'https' : 'http';
2341
2342 } elseif ( self::is_https() ) {
2343
2344 return 'https';
2345
2346 } elseif ( is_admin() ) {
2347
2348 if ( defined( 'FORCE_SSL_ADMIN' ) && FORCE_SSL_ADMIN ) {
2349
2350 return 'https';
2351 }
2352
2353 } elseif ( defined( 'FORCE_SSL' ) && FORCE_SSL ) {
2354
2355 return 'https';
2356 }
2357
2358 return 'http';
2359 }
2360
2361 /*
2362 * $method = 'ANY' uses the current request method.
2363 */
2364 public static function get_request_value( $key, $method = 'ANY', $default = '' ) {
2365
2366 switch ( 'ANY' === $method ? $_SERVER[ 'REQUEST_METHOD' ] : $method ) {
2367
2368 case 'POST':
2369
2370 if ( isset( $_POST[ $key ] ) ) {
2371
2372 return sanitize_text_field( $_POST[ $key ] );
2373 }
2374
2375 break;
2376
2377 case 'GET':
2378
2379 if ( isset( $_GET[ $key ] ) ) {
2380
2381 return sanitize_text_field( $_GET[ $key ] );
2382 }
2383
2384 break;
2385 }
2386
2387 return $default;
2388 }
2389
2390 public static function get_stripped_php( $file_path ) {
2391
2392 $stripped_php = '';
2393
2394 if ( file_exists( $file_path ) ) {
2395
2396 $content = file_get_contents( $file_path );
2397
2398 $comments = array( T_COMMENT );
2399
2400 if ( defined( 'T_DOC_COMMENT' ) ) {
2401
2402 $comments[] = T_DOC_COMMENT; // PHP 5.
2403 }
2404
2405 if ( defined( 'T_ML_COMMENT' ) ) {
2406
2407 $comments[] = T_ML_COMMENT; // PHP 4.
2408 }
2409
2410 $tokens = token_get_all( $content );
2411
2412 foreach ( $tokens as $token ) {
2413
2414 if ( is_array( $token ) ) {
2415
2416 if ( in_array( $token[ 0 ], $comments ) ) {
2417
2418 continue;
2419 }
2420
2421 $token = $token[ 1 ];
2422 }
2423
2424 $stripped_php .= $token;
2425 }
2426
2427 } else $stripped_php = false;
2428
2429 return $stripped_php;
2430 }
2431
2432 /*
2433 * Get the current $_SERVER[ 'REQUEST_URI' ] URL, optionally with tracking and advertising query args removed.
2434 */
2435 public static function get_url( $clean_args = true ) {
2436
2437 static $local_cache = array();
2438
2439 $cache_idx = $clean_args ? 0 : 1; // Converts null to 0.
2440
2441 if ( isset( $local_cache[ $cache_idx ] ) ) {
2442
2443 return $local_cache[ $cache_idx ];
2444 }
2445
2446 $base_url = preg_replace( '/^(.*\/\/[^\/]*).*$/', '$1', get_home_url() );
2447
2448 $local_cache[ $cache_idx ] = esc_url_raw( $base_url . $_SERVER[ 'REQUEST_URI' ] );
2449
2450 if ( $clean_args ) { // Remove tracking and advertising query args.
2451
2452 $query_args = apply_filters( 'sucom_url_clean_query_args', self::$url_clean_query );
2453
2454 if ( ! empty( $query_args ) ) { // Just in case.
2455
2456 $query_args = array_flip( $query_args ); // Move values to keys.
2457
2458 $query_args = array_fill_keys( array_keys( $query_args ), false ); // Set all values to false.
2459
2460 $local_cache[ $cache_idx ] = add_query_arg( $query_args, $local_cache[ $cache_idx ] ); // Remove keys with false values.
2461 }
2462 }
2463
2464 return $local_cache[ $cache_idx ];
2465 }
2466
2467 public static function get_use_post_string( $use_post ) {
2468
2469 $use_post = self::sanitize_use_post( $use_post );
2470
2471 return self::get_bool_string( $use_post );
2472 }
2473
2474 /*
2475 * Add one or more attributes to the HTML tag.
2476 *
2477 * Example HTML tag:
2478 *
2479 * $html argument = '<img src="/image.jpg">
2480 * $html returned = '<img src="/image.jpg" data-pin-nopin="nopin">'
2481 */
2482 public static function insert_html_tag_attributes( $html, array $attr_names_values ) {
2483
2484 foreach ( $attr_names_values as $attr_name => $attr_value ) {
2485
2486 if ( false !== $attr_value && false === strpos( $html, ' ' . $attr_name . '=' ) ) {
2487
2488 $html = preg_replace( '/ *\/?' . '>/', ' ' . $attr_name . '="' . $attr_value . '"$0', $html );
2489 }
2490 }
2491
2492 return $html;
2493 }
2494
2495 public static function mb_convert_encoding_ucs2_utf8( array $match ) {
2496
2497 return mb_convert_encoding( pack( 'H*', $match[ 1 ] ), $to_encoding = 'UTF-8', $from_encoding = 'UCS-2' );
2498 }
2499
2500 public static function mb_decode_numericentity_utf8( array $match ) {
2501
2502 $convmap = array( 0x0, 0x10000, 0, 0xfffff );
2503
2504 return mb_decode_numericentity( $match[ 0 ], $convmap, 'UTF-8' );
2505 }
2506
2507 public static function minify_css( $css_data, $filter_prefix = 'sucom' ) {
2508
2509 if ( ! empty( $css_data ) ) { // Make sure we have something to minify.
2510
2511 $classname = apply_filters( $filter_prefix . '_load_lib', false, 'ext/compressor', 'SuextMinifyCssCompressor' );
2512
2513 if ( 'SuextMinifyCssCompressor' === $classname && class_exists( $classname ) ) {
2514
2515 $css_data = $classname::process( $css_data );
2516 }
2517 }
2518
2519 return $css_data;
2520 }
2521
2522 /*
2523 * Decode Facebook video URLs.
2524 */
2525 public static function replace_unicode_escape( $str ) {
2526
2527 if ( function_exists( 'mb_convert_encoding' ) ) {
2528
2529 return preg_replace_callback( '/\\\\u([0-9a-f]{4})/i', array( __CLASS__, 'mb_convert_encoding_ucs2_utf8' ), $str );
2530 }
2531
2532 return $str;
2533 }
2534
2535 /*
2536 * Unchecked checkboxes are not submitted, so re-create them based on hidden form values.
2537 */
2538 public static function restore_checkboxes( $opts ) {
2539
2540 if ( is_array( $opts ) ) { // Just in case.
2541
2542 $checkbox = self::preg_grep_keys( '/^is_checkbox_/', $opts, $invert = false, $replace = '' );
2543
2544 foreach ( $checkbox as $key => $val ) {
2545
2546 if ( ! array_key_exists( $key, $opts ) ) {
2547
2548 $opts[ $key ] = 0; // Add missing checkbox as empty.
2549 }
2550
2551 unset( $opts[ 'is_checkbox_' . $key] );
2552 }
2553 }
2554
2555 return $opts;
2556 }
2557
2558 public static function safe_error_log( $error_msg, $strip_html = false ) {
2559
2560 $ini_safe = array(
2561 'display_errors' => 0,
2562 'log_errors' => 1,
2563 'error_log' => defined( 'WP_DEBUG_LOG' ) && is_string( WP_DEBUG_LOG ) && WP_DEBUG_LOG ?
2564 WP_DEBUG_LOG : WP_CONTENT_DIR . '/debug.log',
2565 );
2566
2567 $ini_orig = array();
2568
2569 foreach ( $ini_safe as $name => $value ) {
2570
2571 $value_orig = ini_get( $name ); // Returns false on failure.
2572
2573 if ( $value !== $value_orig ) {
2574
2575 if ( false !== ini_set( $name, $value ) ) { // Returns false on failure.
2576
2577 $ini_orig[ $name ] = $value_orig;
2578 }
2579 }
2580 }
2581
2582 if ( $strip_html ) {
2583
2584 $error_msg = self::strip_html( $error_msg );
2585 }
2586
2587 /*
2588 * Use error_log() instead of trigger_error() to avoid HTTP 500.
2589 */
2590 error_log( $error_msg );
2591
2592 foreach ( $ini_orig as $name => $value_orig ) {
2593
2594 /*
2595 * Failed getting the original value.
2596 */
2597 if ( false === $value_orig ) {
2598
2599 ini_restore( $name );
2600
2601 /*
2602 * PHP does not allow setting an empty 'error_log' when ini_get( 'open_basedir' ) is true.
2603 */
2604 } elseif ( 'error_log' === $name && empty( $value_orig ) && ini_get( 'open_basedir' ) ) {
2605
2606 ini_restore( $name );
2607
2608 } else ini_set( $name, $value_orig );
2609 }
2610 }
2611
2612 public static function strip_html( $text ) {
2613
2614 /*
2615 * i = Letters in the pattern match both upper and lower case letters.
2616 * s = A dot metacharacter in the pattern matches all characters, including newlines.
2617 * U = Invert greediness of quantifiers, so they are NOT greedy by default, but become greedy if followed by ?.
2618 *
2619 * See http://php.net/manual/en/reference.pcre.pattern.modifiers.php.
2620 */
2621 $text = self::strip_shortcodes( $text ); // Remove any remaining shortcodes.
2622 $text = preg_replace( '/[\s\n\r]+/s', ' ', $text ); // Put everything on one line.
2623 $text = preg_replace( '/<\?.*\?' . '>/U', ' ', $text ); // Remove php.
2624 $text = preg_replace( '/<script\b[^>]*>(.*)<\/script>/Ui', ' ', $text ); // Remove javascript.
2625 $text = preg_replace( '/<style\b[^>]*>(.*)<\/style>/Ui', ' ', $text ); // Remove inline stylesheets.
2626 $text = preg_replace( '/([\w])<\/(button|dt|h[0-9]+|li|th)>/i', '$1. ', $text ); // Add missing dot to buttons, headers, lists, etc.
2627 $text = preg_replace( '/(<p>|<p[^>]+>|<\/p>)/i', ' ', $text ); // Replace paragraph tags with a space.
2628 $text = trim( strip_tags( $text ) ); // Strip HTML and PHP tags from a string.
2629 $text = preg_replace( '/(\xC2\xA0|\s)+/s', ' ', $text ); // Replace 1+ spaces to a single space.
2630
2631 return trim( $text );
2632 }
2633
2634 /*
2635 * Remove registered shortcodes from the content, along with some known unregistered shortcodes.
2636 */
2637 public static function strip_shortcodes( $content ) {
2638
2639 if ( false === strpos( $content, '[' ) ) { // Optimize and check if there are shortcodes.
2640
2641 return $content;
2642 }
2643
2644 if ( function_exists( 'strip_shortcodes' ) ) {
2645
2646 $content = strip_shortcodes( $content ); // Remove registered shortcodes.
2647
2648 if ( false === strpos( $content, '[' ) ) { // Stop here if there are no more shortcodes.
2649
2650 return $content;
2651 }
2652 }
2653
2654 $shortcodes_preg = array( '/\[\/?(cs_element_|et_|fusion_|mk_|rev_slider_|vc_)[^\]]+\]/' );
2655
2656 $content = preg_replace( $shortcodes_preg, ' ', $content );
2657
2658 return $content;
2659 }
2660
2661 protected static function strnatcasecmp_remove_accents( $a, $b ) {
2662
2663 return strnatcasecmp( remove_accents( $a ), remove_accents( $b ) );
2664 }
2665
2666 public static function unparse_url( $parsed_url ) {
2667
2668 $scheme = isset( $parsed_url[ 'scheme' ] ) ? $parsed_url[ 'scheme' ] . '://' : '';
2669 $user = isset( $parsed_url[ 'user' ] ) ? $parsed_url[ 'user' ] : '';
2670 $pass = isset( $parsed_url[ 'pass' ] ) ? ':' . $parsed_url[ 'pass' ] : '';
2671 $host = isset( $parsed_url[ 'host' ] ) ? $parsed_url[ 'host' ] : '';
2672 $port = isset( $parsed_url[ 'port' ] ) ? ':' . $parsed_url[ 'port' ] : '';
2673 $path = isset( $parsed_url[ 'path' ] ) ? $parsed_url[ 'path' ] : '';
2674 $query = isset( $parsed_url[ 'query' ] ) ? '?' . $parsed_url[ 'query' ] : '';
2675 $fragment = isset( $parsed_url[ 'fragment' ] ) ? '#' . $parsed_url[ 'fragment' ] : '';
2676
2677 return $scheme . $user . $pass . ( $user || $pass ? '@' : '' ) . $host . $port . $path . $query . $fragment;
2678 }
2679
2680 protected static function unquote_csv_value( $val ) {
2681
2682 return trim( $val, '\'" ' ); // Remove quotes and spaces.
2683 }
2684
2685 public static function update_prot( $url = '' ) {
2686
2687 if ( 0 === strpos( $url, '/' ) ) { // Skip relative URLs.
2688
2689 return $url;
2690 }
2691
2692 $prot_slash = self::get_prot() . '://';
2693
2694 if ( 0 === strpos( $url, $prot_slash ) ) { // Skip correct URLs.
2695
2696 return $url;
2697 }
2698
2699 return preg_replace( '/^([a-z]+:\/\/)/', $prot_slash, $url );
2700 }
2701 }
2702 }
2703