Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\String\Slugger;
13
 
14
use Symfony\Component\Intl\Transliterator\EmojiTransliterator;
15
use Symfony\Component\String\AbstractUnicodeString;
16
use Symfony\Component\String\UnicodeString;
17
use Symfony\Contracts\Translation\LocaleAwareInterface;
18
 
19
if (!interface_exists(LocaleAwareInterface::class)) {
20
    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".');
21
}
22
 
23
/**
24
 * @author Titouan Galopin <galopintitouan@gmail.com>
25
 */
26
class AsciiSlugger implements SluggerInterface, LocaleAwareInterface
27
{
28
    private const LOCALE_TO_TRANSLITERATOR_ID = [
29
        'am' => 'Amharic-Latin',
30
        'ar' => 'Arabic-Latin',
31
        'az' => 'Azerbaijani-Latin',
32
        'be' => 'Belarusian-Latin',
33
        'bg' => 'Bulgarian-Latin',
34
        'bn' => 'Bengali-Latin',
35
        'de' => 'de-ASCII',
36
        'el' => 'Greek-Latin',
37
        'fa' => 'Persian-Latin',
38
        'he' => 'Hebrew-Latin',
39
        'hy' => 'Armenian-Latin',
40
        'ka' => 'Georgian-Latin',
41
        'kk' => 'Kazakh-Latin',
42
        'ky' => 'Kirghiz-Latin',
43
        'ko' => 'Korean-Latin',
44
        'mk' => 'Macedonian-Latin',
45
        'mn' => 'Mongolian-Latin',
46
        'or' => 'Oriya-Latin',
47
        'ps' => 'Pashto-Latin',
48
        'ru' => 'Russian-Latin',
49
        'sr' => 'Serbian-Latin',
50
        'sr_Cyrl' => 'Serbian-Latin',
51
        'th' => 'Thai-Latin',
52
        'tk' => 'Turkmen-Latin',
53
        'uk' => 'Ukrainian-Latin',
54
        'uz' => 'Uzbek-Latin',
55
        'zh' => 'Han-Latin',
56
    ];
57
 
58
    private ?string $defaultLocale;
59
    private \Closure|array $symbolsMap = [
60
        'en' => ['@' => 'at', '&' => 'and'],
61
    ];
62
    private bool|string $emoji = false;
63
 
64
    /**
65
     * Cache of transliterators per locale.
66
     *
67
     * @var \Transliterator[]
68
     */
69
    private array $transliterators = [];
70
 
71
    public function __construct(string $defaultLocale = null, array|\Closure $symbolsMap = null)
72
    {
73
        $this->defaultLocale = $defaultLocale;
74
        $this->symbolsMap = $symbolsMap ?? $this->symbolsMap;
75
    }
76
 
77
    public function setLocale(string $locale)
78
    {
79
        $this->defaultLocale = $locale;
80
    }
81
 
82
    public function getLocale(): string
83
    {
84
        return $this->defaultLocale;
85
    }
86
 
87
    /**
88
     * @param bool|string $emoji true will use the same locale,
89
     *                           false will disable emoji,
90
     *                           and a string to use a specific locale
91
     */
92
    public function withEmoji(bool|string $emoji = true): static
93
    {
94
        if (false !== $emoji && !class_exists(EmojiTransliterator::class)) {
95
            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__));
96
        }
97
 
98
        $new = clone $this;
99
        $new->emoji = $emoji;
100
 
101
        return $new;
102
    }
103
 
104
    public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString
105
    {
106
        $locale ??= $this->defaultLocale;
107
 
108
        $transliterator = [];
109
        if ($locale && ('de' === $locale || str_starts_with($locale, 'de_'))) {
110
            // Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl)
111
            $transliterator = ['de-ASCII'];
112
        } elseif (\function_exists('transliterator_transliterate') && $locale) {
113
            $transliterator = (array) $this->createTransliterator($locale);
114
        }
115
 
116
        if ($emojiTransliterator = $this->createEmojiTransliterator($locale)) {
117
            $transliterator[] = $emojiTransliterator;
118
        }
119
 
120
        if ($this->symbolsMap instanceof \Closure) {
121
            // If the symbols map is passed as a closure, there is no need to fallback to the parent locale
122
            // as the closure can just provide substitutions for all locales of interest.
123
            $symbolsMap = $this->symbolsMap;
124
            array_unshift($transliterator, static function ($s) use ($symbolsMap, $locale) {
125
                return $symbolsMap($s, $locale);
126
            });
127
        }
128
 
129
        $unicodeString = (new UnicodeString($string))->ascii($transliterator);
130
 
131
        if (\is_array($this->symbolsMap)) {
132
            $map = null;
133
            if (isset($this->symbolsMap[$locale])) {
134
                $map = $this->symbolsMap[$locale];
135
            } else {
136
                $parent = self::getParentLocale($locale);
137
                if ($parent && isset($this->symbolsMap[$parent])) {
138
                    $map = $this->symbolsMap[$parent];
139
                }
140
            }
141
            if ($map) {
142
                foreach ($map as $char => $replace) {
143
                    $unicodeString = $unicodeString->replace($char, ' '.$replace.' ');
144
                }
145
            }
146
        }
147
 
148
        return $unicodeString
149
            ->replaceMatches('/[^A-Za-z0-9]++/', $separator)
150
            ->trim($separator)
151
        ;
152
    }
153
 
154
    private function createTransliterator(string $locale): ?\Transliterator
155
    {
156
        if (\array_key_exists($locale, $this->transliterators)) {
157
            return $this->transliterators[$locale];
158
        }
159
 
160
        // Exact locale supported, cache and return
161
        if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) {
162
            return $this->transliterators[$locale] = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
163
        }
164
 
165
        // Locale not supported and no parent, fallback to any-latin
166
        if (!$parent = self::getParentLocale($locale)) {
167
            return $this->transliterators[$locale] = null;
168
        }
169
 
170
        // Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales
171
        if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) {
172
            $transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
173
        }
174
 
175
        return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null;
176
    }
177
 
178
    private function createEmojiTransliterator(?string $locale): ?EmojiTransliterator
179
    {
180
        if (\is_string($this->emoji)) {
181
            $locale = $this->emoji;
182
        } elseif (!$this->emoji) {
183
            return null;
184
        }
185
 
186
        while (null !== $locale) {
187
            try {
188
                return EmojiTransliterator::create("emoji-$locale");
189
            } catch (\IntlException) {
190
                $locale = self::getParentLocale($locale);
191
            }
192
        }
193
 
194
        return null;
195
    }
196
 
197
    private static function getParentLocale(?string $locale): ?string
198
    {
199
        if (!$locale) {
200
            return null;
201
        }
202
        if (false === $str = strrchr($locale, '_')) {
203
            // no parent locale
204
            return null;
205
        }
206
 
207
        return substr($locale, 0, -\strlen($str));
208
    }
209
}