2021-08-27 06:46:27 -04:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\String\Slugger ;
2023-02-24 06:26:40 -05:00
use Symfony\Component\Intl\Transliterator\EmojiTransliterator ;
2021-08-27 06:46:27 -04:00
use Symfony\Component\String\AbstractUnicodeString ;
use Symfony\Component\String\UnicodeString ;
use Symfony\Contracts\Translation\LocaleAwareInterface ;
if ( ! interface_exists ( LocaleAwareInterface :: class )) {
throw new \LogicException ( 'You cannot use the "Symfony\Component\String\Slugger\AsciiSlugger" as the "symfony/translation-contracts" package is not installed. Try running "composer require symfony/translation-contracts".' );
}
/**
* @ author Titouan Galopin < galopintitouan @ gmail . com >
*/
class AsciiSlugger implements SluggerInterface , LocaleAwareInterface
{
private const LOCALE_TO_TRANSLITERATOR_ID = [
'am' => 'Amharic-Latin' ,
'ar' => 'Arabic-Latin' ,
'az' => 'Azerbaijani-Latin' ,
'be' => 'Belarusian-Latin' ,
'bg' => 'Bulgarian-Latin' ,
'bn' => 'Bengali-Latin' ,
'de' => 'de-ASCII' ,
'el' => 'Greek-Latin' ,
'fa' => 'Persian-Latin' ,
'he' => 'Hebrew-Latin' ,
'hy' => 'Armenian-Latin' ,
'ka' => 'Georgian-Latin' ,
'kk' => 'Kazakh-Latin' ,
'ky' => 'Kirghiz-Latin' ,
'ko' => 'Korean-Latin' ,
'mk' => 'Macedonian-Latin' ,
'mn' => 'Mongolian-Latin' ,
'or' => 'Oriya-Latin' ,
'ps' => 'Pashto-Latin' ,
'ru' => 'Russian-Latin' ,
'sr' => 'Serbian-Latin' ,
'sr_Cyrl' => 'Serbian-Latin' ,
'th' => 'Thai-Latin' ,
'tk' => 'Turkmen-Latin' ,
'uk' => 'Ukrainian-Latin' ,
'uz' => 'Uzbek-Latin' ,
'zh' => 'Han-Latin' ,
];
2022-03-14 16:22:30 -04:00
private ? string $defaultLocale ;
private \Closure | array $symbolsMap = [
2021-08-27 06:46:27 -04:00
'en' => [ '@' => 'at' , '&' => 'and' ],
];
2023-02-24 06:26:40 -05:00
private bool | string $emoji = false ;
2021-08-27 06:46:27 -04:00
/**
* Cache of transliterators per locale .
*
* @ var \Transliterator []
*/
2022-03-14 16:22:30 -04:00
private array $transliterators = [];
2021-08-27 06:46:27 -04:00
2022-03-14 16:22:30 -04:00
public function __construct ( string $defaultLocale = null , array | \Closure $symbolsMap = null )
2021-08-27 06:46:27 -04:00
{
$this -> defaultLocale = $defaultLocale ;
$this -> symbolsMap = $symbolsMap ? ? $this -> symbolsMap ;
}
2022-03-14 16:22:30 -04:00
public function setLocale ( string $locale )
2021-08-27 06:46:27 -04:00
{
$this -> defaultLocale = $locale ;
}
2022-03-14 16:22:30 -04:00
public function getLocale () : string
2021-08-27 06:46:27 -04:00
{
return $this -> defaultLocale ;
}
/**
2023-02-24 06:26:40 -05:00
* @ param bool | string $emoji true will use the same locale ,
* false will disable emoji ,
* and a string to use a specific locale
2021-08-27 06:46:27 -04:00
*/
2023-02-24 06:26:40 -05:00
public function withEmoji ( bool | string $emoji = true ) : static
{
if ( false !== $emoji && ! class_exists ( EmojiTransliterator :: class )) {
throw new \LogicException ( sprintf ( 'You cannot use the "%s()" method as the "symfony/intl" package is not installed. Try running "composer require symfony/intl".' , __METHOD__ ));
}
$new = clone $this ;
$new -> emoji = $emoji ;
return $new ;
}
2021-08-27 06:46:27 -04:00
public function slug ( string $string , string $separator = '-' , string $locale = null ) : AbstractUnicodeString
{
2023-02-24 06:26:40 -05:00
$locale ? ? = $this -> defaultLocale ;
2021-08-27 06:46:27 -04:00
$transliterator = [];
2023-02-24 06:26:40 -05:00
if ( $locale && ( 'de' === $locale || str_starts_with ( $locale , 'de_' ))) {
2021-08-27 06:46:27 -04:00
// Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl)
$transliterator = [ 'de-ASCII' ];
} elseif ( \function_exists ( 'transliterator_transliterate' ) && $locale ) {
$transliterator = ( array ) $this -> createTransliterator ( $locale );
}
2023-02-24 06:26:40 -05:00
if ( $emojiTransliterator = $this -> createEmojiTransliterator ( $locale )) {
$transliterator [] = $emojiTransliterator ;
}
2021-08-27 06:46:27 -04:00
if ( $this -> symbolsMap instanceof \Closure ) {
// If the symbols map is passed as a closure, there is no need to fallback to the parent locale
// as the closure can just provide substitutions for all locales of interest.
$symbolsMap = $this -> symbolsMap ;
array_unshift ( $transliterator , static function ( $s ) use ( $symbolsMap , $locale ) {
return $symbolsMap ( $s , $locale );
});
}
$unicodeString = ( new UnicodeString ( $string )) -> ascii ( $transliterator );
if ( \is_array ( $this -> symbolsMap )) {
$map = null ;
if ( isset ( $this -> symbolsMap [ $locale ])) {
$map = $this -> symbolsMap [ $locale ];
} else {
$parent = self :: getParentLocale ( $locale );
if ( $parent && isset ( $this -> symbolsMap [ $parent ])) {
$map = $this -> symbolsMap [ $parent ];
}
}
if ( $map ) {
foreach ( $map as $char => $replace ) {
$unicodeString = $unicodeString -> replace ( $char , ' ' . $replace . ' ' );
}
}
}
return $unicodeString
-> replaceMatches ( '/[^A-Za-z0-9]++/' , $separator )
-> trim ( $separator )
;
}
private function createTransliterator ( string $locale ) : ? \Transliterator
{
if ( \array_key_exists ( $locale , $this -> transliterators )) {
return $this -> transliterators [ $locale ];
}
// Exact locale supported, cache and return
if ( $id = self :: LOCALE_TO_TRANSLITERATOR_ID [ $locale ] ? ? null ) {
return $this -> transliterators [ $locale ] = \Transliterator :: create ( $id . '/BGN' ) ? ? \Transliterator :: create ( $id );
}
// Locale not supported and no parent, fallback to any-latin
if ( ! $parent = self :: getParentLocale ( $locale )) {
return $this -> transliterators [ $locale ] = null ;
}
// Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales
if ( $id = self :: LOCALE_TO_TRANSLITERATOR_ID [ $parent ] ? ? null ) {
$transliterator = \Transliterator :: create ( $id . '/BGN' ) ? ? \Transliterator :: create ( $id );
}
return $this -> transliterators [ $locale ] = $this -> transliterators [ $parent ] = $transliterator ? ? null ;
}
2023-02-24 06:26:40 -05:00
private function createEmojiTransliterator ( ? string $locale ) : ? EmojiTransliterator
{
if ( \is_string ( $this -> emoji )) {
$locale = $this -> emoji ;
} elseif ( ! $this -> emoji ) {
return null ;
}
while ( null !== $locale ) {
try {
return EmojiTransliterator :: create ( " emoji- $locale " );
} catch ( \IntlException ) {
$locale = self :: getParentLocale ( $locale );
}
}
return null ;
}
2021-08-27 06:46:27 -04:00
private static function getParentLocale ( ? string $locale ) : ? string
{
if ( ! $locale ) {
return null ;
}
if ( false === $str = strrchr ( $locale , '_' )) {
// no parent locale
return null ;
}
return substr ( $locale , 0 , - \strlen ( $str ));
}
}