?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà"; const GSM7_EXTENDED = '^{}\\[~]|€'; const GSM7_SINGLE = 160; const GSM7_MULTI = 153; const UCS2_SINGLE = 70; const UCS2_MULTI = 67; /** * Length, parts and encoding of a message. * * @param string $text Message (UTF-8). * @return array{length:int,segments:int,per_segment:int,encoding:string} */ public static function info( string $text ): array { $chars = self::chars( $text ); $units = self::gsm7_units( $chars ); $gsm = null !== $units; $single = $gsm ? self::GSM7_SINGLE : self::UCS2_SINGLE; $multi = $gsm ? self::GSM7_MULTI : self::UCS2_MULTI; $length = $gsm ? array_sum( $units ) : self::utf16_length( $chars ); if ( 0 === $length ) { $segments = 0; } elseif ( $length <= $single ) { $segments = 1; } else { $segments = $gsm ? self::gsm7_parts( $units, $multi ) : (int) ceil( $length / $multi ); } return array( 'length' => $length, 'segments' => $segments, 'per_segment' => $length > $single ? $multi : $single, 'encoding' => $gsm ? 'GSM-7' : 'UCS-2', ); } /** * Split into characters (code points); invalid UTF-8 is dropped. * * @param string $text Text. * @return string[] */ private static function chars( string $text ): array { $chars = preg_split( '//u', wp_check_invalid_utf8( $text, true ), -1, PREG_SPLIT_NO_EMPTY ); return is_array( $chars ) ? $chars : array(); } /** * Places each character takes in GSM-7, or null when one is not in the alphabet. * * @param string[] $chars Characters. * @return int[]|null */ private static function gsm7_units( array $chars ): ?array { $units = array(); foreach ( $chars as $char ) { if ( false !== strpos( self::GSM7_EXTENDED, $char ) ) { $units[] = 2; } elseif ( false !== strpos( self::GSM7_CHARS, $char ) ) { $units[] = 1; } else { return null; } } return $units; } /** * UTF-16 code units: characters outside the BMP (4 bytes in UTF-8, e.g. emoji) count two. * * @param string[] $chars Characters. * @return int */ private static function utf16_length( array $chars ): int { $length = 0; foreach ( $chars as $char ) { $length += 4 === strlen( $char ) ? 2 : 1; } return $length; } /** * Parts of a split GSM-7 message; an escaped character that does not fit opens the next part. * * @param int[] $units Places per character. * @param int $per_part Places per part. * @return int */ private static function gsm7_parts( array $units, int $per_part ): int { $parts = 1; $used = 0; foreach ( $units as $size ) { if ( $used + $size > $per_part ) { ++$parts; $used = $size; } else { $used += $size; } } return $parts; } }