PluginProbe ʕ •ᴥ•ʔ
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 24.8.1
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v24.8.1
27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 19.1 19.10 19.11 19.12 19.13 19.14 19.2 19.3 19.4 19.5 19.5.1 19.6 19.6.1 19.7 19.7.1 19.7.2 19.8 19.9 20.0 20.1 20.10 20.11 20.12 20.13 20.2 20.2.1 20.3 20.4 20.5 20.6 20.7 20.8 20.9 21.0 21.1 21.2 21.3 21.4 21.5 21.6 21.7 21.8 21.8.1 21.9 21.9.1 22.0 22.1 22.2 22.3 22.4 22.5 22.6 22.7 22.8 22.9 23.0 23.1 23.2 23.3 23.4 23.5 23.6 23.7 23.8 23.9 24.0 24.1 24.2 24.3 24.4 24.5 24.6 24.7 24.8 24.8.1 24.9 25.0 25.1 25.2 25.3 25.3.1 25.4 25.5 25.6 25.7 25.8 25.9 26.0 26.1 26.1.1 26.2 26.3 26.4 26.5 26.6 26.7 26.8 26.9 27.0 27.1 27.1.1 27.2 27.3 27.4
wordpress-seo / inc / class-wpseo-utils.php
wordpress-seo / inc Last commit date
exceptions 5 years ago options 1 year ago sitemaps 1 year ago class-addon-manager.php 1 year ago class-my-yoast-api-request.php 1 year ago class-post-type.php 1 year ago class-rewrite.php 1 year ago class-upgrade-history.php 1 year ago class-upgrade.php 1 year ago class-wpseo-admin-bar-menu.php 1 year ago class-wpseo-content-images.php 1 year ago class-wpseo-custom-fields.php 1 year ago class-wpseo-custom-taxonomies.php 1 year ago class-wpseo-image-utils.php 1 year ago class-wpseo-installation.php 2 years ago class-wpseo-meta.php 1 year ago class-wpseo-primary-term.php 2 years ago class-wpseo-rank.php 1 year ago class-wpseo-replace-vars.php 1 year ago class-wpseo-replacement-variable.php 5 years ago class-wpseo-shortlinker.php 2 years ago class-wpseo-statistics.php 5 years ago class-wpseo-utils.php 1 year ago class-yoast-dynamic-rewrites.php 2 years ago date-helper.php 5 years ago index.php 10 years ago interface-wpseo-wordpress-ajax-integration.php 7 years ago interface-wpseo-wordpress-integration.php 7 years ago language-utils.php 2 years ago wpseo-functions-deprecated.php 2 years ago wpseo-functions.php 2 years ago wpseo-non-ajax-functions.php 5 years ago
class-wpseo-utils.php
1101 lines
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Internals
6 * @since 1.8.0
7 */
8
9 use Yoast\WP\SEO\Integrations\Feature_Flag_Integration;
10
11 /**
12 * Group of utility methods for use by WPSEO.
13 * All methods are static, this is just a sort of namespacing class wrapper.
14 */
15 class WPSEO_Utils {
16
17 /**
18 * Whether the PHP filter extension is enabled.
19 *
20 * @since 1.8.0
21 *
22 * @var bool
23 */
24 public static $has_filters;
25
26 /**
27 * Check whether file editing is allowed for the .htaccess and robots.txt files.
28 *
29 * {@internal current_user_can() checks internally whether a user is on wp-ms and adjusts accordingly.}}
30 *
31 * @since 1.8.0
32 *
33 * @return bool
34 */
35 public static function allow_system_file_edit() {
36 $allowed = true;
37
38 if ( current_user_can( 'edit_files' ) === false ) {
39 $allowed = false;
40 }
41
42 /**
43 * Filter: 'wpseo_allow_system_file_edit' - Allow developers to change whether the editing of
44 * .htaccess and robots.txt is allowed.
45 *
46 * @param bool $allowed Whether file editing is allowed.
47 */
48 return apply_filters( 'wpseo_allow_system_file_edit', $allowed );
49 }
50
51 /**
52 * Check if the web server is running on Apache or compatible (LiteSpeed).
53 *
54 * @since 1.8.0
55 *
56 * @return bool
57 */
58 public static function is_apache() {
59 if ( ! isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
60 return false;
61 }
62
63 $software = sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) );
64
65 return stripos( $software, 'apache' ) !== false || stripos( $software, 'litespeed' ) !== false;
66 }
67
68 /**
69 * Check if the web server is running on Nginx.
70 *
71 * @since 1.8.0
72 *
73 * @return bool
74 */
75 public static function is_nginx() {
76 if ( ! isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
77 return false;
78 }
79
80 $software = sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) );
81
82 return stripos( $software, 'nginx' ) !== false;
83 }
84
85 /**
86 * Check whether a url is relative.
87 *
88 * @since 1.8.0
89 *
90 * @param string $url URL string to check.
91 *
92 * @return bool
93 */
94 public static function is_url_relative( $url ) {
95 return YoastSEO()->helpers->url->is_relative( $url );
96 }
97
98 /**
99 * Recursively trim whitespace round a string value or of string values within an array.
100 * Only trims strings to avoid typecasting a variable (to string).
101 *
102 * @since 1.8.0
103 *
104 * @param mixed $value Value to trim or array of values to trim.
105 *
106 * @return mixed Trimmed value or array of trimmed values.
107 */
108 public static function trim_recursive( $value ) {
109 if ( is_string( $value ) ) {
110 $value = trim( $value );
111 }
112 elseif ( is_array( $value ) ) {
113 $value = array_map( [ self::class, 'trim_recursive' ], $value );
114 }
115
116 return $value;
117 }
118
119 /**
120 * Emulate the WP native sanitize_text_field function in a %%variable%% safe way.
121 *
122 * Sanitize a string from user input or from the db.
123 *
124 * - Check for invalid UTF-8;
125 * - Convert single < characters to entity;
126 * - Strip all tags;
127 * - Remove line breaks, tabs and extra white space;
128 * - Strip octets - BUT DO NOT REMOVE (part of) VARIABLES WHICH WILL BE REPLACED.
129 *
130 * @link https://core.trac.wordpress.org/browser/trunk/src/wp-includes/formatting.php for the original.
131 *
132 * @since 1.8.0
133 *
134 * @param string $value String value to sanitize.
135 *
136 * @return string
137 */
138 public static function sanitize_text_field( $value ) {
139 $filtered = wp_check_invalid_utf8( $value );
140
141 if ( strpos( $filtered, '<' ) !== false ) {
142 $filtered = wp_pre_kses_less_than( $filtered );
143 // This will strip extra whitespace for us.
144 $filtered = wp_strip_all_tags( $filtered, true );
145 }
146 else {
147 $filtered = trim( preg_replace( '`[\r\n\t ]+`', ' ', $filtered ) );
148 }
149
150 $found = false;
151 while ( preg_match( '`[^%](%[a-f0-9]{2})`i', $filtered, $match ) ) {
152 $filtered = str_replace( $match[1], '', $filtered );
153 $found = true;
154 }
155 unset( $match );
156
157 if ( $found ) {
158 // Strip out the whitespace that may now exist after removing the octets.
159 $filtered = trim( preg_replace( '` +`', ' ', $filtered ) );
160 }
161
162 /**
163 * Filter a sanitized text field string.
164 *
165 * @since WP 2.9.0
166 *
167 * @param string $filtered The sanitized string.
168 * @param string $str The string prior to being sanitized.
169 */
170 return apply_filters( 'sanitize_text_field', $filtered, $value ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Using WP native filter.
171 }
172
173 /**
174 * Sanitize a url for saving to the database.
175 * Not to be confused with the old native WP function.
176 *
177 * @since 1.8.0
178 *
179 * @param string $value String URL value to sanitize.
180 * @param array $allowed_protocols Optional set of allowed protocols.
181 *
182 * @return string
183 */
184 public static function sanitize_url( $value, $allowed_protocols = [ 'http', 'https' ] ) {
185
186 $url = '';
187 $parts = wp_parse_url( $value );
188
189 if ( isset( $parts['scheme'], $parts['host'] ) ) {
190 $url = $parts['scheme'] . '://';
191
192 if ( isset( $parts['user'] ) ) {
193 $url .= rawurlencode( $parts['user'] );
194 $url .= isset( $parts['pass'] ) ? ':' . rawurlencode( $parts['pass'] ) : '';
195 $url .= '@';
196 }
197
198 $parts['host'] = preg_replace(
199 '`[^a-z0-9-.:\[\]\\x80-\\xff]`',
200 '',
201 strtolower( $parts['host'] )
202 );
203
204 $url .= $parts['host'] . ( isset( $parts['port'] ) ? ':' . intval( $parts['port'] ) : '' );
205 }
206
207 if ( isset( $parts['path'] ) && strpos( $parts['path'], '/' ) === 0 ) {
208 $path = explode( '/', wp_strip_all_tags( $parts['path'] ) );
209 $path = self::sanitize_encoded_text_field( $path );
210 $url .= str_replace( '%40', '@', implode( '/', $path ) );
211 }
212
213 if ( ! $url ) {
214 return '';
215 }
216
217 if ( isset( $parts['query'] ) ) {
218 wp_parse_str( $parts['query'], $parsed_query );
219
220 $parsed_query = array_combine(
221 self::sanitize_encoded_text_field( array_keys( $parsed_query ) ),
222 self::sanitize_encoded_text_field( array_values( $parsed_query ) )
223 );
224
225 $url = add_query_arg( $parsed_query, $url );
226 }
227
228 if ( isset( $parts['fragment'] ) ) {
229 $url .= '#' . self::sanitize_encoded_text_field( $parts['fragment'] );
230 }
231
232 if ( strpos( $url, '%' ) !== false ) {
233 $url = preg_replace_callback(
234 '`%[a-fA-F0-9]{2}`',
235 static function ( $octects ) {
236 return strtolower( $octects[0] );
237 },
238 $url
239 );
240 }
241
242 return esc_url_raw( $url, $allowed_protocols );
243 }
244
245 /**
246 * Decode, sanitize and encode the array of strings or the string.
247 *
248 * @since 13.3
249 *
250 * @param array|string $value The value to sanitize and encode.
251 *
252 * @return array|string The sanitized value.
253 */
254 public static function sanitize_encoded_text_field( $value ) {
255 if ( is_array( $value ) ) {
256 return array_map( [ self::class, 'sanitize_encoded_text_field' ], $value );
257 }
258
259 return rawurlencode( sanitize_text_field( rawurldecode( $value ) ) );
260 }
261
262 /**
263 * Validate a value as boolean.
264 *
265 * @since 1.8.0
266 *
267 * @param mixed $value Value to validate.
268 *
269 * @return bool
270 */
271 public static function validate_bool( $value ) {
272 if ( ! isset( self::$has_filters ) ) {
273 self::$has_filters = extension_loaded( 'filter' );
274 }
275
276 if ( self::$has_filters ) {
277 return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
278 }
279 else {
280 return self::emulate_filter_bool( $value );
281 }
282 }
283
284 /**
285 * Cast a value to bool.
286 *
287 * @since 1.8.0
288 *
289 * @param mixed $value Value to cast.
290 *
291 * @return bool
292 */
293 public static function emulate_filter_bool( $value ) {
294 $true = [
295 '1',
296 'true',
297 'True',
298 'TRUE',
299 'y',
300 'Y',
301 'yes',
302 'Yes',
303 'YES',
304 'on',
305 'On',
306 'ON',
307 ];
308 $false = [
309 '0',
310 'false',
311 'False',
312 'FALSE',
313 'n',
314 'N',
315 'no',
316 'No',
317 'NO',
318 'off',
319 'Off',
320 'OFF',
321 ];
322
323 if ( is_bool( $value ) ) {
324 return $value;
325 }
326 elseif ( is_int( $value ) && ( $value === 0 || $value === 1 ) ) {
327 return (bool) $value;
328 }
329 elseif ( ( is_float( $value ) && ! is_nan( $value ) ) && ( $value === (float) 0 || $value === (float) 1 ) ) {
330 return (bool) $value;
331 }
332 elseif ( is_string( $value ) ) {
333 $value = trim( $value );
334 if ( in_array( $value, $true, true ) ) {
335 return true;
336 }
337 elseif ( in_array( $value, $false, true ) ) {
338 return false;
339 }
340 else {
341 return false;
342 }
343 }
344
345 return false;
346 }
347
348 /**
349 * Validate a value as integer.
350 *
351 * @since 1.8.0
352 *
353 * @param mixed $value Value to validate.
354 *
355 * @return int|bool Int or false in case of failure to convert to int.
356 */
357 public static function validate_int( $value ) {
358 if ( ! isset( self::$has_filters ) ) {
359 self::$has_filters = extension_loaded( 'filter' );
360 }
361
362 if ( self::$has_filters ) {
363 return filter_var( $value, FILTER_VALIDATE_INT );
364 }
365 else {
366 return self::emulate_filter_int( $value );
367 }
368 }
369
370 /**
371 * Cast a value to integer.
372 *
373 * @since 1.8.0
374 *
375 * @param mixed $value Value to cast.
376 *
377 * @return int|bool
378 */
379 public static function emulate_filter_int( $value ) {
380 if ( is_int( $value ) ) {
381 return $value;
382 }
383 elseif ( is_float( $value ) ) {
384 // phpcs:ignore Universal.Operators.StrictComparisons -- Purposeful loose comparison.
385 if ( (int) $value == $value && ! is_nan( $value ) ) {
386 return (int) $value;
387 }
388 else {
389 return false;
390 }
391 }
392 elseif ( is_string( $value ) ) {
393 $value = trim( $value );
394 if ( $value === '' ) {
395 return false;
396 }
397 elseif ( ctype_digit( $value ) ) {
398 return (int) $value;
399 }
400 elseif ( strpos( $value, '-' ) === 0 && ctype_digit( substr( $value, 1 ) ) ) {
401 return (int) $value;
402 }
403 else {
404 return false;
405 }
406 }
407
408 return false;
409 }
410
411 /**
412 * Clears the WP or W3TC cache depending on which is used.
413 *
414 * @since 1.8.0
415 *
416 * @return void
417 */
418 public static function clear_cache() {
419 if ( function_exists( 'w3tc_flush_posts' ) ) {
420 w3tc_flush_posts();
421 }
422 elseif ( function_exists( 'wp_cache_clear_cache' ) ) {
423 wp_cache_clear_cache();
424 }
425 }
426
427 /**
428 * Clear rewrite rules.
429 *
430 * @since 1.8.0
431 *
432 * @return void
433 */
434 public static function clear_rewrites() {
435 update_option( 'rewrite_rules', '' );
436 }
437
438 /**
439 * Do simple reliable math calculations without the risk of wrong results.
440 *
441 * In the rare case that the bcmath extension would not be loaded, it will return the normal calculation results.
442 *
443 * @link http://floating-point-gui.de/
444 * @link http://php.net/language.types.float.php See the big red warning.
445 *
446 * @since 1.5.0
447 * @since 1.8.0 Moved from stand-alone function to this class.
448 *
449 * @param mixed $number1 Scalar (string/int/float/bool).
450 * @param string $action Calculation action to execute. Valid input:
451 * '+' or 'add' or 'addition',
452 * '-' or 'sub' or 'subtract',
453 * '*' or 'mul' or 'multiply',
454 * '/' or 'div' or 'divide',
455 * '%' or 'mod' or 'modulus'
456 * '=' or 'comp' or 'compare'.
457 * @param mixed $number2 Scalar (string/int/float/bool).
458 * @param bool $round Whether or not to round the result. Defaults to false.
459 * Will be disregarded for a compare operation.
460 * @param int $decimals Decimals for rounding operation. Defaults to 0.
461 * @param int $precision Calculation precision. Defaults to 10.
462 *
463 * @return mixed Calculation Result or false if either or the numbers isn't scalar or
464 * an invalid operation was passed.
465 * - For compare the result will always be an integer.
466 * - For all other operations, the result will either be an integer (preferred)
467 * or a float.
468 */
469 public static function calc( $number1, $action, $number2, $round = false, $decimals = 0, $precision = 10 ) {
470 static $bc;
471
472 if ( ! is_scalar( $number1 ) || ! is_scalar( $number2 ) ) {
473 return false;
474 }
475
476 if ( ! isset( $bc ) ) {
477 $bc = extension_loaded( 'bcmath' );
478 }
479
480 if ( $bc ) {
481 $number1 = number_format( $number1, 10, '.', '' );
482 $number2 = number_format( $number2, 10, '.', '' );
483 }
484
485 $result = null;
486 $compare = false;
487
488 switch ( $action ) {
489 case '+':
490 case 'add':
491 case 'addition':
492 $result = ( $bc ) ? bcadd( $number1, $number2, $precision ) /* string */ : ( $number1 + $number2 );
493 break;
494
495 case '-':
496 case 'sub':
497 case 'subtract':
498 $result = ( $bc ) ? bcsub( $number1, $number2, $precision ) /* string */ : ( $number1 - $number2 );
499 break;
500
501 case '*':
502 case 'mul':
503 case 'multiply':
504 $result = ( $bc ) ? bcmul( $number1, $number2, $precision ) /* string */ : ( $number1 * $number2 );
505 break;
506
507 case '/':
508 case 'div':
509 case 'divide':
510 if ( $bc ) {
511 $result = bcdiv( $number1, $number2, $precision ); // String, or NULL if right_operand is 0.
512 }
513 elseif ( $number2 != 0 ) { // phpcs:ignore Universal.Operators.StrictComparisons -- Purposeful loose comparison.
514 $result = ( $number1 / $number2 );
515 }
516
517 if ( ! isset( $result ) ) {
518 $result = 0;
519 }
520 break;
521
522 case '%':
523 case 'mod':
524 case 'modulus':
525 if ( $bc ) {
526 $result = bcmod( $number1, $number2 ); // String, or NULL if modulus is 0.
527 }
528 elseif ( $number2 != 0 ) { // phpcs:ignore Universal.Operators.StrictComparisons -- Purposeful loose comparison.
529 $result = ( $number1 % $number2 );
530 }
531
532 if ( ! isset( $result ) ) {
533 $result = 0;
534 }
535 break;
536
537 case '=':
538 case 'comp':
539 case 'compare':
540 $compare = true;
541 if ( $bc ) {
542 $result = bccomp( $number1, $number2, $precision ); // Returns int 0, 1 or -1.
543 }
544 else {
545 // phpcs:ignore Universal.Operators.StrictComparisons -- Purposeful loose comparison.
546 $result = ( $number1 == $number2 ) ? 0 : ( ( $number1 > $number2 ) ? 1 : -1 );
547 }
548 break;
549 }
550
551 if ( isset( $result ) ) {
552 if ( $compare === false ) {
553 if ( $round === true ) {
554 $result = round( floatval( $result ), $decimals );
555 if ( $decimals === 0 ) {
556 $result = (int) $result;
557 }
558 }
559 else {
560 // phpcs:ignore Universal.Operators.StrictComparisons -- Purposeful loose comparison.
561 $result = ( intval( $result ) == $result ) ? intval( $result ) : floatval( $result );
562 }
563 }
564
565 return $result;
566 }
567
568 return false;
569 }
570
571 /**
572 * Trim whitespace and NBSP (Non-breaking space) from string.
573 *
574 * @since 2.0.0
575 *
576 * @param string $text String input to trim.
577 *
578 * @return string
579 */
580 public static function trim_nbsp_from_string( $text ) {
581 $find = [ '&nbsp;', chr( 0xC2 ) . chr( 0xA0 ) ];
582 $text = str_replace( $find, ' ', $text );
583 $text = trim( $text );
584
585 return $text;
586 }
587
588 /**
589 * Check if a string is a valid datetime.
590 *
591 * @since 2.0.0
592 *
593 * @param string $datetime String input to check as valid input for DateTime class.
594 *
595 * @return bool
596 */
597 public static function is_valid_datetime( $datetime ) {
598 return YoastSEO()->helpers->date->is_valid_datetime( $datetime );
599 }
600
601 /**
602 * Format the URL to be sure it is okay for using as a redirect url.
603 *
604 * This method will parse the URL and combine them in one string.
605 *
606 * @since 2.3.0
607 *
608 * @param string $url URL string.
609 *
610 * @return mixed
611 */
612 public static function format_url( $url ) {
613 $parsed_url = wp_parse_url( $url );
614
615 $formatted_url = '';
616 if ( ! empty( $parsed_url['path'] ) ) {
617 $formatted_url = $parsed_url['path'];
618 }
619
620 // Prepend a slash if first char != slash.
621 if ( stripos( $formatted_url, '/' ) !== 0 ) {
622 $formatted_url = '/' . $formatted_url;
623 }
624
625 // Append 'query' string if it exists.
626 if ( ! empty( $parsed_url['query'] ) ) {
627 $formatted_url .= '?' . $parsed_url['query'];
628 }
629
630 return apply_filters( 'wpseo_format_admin_url', $formatted_url );
631 }
632
633 /**
634 * Retrieves the sitename.
635 *
636 * @since 3.0.0
637 *
638 * @return string
639 */
640 public static function get_site_name() {
641 return YoastSEO()->helpers->site->get_site_name();
642 }
643
644 /**
645 * Check if the current opened page is a Yoast SEO page.
646 *
647 * @since 3.0.0
648 *
649 * @return bool
650 */
651 public static function is_yoast_seo_page() {
652 return YoastSEO()->helpers->current_page->is_yoast_seo_page();
653 }
654
655 /**
656 * Check if the current opened page belongs to Yoast SEO Free.
657 *
658 * @since 3.3.0
659 *
660 * @param string $current_page The current page the user is on.
661 *
662 * @return bool
663 */
664 public static function is_yoast_seo_free_page( $current_page ) {
665 $yoast_seo_free_pages = [
666 'wpseo_dashboard',
667 'wpseo_tools',
668 'wpseo_search_console',
669 'wpseo_licenses',
670 ];
671
672 return in_array( $current_page, $yoast_seo_free_pages, true );
673 }
674
675 /**
676 * Determine if Yoast SEO is in development mode?
677 *
678 * Inspired by JetPack (https://github.com/Automattic/jetpack/blob/master/class.jetpack.php#L1383-L1406).
679 *
680 * @since 3.0.0
681 *
682 * @return bool
683 */
684 public static function is_development_mode() {
685 $development_mode = false;
686
687 if ( defined( 'YOAST_ENVIRONMENT' ) && YOAST_ENVIRONMENT === 'development' ) {
688 $development_mode = true;
689 }
690 elseif ( defined( 'WPSEO_DEBUG' ) ) {
691 $development_mode = WPSEO_DEBUG;
692 }
693 elseif ( site_url() && strpos( site_url(), '.' ) === false ) {
694 $development_mode = true;
695 }
696
697 /**
698 * Filter the Yoast SEO development mode.
699 *
700 * @since 3.0
701 *
702 * @param bool $development_mode Is Yoast SEOs development mode active.
703 */
704 return apply_filters( 'yoast_seo_development_mode', $development_mode );
705 }
706
707 /**
708 * Retrieve home URL with proper trailing slash.
709 *
710 * @since 3.3.0
711 *
712 * @param string $path Path relative to home URL.
713 * @param string|null $scheme Scheme to apply.
714 *
715 * @return string Home URL with optional path, appropriately slashed if not.
716 */
717 public static function home_url( $path = '', $scheme = null ) {
718 return YoastSEO()->helpers->url->home( $path, $scheme );
719 }
720
721 /**
722 * Checks if the WP-REST-API is available.
723 *
724 * @since 3.6
725 * @since 3.7 Introduced the $minimum_version parameter.
726 *
727 * @param string $minimum_version The minimum version the API should be.
728 *
729 * @return bool Returns true if the API is available.
730 */
731 public static function is_api_available( $minimum_version = '2.0' ) {
732 return ( defined( 'REST_API_VERSION' )
733 && version_compare( REST_API_VERSION, $minimum_version, '>=' ) );
734 }
735
736 /**
737 * Determine whether or not the metabox should be displayed for a post type.
738 *
739 * @param string|null $post_type Optional. The post type to check the visibility of the metabox for.
740 *
741 * @return bool Whether or not the metabox should be displayed.
742 */
743 protected static function display_post_type_metabox( $post_type = null ) {
744 if ( ! isset( $post_type ) ) {
745 $post_type = get_post_type();
746 }
747
748 if ( ! isset( $post_type ) || ! WPSEO_Post_Type::is_post_type_accessible( $post_type ) ) {
749 return false;
750 }
751
752 if ( $post_type === 'attachment' && WPSEO_Options::get( 'disable-attachment' ) ) {
753 return false;
754 }
755
756 return apply_filters( 'wpseo_enable_editor_features_' . $post_type, WPSEO_Options::get( 'display-metabox-pt-' . $post_type ) );
757 }
758
759 /**
760 * Determine whether or not the metabox should be displayed for a taxonomy.
761 *
762 * @param string|null $taxonomy Optional. The post type to check the visibility of the metabox for.
763 *
764 * @return bool Whether or not the metabox should be displayed.
765 */
766 protected static function display_taxonomy_metabox( $taxonomy = null ) {
767 if ( ! isset( $taxonomy ) || ! in_array( $taxonomy, get_taxonomies( [ 'public' => true ], 'names' ), true ) ) {
768 return false;
769 }
770
771 return WPSEO_Options::get( 'display-metabox-tax-' . $taxonomy );
772 }
773
774 /**
775 * Determines whether the metabox is active for the given identifier and type.
776 *
777 * @param string $identifier The identifier to check for.
778 * @param string $type The type to check for.
779 *
780 * @return bool Whether or not the metabox is active.
781 */
782 public static function is_metabox_active( $identifier, $type ) {
783 if ( $type === 'post_type' ) {
784 return self::display_post_type_metabox( $identifier );
785 }
786
787 if ( $type === 'taxonomy' ) {
788 return self::display_taxonomy_metabox( $identifier );
789 }
790
791 return false;
792 }
793
794 /**
795 * Determines whether the plugin is active for the entire network.
796 *
797 * @return bool Whether the plugin is network-active.
798 */
799 public static function is_plugin_network_active() {
800 return YoastSEO()->helpers->url->is_plugin_network_active();
801 }
802
803 /**
804 * Gets the type of the current post.
805 *
806 * @return string The post type, or an empty string.
807 */
808 public static function get_post_type() {
809 $wp_screen = get_current_screen();
810
811 if ( $wp_screen !== null && ! empty( $wp_screen->post_type ) ) {
812 return $wp_screen->post_type;
813 }
814 return '';
815 }
816
817 /**
818 * Gets the type of the current page.
819 *
820 * @return string Returns 'post' if the current page is a post edit page. Taxonomy in other cases.
821 */
822 public static function get_page_type() {
823 global $pagenow;
824 if ( WPSEO_Metabox::is_post_edit( $pagenow ) ) {
825 return 'post';
826 }
827
828 return 'taxonomy';
829 }
830
831 /**
832 * Getter for the Adminl10n array. Applies the wpseo_admin_l10n filter.
833 *
834 * @return array The Adminl10n array.
835 */
836 public static function get_admin_l10n() {
837 $post_type = self::get_post_type();
838 $page_type = self::get_page_type();
839
840 $label_object = false;
841 $no_index = false;
842
843 if ( $page_type === 'post' ) {
844 $label_object = get_post_type_object( $post_type );
845 $no_index = WPSEO_Options::get( 'noindex-' . $post_type, false );
846 }
847 else {
848 $label_object = WPSEO_Taxonomy::get_labels();
849
850 $wp_screen = get_current_screen();
851
852 if ( $wp_screen !== null && ! empty( $wp_screen->taxonomy ) ) {
853 $taxonomy_slug = $wp_screen->taxonomy;
854 $no_index = WPSEO_Options::get( 'noindex-tax-' . $taxonomy_slug, false );
855 }
856 }
857
858 $wpseo_admin_l10n = [
859 'displayAdvancedTab' => WPSEO_Capability_Utils::current_user_can( 'wpseo_edit_advanced_metadata' ) || ! WPSEO_Options::get( 'disableadvanced_meta' ),
860 'noIndex' => (bool) $no_index,
861 'isPostType' => (bool) get_post_type(),
862 'postType' => get_post_type(),
863 'postTypeNamePlural' => ( $page_type === 'post' ) ? $label_object->label : $label_object->name,
864 'postTypeNameSingular' => ( $page_type === 'post' ) ? $label_object->labels->singular_name : $label_object->singular_name,
865 'isBreadcrumbsDisabled' => WPSEO_Options::get( 'breadcrumbs-enable', false ) !== true && ! current_theme_supports( 'yoast-seo-breadcrumbs' ),
866 'isAiFeatureActive' => (bool) WPSEO_Options::get( 'enable_ai_generator' ),
867 ];
868
869 $additional_entries = apply_filters( 'wpseo_admin_l10n', [] );
870 if ( is_array( $additional_entries ) ) {
871 $wpseo_admin_l10n = array_merge( $wpseo_admin_l10n, $additional_entries );
872 }
873
874 return $wpseo_admin_l10n;
875 }
876
877 /**
878 * Retrieves the analysis worker log level. Defaults to errors only.
879 *
880 * Uses bool YOAST_SEO_DEBUG as flag to enable logging. Off equals ERROR.
881 * Uses string YOAST_SEO_DEBUG_ANALYSIS_WORKER as log level for the Analysis
882 * Worker. Defaults to INFO.
883 * Can be: TRACE, DEBUG, INFO, WARN or ERROR.
884 *
885 * @return string The log level to use.
886 */
887 public static function get_analysis_worker_log_level() {
888 if ( defined( 'YOAST_SEO_DEBUG' ) && YOAST_SEO_DEBUG ) {
889 return defined( 'YOAST_SEO_DEBUG_ANALYSIS_WORKER' ) ? YOAST_SEO_DEBUG_ANALYSIS_WORKER : 'INFO';
890 }
891
892 return 'ERROR';
893 }
894
895 /**
896 * Returns the unfiltered home URL.
897 *
898 * In case WPML is installed, returns the original home_url and not the WPML version.
899 * In case of a multisite setup we return the network_home_url.
900 *
901 * @codeCoverageIgnore
902 *
903 * @return string The home url.
904 */
905 public static function get_home_url() {
906 return YoastSEO()->helpers->url->network_safe_home_url();
907 }
908
909 /**
910 * Prepares data for outputting as JSON.
911 *
912 * @param array $data The data to format.
913 *
914 * @return false|string The prepared JSON string.
915 */
916 public static function format_json_encode( $data ) {
917 $flags = ( JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
918
919 if ( self::is_development_mode() ) {
920 $flags = ( $flags | JSON_PRETTY_PRINT );
921
922 /**
923 * Filter the Yoast SEO development mode.
924 *
925 * @param array $data Allows filtering of the JSON data for debug purposes.
926 */
927 $data = apply_filters( 'wpseo_debug_json_data', $data );
928 }
929
930 // phpcs:ignore Yoast.Yoast.JsonEncodeAlternative.FoundWithAdditionalParams -- This is the definition of format_json_encode.
931 return wp_json_encode( $data, $flags );
932 }
933
934 /**
935 * Extends the allowed post tags with accessibility-related attributes.
936 *
937 * @codeCoverageIgnore
938 *
939 * @param array $allowed_post_tags The allowed post tags.
940 *
941 * @return array The allowed tags including post tags, input tags and select tags.
942 */
943 public static function extend_kses_post_with_a11y( $allowed_post_tags ) {
944 static $a11y_tags;
945
946 if ( isset( $a11y_tags ) === false ) {
947 $a11y_tags = [
948 'button' => [
949 'aria-expanded' => true,
950 'aria-controls' => true,
951 ],
952 'div' => [
953 'tabindex' => true,
954 ],
955 // Below are attributes that are needed for backwards compatibility (WP < 5.1).
956 'span' => [
957 'aria-hidden' => true,
958 ],
959 'input' => [
960 'aria-describedby' => true,
961 ],
962 'select' => [
963 'aria-describedby' => true,
964 ],
965 'textarea' => [
966 'aria-describedby' => true,
967 ],
968 ];
969
970 // Add the global allowed attributes to each html element.
971 $a11y_tags = array_map( '_wp_add_global_attributes', $a11y_tags );
972 }
973
974 return array_merge_recursive( $allowed_post_tags, $a11y_tags );
975 }
976
977 /**
978 * Extends the allowed post tags with input, select and option tags.
979 *
980 * @codeCoverageIgnore
981 *
982 * @param array $allowed_post_tags The allowed post tags.
983 *
984 * @return array The allowed tags including post tags, input tags, select tags and option tags.
985 */
986 public static function extend_kses_post_with_forms( $allowed_post_tags ) {
987 static $input_tags;
988
989 if ( isset( $input_tags ) === false ) {
990 $input_tags = [
991 'input' => [
992 'accept' => true,
993 'accesskey' => true,
994 'align' => true,
995 'alt' => true,
996 'autocomplete' => true,
997 'autofocus' => true,
998 'checked' => true,
999 'contenteditable' => true,
1000 'dirname' => true,
1001 'disabled' => true,
1002 'draggable' => true,
1003 'dropzone' => true,
1004 'form' => true,
1005 'formaction' => true,
1006 'formenctype' => true,
1007 'formmethod' => true,
1008 'formnovalidate' => true,
1009 'formtarget' => true,
1010 'height' => true,
1011 'hidden' => true,
1012 'lang' => true,
1013 'list' => true,
1014 'max' => true,
1015 'maxlength' => true,
1016 'min' => true,
1017 'multiple' => true,
1018 'name' => true,
1019 'pattern' => true,
1020 'placeholder' => true,
1021 'readonly' => true,
1022 'required' => true,
1023 'size' => true,
1024 'spellcheck' => true,
1025 'src' => true,
1026 'step' => true,
1027 'tabindex' => true,
1028 'translate' => true,
1029 'type' => true,
1030 'value' => true,
1031 'width' => true,
1032
1033 /*
1034 * Below are attributes that are needed for backwards compatibility (WP < 5.1).
1035 * They are used for the social media image in the metabox.
1036 * These can be removed once we move to the React versions of the social previews.
1037 */
1038 'data-target' => true,
1039 'data-target-id' => true,
1040 ],
1041 'select' => [
1042 'accesskey' => true,
1043 'autofocus' => true,
1044 'contenteditable' => true,
1045 'disabled' => true,
1046 'draggable' => true,
1047 'dropzone' => true,
1048 'form' => true,
1049 'hidden' => true,
1050 'lang' => true,
1051 'multiple' => true,
1052 'name' => true,
1053 'onblur' => true,
1054 'onchange' => true,
1055 'oncontextmenu' => true,
1056 'onfocus' => true,
1057 'oninput' => true,
1058 'oninvalid' => true,
1059 'onreset' => true,
1060 'onsearch' => true,
1061 'onselect' => true,
1062 'onsubmit' => true,
1063 'required' => true,
1064 'size' => true,
1065 'spellcheck' => true,
1066 'tabindex' => true,
1067 'translate' => true,
1068 ],
1069 'option' => [
1070 'class' => true,
1071 'disabled' => true,
1072 'id' => true,
1073 'label' => true,
1074 'selected' => true,
1075 'value' => true,
1076 ],
1077 ];
1078
1079 // Add the global allowed attributes to each html element.
1080 $input_tags = array_map( '_wp_add_global_attributes', $input_tags );
1081 }
1082
1083 return array_merge_recursive( $allowed_post_tags, $input_tags );
1084 }
1085
1086 /**
1087 * Gets an array of enabled features.
1088 *
1089 * @return string[] The array of enabled features.
1090 */
1091 public static function retrieve_enabled_features() {
1092 /**
1093 * The feature flag integration.
1094 *
1095 * @var Feature_Flag_Integration $feature_flag_integration;
1096 */
1097 $feature_flag_integration = YoastSEO()->classes->get( Feature_Flag_Integration::class );
1098 return $feature_flag_integration->get_enabled_features();
1099 }
1100 }
1101