]*>.*?#is', '##s', '#]*)?/?>#i', ); $clean = preg_replace( $patterns, '', $text ); return null === $clean ? '' : $clean; } /** * Trim the way the API does. * * @param string $text Text. */ public static function trim( string $text ): string { $trimmed = preg_replace( '/^' . self::TRIM_CLASS . '+|' . self::TRIM_CLASS . '+$/u', '', $text ); return null === $trimmed ? trim( $text ) : $trimmed; } /** * Length in UTF-16 code units, matching JavaScript's string length. * * @param string $text Text. */ public static function length( string $text ): int { $chars = preg_match_all( '/./us', $text ); if ( false === $chars ) { return strlen( $text ); } $astral = preg_match_all( '/[\x{10000}-\x{10FFFF}]/u', $text ); return (int) $chars + (int) $astral; } /** * Billing units for one recipient. * * @param string $text Message. */ public static function units( string $text ): int { return max( 1, (int) ceil( self::length( self::trim( $text ) ) / self::UNIT_LENGTH ) ); } /** * Fill {placeholders}. strtr() makes a single pass, so a value that itself * contains "{something}" is never expanded again. * * @param string $template Template text. * @param array $vars Placeholder values, keyed without braces. */ public static function render( string $template, array $vars ): string { $pairs = array(); foreach ( $vars as $key => $value ) { $pairs[ '{' . $key . '}' ] = self::plain( $value ); } return self::trim( strtr( $template, $pairs ) ); } /** * Turn any value (including formatted HTML such as wc_price()) into a * single line of plain text. * * @param mixed $value Value. */ public static function plain( $value ): string { if ( is_array( $value ) || is_object( $value ) || null === $value ) { return ''; } if ( is_bool( $value ) ) { return $value ? '1' : '0'; } $text = html_entity_decode( wp_strip_all_tags( (string) $value ), ENT_QUOTES | ENT_HTML5, 'UTF-8' ); $text = str_replace( "\xC2\xA0", ' ', $text ); $text = preg_replace( '/\s+/u', ' ', $text ); return trim( (string) $text ); } /** * A customer typed name, made safe to put in an SMS sent from the shop's * own sender. * * Anyone can place an order with someone else's phone number, so a name * like "Your account is locked, log in at evil.example" would otherwise * reach that person as a message from the shop. Only letters (any script), * spaces, apostrophes and hyphens survive, which removes links, digits and * punctuation, and the result is capped in length. * * @param mixed $value Raw name. */ public static function safe_name( $value ): string { $name = self::plain( $value ); $name = preg_replace( "/[^\\p{L}\\p{M}\\s'\\x{2019}\\x{05F3}\\-]/u", '', $name ); $name = trim( (string) preg_replace( '/\s+/u', ' ', (string) $name ) ); if ( self::length( $name ) > self::MAX_NAME_LENGTH ) { $name = trim( (string) preg_replace( '/^(.{' . self::MAX_NAME_LENGTH . '}).*$/us', '$1', $name ) ); } return $name; } /** * Shorten text for a table cell. * * @param string $text Text. * @param int $limit Characters to keep. */ public static function excerpt( string $text, int $limit = 80 ): string { $text = self::plain( $text ); if ( preg_match_all( '/./us', $text ) <= $limit ) { return $text; } return trim( (string) preg_replace( '/^(.{' . $limit . '}).*$/us', '$1', $text ) ) . '...'; } }