From fede6b64132e18344e0046d4a540428b0baa9684 Mon Sep 17 00:00:00 2001 From: Andy Fragen Date: Fri, 18 Jul 2025 19:24:54 -0700 Subject: [PATCH] Split compat.php into php-polyfill.php and wp-polyfill.php (#170) Signed-off-by: Andy Fragen Co-authored-by: Colin Stewart <79332690+costdev@users.noreply.github.com> --- inc/compatibility/compat.php | 567 ----------------------------- inc/compatibility/php-polyfill.php | 212 +++++++++++ inc/compatibility/wp-polyfill.php | 340 +++++++++++++++++ phpcs.xml.dist | 4 +- plugin.php | 3 +- 5 files changed, 556 insertions(+), 570 deletions(-) delete mode 100644 inc/compatibility/compat.php create mode 100644 inc/compatibility/php-polyfill.php create mode 100644 inc/compatibility/wp-polyfill.php diff --git a/inc/compatibility/compat.php b/inc/compatibility/compat.php deleted file mode 100644 index a9c99b1..0000000 --- a/inc/compatibility/compat.php +++ /dev/null @@ -1,567 +0,0 @@ - 'utf-8' ] ); - * - * `is_utf8_charset` should be used outside of this file. - * - * @ignore - * @since 6.6.1 - * - * @param string $charset_slug Slug representing a text character encoding, or "charset". - * E.g. "UTF-8", "Windows-1252", "ISO-8859-1", "SJIS". - * - * @return bool Whether the slug represents the UTF-8 encoding. - */ - function _is_utf8_charset( $charset_slug ) { - if ( ! is_string( $charset_slug ) ) { - return false; - } - - return ( - 0 === strcasecmp( 'UTF-8', $charset_slug ) || - 0 === strcasecmp( 'UTF8', $charset_slug ) - ); - } -} - -if ( ! function_exists( 'mb_substr' ) ) { - /** - * Compat function to mimic mb_substr(). - * - * @ignore - * @since 3.2.0 - * - * @see _mb_substr() - * - * @param string $string The string to extract the substring from. - * @param int $start Position to being extraction from in `$string`. - * @param int|null $length Optional. Maximum number of characters to extract from `$string`. - * Default null. - * @param string|null $encoding Optional. Character encoding to use. Default null. - * @return string Extracted substring. - */ - function mb_substr( $string, $start, $length = null, $encoding = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound - return _mb_substr( $string, $start, $length, $encoding ); - } -} - -// If _mb_substr already loaded. -if ( ! function_exists( '_mb_substr' ) ) { - /** - * Internal compat function to mimic mb_substr(). - * - * Only understands UTF-8 and 8bit. All other character sets will be treated as 8bit. - * For `$encoding === UTF-8`, the `$str` input is expected to be a valid UTF-8 byte - * sequence. The behavior of this function for invalid inputs is undefined. - * - * @ignore - * @since 3.2.0 - * - * @param string $str The string to extract the substring from. - * @param int $start Position to being extraction from in `$str`. - * @param int|null $length Optional. Maximum number of characters to extract from `$str`. - * Default null. - * @param string|null $encoding Optional. Character encoding to use. Default null. - * @return string Extracted substring. - */ - function _mb_substr( $str, $start, $length = null, $encoding = null ) { - if ( null === $str ) { - return ''; - } - - if ( null === $encoding ) { - $encoding = get_option( 'blog_charset' ); - } - - /* - * The solution below works only for UTF-8, so in case of a different - * charset just use built-in substr(). - */ - if ( ! _is_utf8_charset( $encoding ) ) { - return is_null( $length ) ? substr( $str, $start ) : substr( $str, $start, $length ); - } - - if ( _wp_can_use_pcre_u() ) { - // Use the regex unicode support to separate the UTF-8 characters into an array. - preg_match_all( '/./us', $str, $match ); - $chars = is_null( $length ) ? array_slice( $match[0], $start ) : array_slice( $match[0], $start, $length ); - return implode( '', $chars ); - } - - $regex = '/( - [\x00-\x7F] # single-byte sequences 0xxxxxxx - | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx - | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 - | [\xE1-\xEC][\x80-\xBF]{2} - | \xED[\x80-\x9F][\x80-\xBF] - | [\xEE-\xEF][\x80-\xBF]{2} - | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 - | [\xF1-\xF3][\x80-\xBF]{3} - | \xF4[\x80-\x8F][\x80-\xBF]{2} - )/x'; - - // Start with 1 element instead of 0 since the first thing we do is pop. - $chars = [ '' ]; - - do { - // We had some string left over from the last round, but we counted it in that last round. - array_pop( $chars ); - - /* - * Split by UTF-8 character, limit to 1000 characters (last array element will contain - * the rest of the string). - */ - $pieces = preg_split( $regex, $str, 1000, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); - - $chars = array_merge( $chars, $pieces ); - - // If there's anything left over, repeat the loop. - } while ( count( $pieces ) > 1 && $str = array_pop( $pieces ) ); // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition - - return implode( '', array_slice( $chars, $start, $length ) ); - } -} - -if ( ! function_exists( 'mb_strlen' ) ) { - /** - * Compat function to mimic mb_strlen(). - * - * @ignore - * @since 4.2.0 - * - * @see _mb_strlen() - * - * @param string $string The string to retrieve the character length from. - * @param string|null $encoding Optional. Character encoding to use. Default null. - * @return int String length of `$string`. - */ - function mb_strlen( $string, $encoding = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound - return _mb_strlen( $string, $encoding ); - } -} - -// If _mb_strlen is already loaded. -if ( ! function_exists( '_mb_strlen' ) ) { - /** - * Internal compat function to mimic mb_strlen(). - * - * Only understands UTF-8 and 8bit. All other character sets will be treated as 8bit. - * For `$encoding === UTF-8`, the `$str` input is expected to be a valid UTF-8 byte - * sequence. The behavior of this function for invalid inputs is undefined. - * - * @ignore - * @since 4.2.0 - * - * @param string $str The string to retrieve the character length from. - * @param string|null $encoding Optional. Character encoding to use. Default null. - * @return int String length of `$str`. - */ - function _mb_strlen( $str, $encoding = null ) { - if ( null === $encoding ) { - $encoding = get_option( 'blog_charset' ); - } - - /* - * The solution below works only for UTF-8, so in case of a different charset - * just use built-in strlen(). - */ - if ( ! _is_utf8_charset( $encoding ) ) { - return strlen( $str ); - } - - if ( _wp_can_use_pcre_u() ) { - // Use the regex unicode support to separate the UTF-8 characters into an array. - preg_match_all( '/./us', $str, $match ); - return count( $match[0] ); - } - - $regex = '/(?: - [\x00-\x7F] # single-byte sequences 0xxxxxxx - | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx - | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 - | [\xE1-\xEC][\x80-\xBF]{2} - | \xED[\x80-\x9F][\x80-\xBF] - | [\xEE-\xEF][\x80-\xBF]{2} - | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 - | [\xF1-\xF3][\x80-\xBF]{3} - | \xF4[\x80-\x8F][\x80-\xBF]{2} - )/x'; - - // Start at 1 instead of 0 since the first thing we do is decrement. - $count = 1; - - do { - // We had some string left over from the last round, but we counted it in that last round. - --$count; - - /* - * Split by UTF-8 character, limit to 1000 characters (last array element will contain - * the rest of the string). - */ - $pieces = preg_split( $regex, $str, 1000 ); - - // Increment. - $count += count( $pieces ); - - // If there's anything left over, repeat the loop. - } while ( $str = array_pop( $pieces ) ); // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition - - // Fencepost: preg_split() always returns one extra item in the array. - return --$count; - } -} - -// sodium_crypto_box() was introduced in PHP 7.2. -if ( ! function_exists( 'sodium_crypto_box' ) ) { - require ABSPATH . WPINC . '/sodium_compat/autoload.php'; -} - -if ( ! function_exists( 'is_countable' ) ) { - /** - * Polyfill for is_countable() function added in PHP 7.3. - * - * Verify that the content of a variable is an array or an object - * implementing the Countable interface. - * - * @since 4.9.6 - * - * @param mixed $value The value to check. - * @return bool True if `$value` is countable, false otherwise. - */ - function is_countable( $value ) { - return ( is_array( $value ) - || $value instanceof Countable - || $value instanceof SimpleXMLElement - || $value instanceof ResourceBundle - ); - } -} - -if ( ! function_exists( 'array_key_first' ) ) { - /** - * Polyfill for array_key_first() function added in PHP 7.3. - * - * Get the first key of the given array without affecting - * the internal array pointer. - * - * @since 5.9.0 - * - * @param array $array An array. - * @return string|int|null The first key of array if the array - * is not empty; `null` otherwise. - */ - function array_key_first( array $array ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound - if ( empty( $array ) ) { - return null; - } - - foreach ( $array as $key => $value ) { - return $key; - } - } -} - -if ( ! function_exists( 'array_key_last' ) ) { - /** - * Polyfill for `array_key_last()` function added in PHP 7.3. - * - * Get the last key of the given array without affecting the - * internal array pointer. - * - * @since 5.9.0 - * - * @param array $array An array. - * @return string|int|null The last key of array if the array - * is not empty; `null` otherwise. - */ - function array_key_last( array $array ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound - if ( empty( $array ) ) { - return null; - } - - end( $array ); - - return key( $array ); - } -} - -if ( ! function_exists( 'array_is_list' ) ) { - /** - * Polyfill for `array_is_list()` function added in PHP 8.1. - * - * Determines if the given array is a list. - * - * An array is considered a list if its keys consist of consecutive numbers from 0 to count($array)-1. - * - * @see https://github.com/symfony/polyfill-php81/tree/main - * - * @since 6.5.0 - * - * @param array $arr The array being evaluated. - * @return bool True if array is a list, false otherwise. - */ - function array_is_list( $arr ) { - if ( ( [] === $arr ) || ( array_values( $arr ) === $arr ) ) { - return true; - } - - $next_key = -1; - - foreach ( $arr as $k => $v ) { - if ( ++$next_key !== $k ) { - return false; - } - } - - return true; - } -} - -if ( ! function_exists( 'str_contains' ) ) { - /** - * Polyfill for `str_contains()` function added in PHP 8.0. - * - * Performs a case-sensitive check indicating if needle is - * contained in haystack. - * - * @since 5.9.0 - * - * @param string $haystack The string to search in. - * @param string $needle The substring to search for in the `$haystack`. - * @return bool True if `$needle` is in `$haystack`, otherwise false. - */ - function str_contains( $haystack, $needle ) { - if ( '' === $needle ) { - return true; - } - - return false !== strpos( $haystack, $needle ); - } -} - -if ( ! function_exists( 'str_starts_with' ) ) { - /** - * Polyfill for `str_starts_with()` function added in PHP 8.0. - * - * Performs a case-sensitive check indicating if - * the haystack begins with needle. - * - * @since 5.9.0 - * - * @param string $haystack The string to search in. - * @param string $needle The substring to search for in the `$haystack`. - * @return bool True if `$haystack` starts with `$needle`, otherwise false. - */ - function str_starts_with( $haystack, $needle ) { - if ( '' === $needle ) { - return true; - } - - return 0 === strpos( $haystack, $needle ); - } -} - -if ( ! function_exists( 'str_ends_with' ) ) { - /** - * Polyfill for `str_ends_with()` function added in PHP 8.0. - * - * Performs a case-sensitive check indicating if - * the haystack ends with needle. - * - * @since 5.9.0 - * - * @param string $haystack The string to search in. - * @param string $needle The substring to search for in the `$haystack`. - * @return bool True if `$haystack` ends with `$needle`, otherwise false. - */ - function str_ends_with( $haystack, $needle ) { - if ( '' === $haystack ) { - return '' === $needle; - } - - $len = strlen( $needle ); - - return substr( $haystack, -$len, $len ) === $needle; - } -} - -if ( ! function_exists( 'array_find' ) ) { - /** - * Polyfill for `array_find()` function added in PHP 8.4. - * - * Searches an array for the first element that passes a given callback. - * - * @since 6.8.0 - * - * @param array $array The array to search. - * @param callable $callback The callback to run for each element. - * @return mixed|null The first element in the array that passes the `$callback`, otherwise null. - */ - function array_find( array $array, callable $callback ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound - foreach ( $array as $key => $value ) { - if ( $callback( $value, $key ) ) { - return $value; - } - } - - return null; - } -} - -if ( ! function_exists( 'array_find_key' ) ) { - /** - * Polyfill for `array_find_key()` function added in PHP 8.4. - * - * Searches an array for the first key that passes a given callback. - * - * @since 6.8.0 - * - * @param array $array The array to search. - * @param callable $callback The callback to run for each element. - * @return int|string|null The first key in the array that passes the `$callback`, otherwise null. - */ - function array_find_key( array $array, callable $callback ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound - foreach ( $array as $key => $value ) { - if ( $callback( $value, $key ) ) { - return $key; - } - } - - return null; - } -} - -if ( ! function_exists( 'array_any' ) ) { - /** - * Polyfill for `array_any()` function added in PHP 8.4. - * - * Checks if any element of an array passes a given callback. - * - * @since 6.8.0 - * - * @param array $array The array to check. - * @param callable $callback The callback to run for each element. - * @return bool True if any element in the array passes the `$callback`, otherwise false. - */ - function array_any( array $array, callable $callback ): bool { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound - foreach ( $array as $key => $value ) { - if ( $callback( $value, $key ) ) { - return true; - } - } - - return false; - } -} - -if ( ! function_exists( 'array_all' ) ) { - /** - * Polyfill for `array_all()` function added in PHP 8.4. - * - * Checks if all elements of an array pass a given callback. - * - * @since 6.8.0 - * - * @param array $array The array to check. - * @param callable $callback The callback to run for each element. - * @return bool True if all elements in the array pass the `$callback`, otherwise false. - */ - function array_all( array $array, callable $callback ): bool { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound - foreach ( $array as $key => $value ) { - if ( ! $callback( $value, $key ) ) { - return false; - } - } - - return true; - } -} - -// IMAGETYPE_AVIF constant is only defined in PHP 8.x or later. -if ( ! defined( 'IMAGETYPE_AVIF' ) ) { - define( 'IMAGETYPE_AVIF', 19 ); -} - -// IMG_AVIF constant is only defined in PHP 8.x or later. -if ( ! defined( 'IMG_AVIF' ) ) { - define( 'IMG_AVIF', IMAGETYPE_AVIF ); -} - -// IMAGETYPE_HEIC constant is not yet defined in PHP as of PHP 8.3. -if ( ! defined( 'IMAGETYPE_HEIC' ) ) { - define( 'IMAGETYPE_HEIC', 99 ); -} diff --git a/inc/compatibility/php-polyfill.php b/inc/compatibility/php-polyfill.php new file mode 100644 index 0000000..5e6ed74 --- /dev/null +++ b/inc/compatibility/php-polyfill.php @@ -0,0 +1,212 @@ + $arr The array being evaluated. + * @return bool True if array is a list, false otherwise. + */ + function array_is_list( $arr ) { + if ( ( [] === $arr ) || ( array_values( $arr ) === $arr ) ) { + return true; + } + + $next_key = -1; + + foreach ( $arr as $k => $v ) { + if ( ++$next_key !== $k ) { + return false; + } + } + + return true; + } +} + +if ( ! function_exists( 'array_find' ) ) { + /** + * Polyfill for `array_find()` function added in PHP 8.4. + * + * Searches an array for the first element that passes a given callback. + * + * @since WordPress 6.8.0 + * + * @param array $array The array to search. + * @param callable $callback The callback to run for each element. + * @return mixed|null The first element in the array that passes the `$callback`, otherwise null. + */ + function array_find( array $array, callable $callback ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound + foreach ( $array as $key => $value ) { + if ( $callback( $value, $key ) ) { + return $value; + } + } + + return null; + } +} + +if ( ! function_exists( 'array_find_key' ) ) { + /** + * Polyfill for `array_find_key()` function added in PHP 8.4. + * + * Searches an array for the first key that passes a given callback. + * + * @since WordPress 6.8.0 + * + * @param array $array The array to search. + * @param callable $callback The callback to run for each element. + * @return int|string|null The first key in the array that passes the `$callback`, otherwise null. + */ + function array_find_key( array $array, callable $callback ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound + foreach ( $array as $key => $value ) { + if ( $callback( $value, $key ) ) { + return $key; + } + } + + return null; + } +} + +if ( ! function_exists( 'array_any' ) ) { + /** + * Polyfill for `array_any()` function added in PHP 8.4. + * + * Checks if any element of an array passes a given callback. + * + * @since WordPress 6.8.0 + * + * @param array $array The array to check. + * @param callable $callback The callback to run for each element. + * @return bool True if any element in the array passes the `$callback`, otherwise false. + */ + function array_any( array $array, callable $callback ): bool { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound + foreach ( $array as $key => $value ) { + if ( $callback( $value, $key ) ) { + return true; + } + } + + return false; + } +} + +if ( ! function_exists( 'array_all' ) ) { + /** + * Polyfill for `array_all()` function added in PHP 8.4. + * + * Checks if all elements of an array pass a given callback. + * + * @since WordPress 6.8.0 + * + * @param array $array The array to check. + * @param callable $callback The callback to run for each element. + * @return bool True if all elements in the array pass the `$callback`, otherwise false. + */ + function array_all( array $array, callable $callback ): bool { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound + foreach ( $array as $key => $value ) { + if ( ! $callback( $value, $key ) ) { + return false; + } + } + + return true; + } +} + +// IMAGETYPE_AVIF constant is only defined in PHP 8.x or later. +if ( ! defined( 'IMAGETYPE_AVIF' ) ) { + define( 'IMAGETYPE_AVIF', 19 ); +} + +// IMG_AVIF constant is only defined in PHP 8.x or later. +if ( ! defined( 'IMG_AVIF' ) ) { + define( 'IMG_AVIF', IMAGETYPE_AVIF ); +} + +// IMAGETYPE_HEIC constant is not yet defined in PHP as of PHP 8.3. +if ( ! defined( 'IMAGETYPE_HEIC' ) ) { + define( 'IMAGETYPE_HEIC', 99 ); +} diff --git a/inc/compatibility/wp-polyfill.php b/inc/compatibility/wp-polyfill.php new file mode 100644 index 0000000..c9685cb --- /dev/null +++ b/inc/compatibility/wp-polyfill.php @@ -0,0 +1,340 @@ +'; + $regex = <<(.*?)) # the "anything" matched by the lookahead + (?({$cdata_regex})) # the CDATA Section matched by the lookahead + +| # alternative + + (?(.*)) # non-CDATA Section +/sx +EOF; + + $safe_text = (string) preg_replace_callback( + $regex, + static function ( $matches ) { + if ( ! isset( $matches[0] ) ) { + return ''; + } + + if ( isset( $matches['non_cdata'] ) ) { + // escape HTML entities in the non-CDATA Section. + return _wp_specialchars( $matches['non_cdata'], ENT_XML1 ); + } + + // Return the CDATA Section unchanged, escape HTML entities in the rest. + return _wp_specialchars( $matches['non_cdata_followed_by_cdata'], ENT_XML1 ) . $matches['cdata']; + }, + $safe_text + ); + + /** + * Filters a string cleaned and escaped for output in XML. + * + * Text passed to esc_xml() is stripped of invalid or special characters + * before output. HTML named character references are converted to their + * equivalent code points. + * + * @since WordPress 5.5.0 + * + * @param string $safe_text The text after it has been escaped. + * @param string $text The text prior to being escaped. + */ + return apply_filters( 'esc_xml', $safe_text, $text ); + } +} + +if ( ! function_exists( 'is_post_status_viewable' ) ) { + /** + * Determines whether a post status is considered "viewable". + * + * For built-in post statuses such as publish and private, the 'public' value will be evaluated. + * For all others, the 'publicly_queryable' value will be used. + * + * @since WordPress 5.7.0 + * @since WordPress 5.9.0 Added `is_post_status_viewable` hook to filter the result. + * + * @param string|stdClass $post_status Post status name or object. + * @return bool Whether the post status should be considered viewable. + */ + function is_post_status_viewable( $post_status ) { + if ( is_scalar( $post_status ) ) { + $post_status = get_post_status_object( $post_status ); + + if ( ! $post_status ) { + return false; + } + } + + if ( + ! is_object( $post_status ) + || $post_status->internal + || $post_status->protected + ) { + return false; + } + + $is_viewable = $post_status->publicly_queryable || ( $post_status->_builtin && $post_status->public ); + + /** + * Filters whether a post status is considered "viewable". + * + * The returned filtered value must be a boolean type to ensure + * `is_post_status_viewable()` only returns a boolean. This strictness + * is by design to maintain backwards-compatibility and guard against + * potential type errors in PHP 8.1+. Non-boolean values (even falsey + * and truthy values) will result in the function returning false. + * + * @since WordPress 5.9.0 + * + * @param bool $is_viewable Whether the post status is "viewable" (strict type). + * @param stdClass $post_status Post status object. + */ + return true === apply_filters( 'is_post_status_viewable', $is_viewable, $post_status ); + } +} + +// If _is_utf8_charset is already loaded. +if ( ! function_exists( '_is_utf8_charset' ) ) { + /** + * Indicates if a given slug for a character set represents the UTF-8 text encoding. + * + * A charset is considered to represent UTF-8 if it is a case-insensitive match + * of "UTF-8" with or without the hyphen. + * + * Example: + * + * true === _is_utf8_charset( 'UTF-8' ); + * true === _is_utf8_charset( 'utf8' ); + * false === _is_utf8_charset( 'latin1' ); + * false === _is_utf8_charset( 'UTF 8' ); + * + * // Only strings match. + * false === _is_utf8_charset( [ 'charset' => 'utf-8' ] ); + * + * `is_utf8_charset` should be used outside of this file. + * + * @ignore + * @since WordPress 6.6.1 + * + * @param string $charset_slug Slug representing a text character encoding, or "charset". + * E.g. "UTF-8", "Windows-1252", "ISO-8859-1", "SJIS". + * + * @return bool Whether the slug represents the UTF-8 encoding. + */ + function _is_utf8_charset( $charset_slug ) { + if ( ! is_string( $charset_slug ) ) { + return false; + } + + return ( + 0 === strcasecmp( 'UTF-8', $charset_slug ) || + 0 === strcasecmp( 'UTF8', $charset_slug ) + ); + } +} + +if ( ! function_exists( 'wp_get_admin_notice' ) ) { + /** + * Creates and returns the markup for an admin notice. + * + * @since WordPress 6.4.0 + * + * @param string $message The message. + * @param array $args { + * Optional. An array of arguments for the admin notice. Default empty array. + * + * @type string $type Optional. The type of admin notice. + * For example, 'error', 'success', 'warning', 'info'. + * Default empty string. + * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. + * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. + * @type string[] $additional_classes Optional. A string array of class names. Default empty array. + * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. + * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. + * } + * @return string The markup for an admin notice. + */ + function wp_get_admin_notice( $message, $args = [] ) { + $defaults = [ + 'type' => '', + 'dismissible' => false, + 'id' => '', + 'additional_classes' => [], + 'attributes' => [], + 'paragraph_wrap' => true, + ]; + + $args = wp_parse_args( $args, $defaults ); + + /** + * Filters the arguments for an admin notice. + * + * @since WordPress 6.4.0 + * + * @param array $args The arguments for the admin notice. + * @param string $message The message for the admin notice. + */ + $args = apply_filters( 'wp_admin_notice_args', $args, $message ); + $id = ''; + $classes = 'notice'; + $attributes = ''; + + if ( is_string( $args['id'] ) ) { + $trimmed_id = trim( $args['id'] ); + + if ( '' !== $trimmed_id ) { + $id = 'id="' . $trimmed_id . '" '; + } + } + + if ( is_string( $args['type'] ) ) { + $type = trim( $args['type'] ); + + if ( str_contains( $type, ' ' ) ) { + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: %s: The "type" key. */ + __( 'The %s key must be a string without spaces.' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- This intentionally uses WordPress Core's translation string. + 'type' + ), + '6.4.0' + ); + } + + if ( '' !== $type ) { + $classes .= ' notice-' . $type; + } + } + + if ( true === $args['dismissible'] ) { + $classes .= ' is-dismissible'; + } + + if ( is_array( $args['additional_classes'] ) && ! empty( $args['additional_classes'] ) ) { + $classes .= ' ' . implode( ' ', $args['additional_classes'] ); + } + + if ( is_array( $args['attributes'] ) && ! empty( $args['attributes'] ) ) { + $attributes = ''; + foreach ( $args['attributes'] as $attr => $val ) { + if ( is_bool( $val ) ) { + $attributes .= $val ? ' ' . $attr : ''; + } elseif ( is_int( $attr ) ) { + $attributes .= ' ' . esc_attr( trim( $val ) ); + } elseif ( $val ) { + $attributes .= ' ' . $attr . '="' . esc_attr( trim( $val ) ) . '"'; + } + } + } + + if ( false !== $args['paragraph_wrap'] ) { + $message = "

$message

"; + } + + $markup = sprintf( '
%4$s
', $id, $classes, $attributes, $message ); + + /** + * Filters the markup for an admin notice. + * + * @since WordPress 6.4.0 + * + * @param string $markup The HTML markup for the admin notice. + * @param string $message The message for the admin notice. + * @param array $args The arguments for the admin notice. + */ + return apply_filters( 'wp_admin_notice_markup', $markup, $message, $args ); + } +} + +if ( ! function_exists( 'wp_admin_notice' ) ) { + /** + * Outputs an admin notice. + * + * @since WordPress 6.4.0 + * + * @param string $message The message to output. + * @param array $args { + * Optional. An array of arguments for the admin notice. Default empty array. + * + * @type string $type Optional. The type of admin notice. + * For example, 'error', 'success', 'warning', 'info'. + * Default empty string. + * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. + * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. + * @type string[] $additional_classes Optional. A string array of class names. Default empty array. + * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. + * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. + * } + */ + function wp_admin_notice( $message, $args = [] ) { + /** + * Fires before an admin notice is output. + * + * @since WordPress 6.4.0 + * + * @param string $message The message for the admin notice. + * @param array $args The arguments for the admin notice. + */ + do_action( 'wp_admin_notice', $message, $args ); + + echo wp_kses_post( wp_get_admin_notice( $message, $args ) ); + } +} +if ( ! function_exists( 'wp_get_wp_version' ) ) { + /** + * Returns the current WordPress version. + * + * Returns an unmodified value of `$wp_version`. Some plugins modify the global + * in an attempt to improve security through obscurity. This practice can cause + * errors in WordPress, so the ability to get an unmodified version is needed. + * + * @since WordPress 6.7.0 + * + * @return string The current WordPress version. + */ + function wp_get_wp_version() { + static $wp_version; + + if ( ! isset( $wp_version ) ) { + require ABSPATH . WPINC . '/version.php'; + } + + return $wp_version; + } +} + +if ( ! function_exists( 'get_user' ) ) { + /** + * Retrieves user info by user ID. + * + * @since WordPress 6.7.0 + * + * @param int $user_id User ID. + * + * @return WP_User|false WP_User object on success, false on failure. + */ + function get_user( $user_id ) { + return get_user_by( 'id', $user_id ); + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 8b6bfb5..891a72f 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -55,8 +55,9 @@ - inc/compatibility/compat\.php inc/icons/svg\.php + inc/compatibility/php-polyfill\.php + inc/compatibility/wp-polyfill\.php @@ -70,7 +71,6 @@ plugin\.php - inc/compatibility/compat\.php inc/icons/svg\.php tests/phpunit/bootstrap\.php diff --git a/plugin.php b/plugin.php index 4ddd519..3305724 100644 --- a/plugin.php +++ b/plugin.php @@ -38,7 +38,8 @@ require_once __DIR__ . '/inc/user-notification/namespace.php'; require_once __DIR__ . '/inc/version-check/namespace.php'; // External dependencies. -require_once __DIR__ . '/inc/compatibility/compat.php'; +require_once __DIR__ . '/inc/compatibility/php-polyfill.php'; +require_once __DIR__ . '/inc/compatibility/wp-polyfill.php'; require_once __DIR__ . '/inc/updater/class-lite.php'; /**