| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- <?php
- /**
- * A fast PHP slug generator and transliteration library, started as a PHP port of URLify.js
- * from the Django project + fallback via "Portable ASCII".
- *
- * - https://github.com/django/django/blob/master/django/contrib/admin/static/admin/js/urlify.js
- * - https://github.com/voku/portable-ascii
- *
- * Handles symbols from latin languages, Arabic, Azerbaijani, Bulgarian, Burmese, Croatian, Czech, Danish, Esperanto,
- * Estonian, Finnish, French, Switzerland (French), Austrian (French), Georgian, German, Switzerland (German),
- * Austrian (German), Greek, Hindi, Kazakh, Latvian, Lithuanian, Norwegian, Persian, Polish, Romanian, Russian, Swedish,
- * Serbian, Slovak, Turkish, Ukrainian and Vietnamese ... and many other via "ASCII::to_transliterate()".
- */
- class URLify
- {
- /**
- * The language-mapping array.
- *
- * ISO 639-1 codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
- *
- * @var array[]
- */
- public static $maps = [];
- /**
- * List of words to remove from URLs.
- *
- * @var array[]
- */
- public static $remove_list = [];
- /**
- * An array of strings that will convert into the separator-char - used by "URLify::filter()".
- *
- * @var string[]
- */
- private static $arrayToSeparator = [];
- /**
- * Add new strings the will be replaced with the separator.
- *
- * @param array $array <p>An array of things that should replaced by the separator.</p>
- * @param bool $merge <p>Keep the previous (default) array-to-separator array.</p>
- *
- * @return void
- *
- * @psalm-param string[] $array
- */
- public static function add_array_to_separator(array $array, bool $merge = true)
- {
- if ($merge === true) {
- self::$arrayToSeparator = \array_unique(
- \array_merge(
- self::$arrayToSeparator,
- $array
- )
- );
- } else {
- self::$arrayToSeparator = $array;
- }
- }
- /**
- * Add new characters to the list. `$map` should be a hash.
- *
- * @param array $map
- * @param string|null $language
- *
- * @return void
- *
- * @psalm-param array<string, string> $map
- */
- public static function add_chars(array $map, ?string $language = null)
- {
- $language_key = $language ?? \uniqid('urlify', true);
- if (isset(self::$maps[$language_key])) {
- self::$maps[$language_key] = \array_merge($map, self::$maps[$language_key]);
- } else {
- self::$maps[$language_key] = $map;
- }
- }
- /**
- * @return void
- */
- public static function reset_chars()
- {
- self::$maps = [];
- }
- /**
- * Transliterates characters to their ASCII equivalents.
- * $language specifies a priority for a specific language.
- * The latter is useful if languages have different rules for the same character.
- *
- * @param string $string <p>The input string.</p>
- * @param string $language <p>Your primary language.</p>
- * @param string $unknown <p>Character use if character unknown. (default is ?).</p>
- *
- * @return string
- */
- public static function downcode(
- string $string,
- string $language = 'en',
- string $unknown = ''
- ): string {
- $string = self::expandString($string, $language);
- foreach (self::$maps as $mapsInner) {
- foreach ($mapsInner as $orig => $replace) {
- $string = \str_replace($orig, $replace, $string);
- }
- }
- $string = \voku\helper\ASCII::to_ascii(
- $string,
- $language,
- false,
- true
- );
- return \voku\helper\ASCII::to_transliterate(
- $string,
- $unknown,
- false
- );
- }
-
- /**
- * Convert a String to URL slug. Wraps <strong>filter()</strong> with a simpler
- * set of defaults for typical usage in generating blog post slugs.
- *
- * @param string $string <p>The text you want to convert.</p>
- * @param int $maxLength <p>Max. length of the output string, set to "0" (zero) to
- * disable it</p>
- * @param string $separator <p>Define a new separator for the words.</p>
- * @param string $language <p>The language you want to convert to.</p>
- */
- public static function slug(
- string $string,
- int $maxLength = 200,
- string $separator = '-',
- string $language = 'en'
- ): string {
- return self::filter ($string, $maxLength, $language, false, false, true, $separator);
- }
- /**
- * Convert a String to URL.
- *
- * e.g.: "Petty<br>theft" to "Petty-theft"
- *
- * @param string $string <p>The text you want to convert.</p>
- * @param int $maxLength <p>Max. length of the output string, set to "0" (zero) to
- * disable it</p>
- * @param string $language <p>The language you want to convert to.</p>
- * @param bool $fileName <p>
- * Keep the "." from the extension e.g.: "imaäe.jpg" =>
- * "image.jpg"
- * </p>
- * @param bool $removeWords <p>
- * Remove some "words" from the string.<br />
- * Info: Set extra words via <strong>remove_words()</strong>.
- * </p>
- * @param bool $strToLower <p>Use <strong>strtolower()</strong> at the end.</p>
- * @param bool|string $separator <p>Define a new separator for the words.</p>
- *
- * @return string
- */
- public static function filter(
- string $string,
- int $maxLength = 200,
- string $language = 'en',
- bool $fileName = false,
- bool $removeWords = false,
- bool $strToLower = true,
- $separator = '-'
- ): string {
- if ($string === '') {
- return '';
- }
- // fallback
- if ($language === '') {
- $language = 'en';
- }
- // separator-fallback
- if ($separator === false) {
- $separator = '_';
- }
- if ($separator === true || $separator === '') {
- $separator = '-';
- }
- // escaped separator
- $separatorEscaped = \preg_quote($separator, '/');
- // use defaults, if there are no values
- if (self::$arrayToSeparator === []) {
- self::reset_array_to_separator();
- }
- // remove apostrophes which are not used as quotes around a string
- if (\strpos($string, "'") !== false) {
- $stringTmp = \preg_replace("/(\w)'(\w)/u", '${1}${2}', $string);
- if ($stringTmp !== null) {
- $string = (string) $stringTmp;
- }
- }
- // replace with $separator
- $string = (string) \preg_replace(
- self::$arrayToSeparator,
- $separator,
- $string
- );
- // remove all other html-tags
- if (
- \strpos($string, '<') !== false
- ||
- \strpos($string, '>') !== false
- ) {
- $string = \strip_tags($string);
- }
- // use special language replacer
- $string = self::downcode($string, $language);
- // replace with $separator, again
- $string = (string) \preg_replace(
- self::$arrayToSeparator,
- $separator,
- $string
- );
- // remove all these words from the string before urlifying
- $removeWordsSearch = '//';
- if ($removeWords === true) {
- $removeList = self::get_remove_list($language);
- if ($removeList !== []) {
- $removeWordsSearch = '/\b(?:' . \implode('|', $removeList) . ')\b/ui';
- }
- }
- // keep the "." from e.g.: a file-extension?
- if ($fileName) {
- $removePatternAddOn = '.';
- } else {
- $removePatternAddOn = '';
- }
- $string = (string) \preg_replace(
- [
- // 1) remove un-needed chars
- '/[^' . $separatorEscaped . $removePatternAddOn . '\-a-zA-Z0-9\s]/u',
- // 2) convert spaces to $separator
- '/[\s]+/u',
- // 3) remove some extras words
- $removeWordsSearch,
- // 4) remove double $separator's
- '/[' . ($separatorEscaped ?: ' ') . ']+/u',
- // 5) remove $separator at the end
- '/[' . ($separatorEscaped ?: ' ') . ']+$/u',
- ],
- [
- '',
- $separator,
- '',
- $separator,
- '',
- ],
- $string
- );
- // "substr" only if "$length" is set
- if (
- $maxLength
- &&
- $maxLength > 0
- &&
- \strlen($string) > $maxLength
- ) {
- $string = (string) \substr(\trim($string, $separator), 0, $maxLength);
- }
- // convert to lowercase
- if ($strToLower === true) {
- $string = \strtolower($string);
- }
- // trim "$separator" from beginning and end of the string
- return \trim($string, $separator);
- }
- /**
- * Append words to the remove list. Accepts either single words or an array of words.
- *
- * @param string|string[] $words
- * @param string $language
- * @param bool $merge <p>Keep the previous (default) remove-words array.</p>
- *
- * @return void
- */
- public static function remove_words($words, string $language = 'en', bool $merge = true)
- {
- if (\is_array($words) === false) {
- $words = [$words];
- }
- foreach ($words as $removeWordKey => $removeWord) {
- $words[$removeWordKey] = \preg_quote($removeWord, '/');
- }
- if ($merge === true) {
- self::$remove_list[$language] = \array_unique(
- \array_merge(
- self::get_remove_list($language),
- $words
- )
- );
- } else {
- self::$remove_list[$language] = $words;
- }
- }
- /**
- * Reset the internal "self::$arrayToSeparator" to the default values.
- *
- * @return void
- */
- public static function reset_array_to_separator()
- {
- self::$arrayToSeparator = [
- '/"|&|<|>|–|—/i', // ", &, <, >, –, —
- '/⁻|-|—|_|"|`|´|\'/',
- "#/\r\n|\r|\n|<br.*/?>#isU",
- ];
- }
- /**
- * reset the word-remove-array
- *
- * @param string $language
- *
- * @return void
- */
- public static function reset_remove_list(string $language = 'en')
- {
- if ($language === '') {
- return;
- }
- $language_orig = $language;
- $language = self::get_language_for_reset_remove_list($language);
- if ($language === '') {
- return;
- }
- $stopWords = new \voku\helper\StopWords();
- try {
- self::$remove_list[$language_orig] = $stopWords->getStopWordsFromLanguage($language);
- } catch (\voku\helper\StopWordsLanguageNotExists $e) {
- self::$remove_list[$language_orig] = [];
- }
- }
- /**
- * Alias of `URLify::downcode()`.
- *
- * @param string $string
- * @param string $language
- *
- * @return string
- */
- public static function transliterate(string $string, string $language = 'en'): string
- {
- return self::downcode($string, $language);
- }
- /**
- * Expands the given string replacing some special parts for words.
- * e.g. "lorem@ipsum.com" is replaced by "lorem at ipsum dot com".
- *
- * Most of these transformations have been inspired by the pelle/slugger
- * project, distributed under the Eclipse Public License.
- * Copyright 2012 Pelle Braendgaard
- *
- * @param string $string The string to expand
- * @param string $language
- *
- * @return string The result of expanding the string
- */
- protected static function expandString(string $string, string $language = 'en'): string
- {
- $string = self::expandCurrencies($string, $language);
- return self::expandSymbols($string, $language);
- }
- /**
- * @param string $language
- *
- * @return string
- */
- private static function get_language_for_reset_remove_list(string $language)
- {
- if ($language === '') {
- return '';
- }
- if (
- \strpos($language, '_') === false
- &&
- \strpos($language, '-') === false
- ) {
- $language = \strtolower($language);
- } else {
- $regex = '/(?<first>[a-z]{2}).*/i';
- $language = \strtolower((string) \preg_replace($regex, '$1', $language));
- }
- return $language;
- }
- /**
- * Expands the numeric currencies in euros, dollars, pounds
- * and yens that the given string may include.
- *
- * @param string $string
- * @param string $language
- *
- * @return string
- */
- private static function expandCurrencies(string $string, string $language = 'en')
- {
- if (
- \strpos($string, '€') === false
- &&
- \strpos($string, '$') === false
- &&
- \strpos($string, '£') === false
- &&
- \strpos($string, '¥') === false
- ) {
- return $string;
- }
- if ($language === 'de') {
- return (string) \preg_replace(
- [
- '/(?:\s|^)(\d+)(?: )*€(?:\s|$)/',
- '/(?:\s|^)\$(?: )*(\d+)(?:\s|$)/',
- '/(?:\s|^)£(?: )*(\d+)(?:\s|$)/',
- '/(?:\s|^)¥(?: )*(\d+)(?:\s|$)/',
- '/(?:\s|^)(\d+)[.|,](\d+)(?: )*€(?:\s|$)/',
- '/(?:\s|^)\$(?: )*(\d+)[.|,](\d+)(?:\s|$)/',
- '/(?:\s|^)£(?: )*(\d+)[.|,](\d+)(?:\s|$)/',
- ],
- [
- ' \1 Euro ',
- ' \1 Dollar ',
- ' \1 Pound ',
- ' \1 Yen ',
- ' \1 Euro \2 Cent ',
- ' \1 Dollar \2 Cent ',
- ' \1 Pound \2 Pence ',
- ],
- $string
- );
- }
- return (string) \preg_replace(
- [
- '/(?:\s|^)1(?: )*€(?:\s|$)/',
- '/(?:\s|^)(\d+)(?: )*€(?:\s|$)/',
- '/(?:\s|^)\$(?: )*1(?:\s|$)/',
- '/(?:\s|^)\$(?: )*(\d+)(?:\s|$)/',
- '/(?:\s|^)£(?: )*1(?:\s|$)/',
- '/(?:\s|^)£(?: )*(\d+)(?:\s|$)/',
- '/(?:\s|^)¥(?: )*(\d+)(?:\s|$)/',
- '/(?:\s|^)1[.|,](\d+)(?: )*€(?:\s|$)/',
- '/(?:\s|^)(\d+)[.|,](\d+)(?: )*€(?:\s|$)/',
- '/(?:\s|^)1[.|,](\d+)(?: )*$(?:\s|$)/',
- '/(?:\s|^)\$(?: )*(\d+)[.|,](\d+)(?:\s|$)/',
- '/(?:\s|^)1[.|,](\d+)(?: )*£(?:\s|$)/',
- '/(?:\s|^)£(?: )*(\d+)[.|,](\d+)(?:\s|$)/',
- ],
- [
- ' 1 Euro ',
- ' \1 Euros ',
- ' 1 Dollar ',
- ' \1 Dollars ',
- ' 1 Pound ',
- ' \1 Pounds ',
- ' \1 Yen ',
- ' 1 Euros \1 Cents ',
- ' \1 Euros \2 Cents ',
- ' 1 Dollars \1 Cents ',
- ' \1 Dollars \2 Cents ',
- ' 1 Pounds \1 Pence ',
- ' \1 Pounds \2 Pence ',
- ],
- $string
- );
- }
- /**
- * Expands the special symbols that the given string may include, such as '@', '.', '#' and '%'.
- *
- * @param string $string
- * @param string $language
- *
- * @return string
- */
- private static function expandSymbols(string $string, string $language = 'en')
- {
- if (
- \strpos($string, '©') === false
- &&
- \strpos($string, '®') === false
- &&
- \strpos($string, '@') === false
- &&
- \strpos($string, '&') === false
- &&
- \strpos($string, '%') === false
- &&
- \strpos($string, '=') === false
- ) {
- return $string;
- }
- $maps = \voku\helper\ASCII::charsArray(true);
- return (string) \preg_replace(
- [
- '/\s*©\s*/',
- '/\s*®\s*/',
- '/\s*@\s*/',
- '/\s*&\s*/',
- '/\s*%\s*/',
- '/(\s*=\s*)/',
- ],
- [
- $maps['latin_symbols']['©'],
- $maps['latin_symbols']['®'],
- $maps['latin_symbols']['@'],
- $maps[$language]['&'] ?? '&',
- $maps[$language]['%'] ?? '%',
- $maps[$language]['='] ?? '=',
- ],
- $string
- );
- }
- /**
- * return the "self::$remove_list[$language]" array
- *
- * @param string $language
- *
- * @return array<mixed>
- */
- private static function get_remove_list(string $language = 'en')
- {
- // check for language
- if ($language === '') {
- return [];
- }
- // set remove-array
- if (!isset(self::$remove_list[$language])) {
- self::reset_remove_list($language);
- }
- // check for array
- if (
- !isset(self::$remove_list[$language])
- ||
- empty(self::$remove_list[$language])
- ) {
- return [];
- }
- return self::$remove_list[$language];
- }
- }
|