diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3b762c9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.php] +indent_size = 4 + +[*.md,*.txt] +trim_trailing_whitespace = false +insert_final_newline = false + +[composer.json] +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..60a3d50 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Note: You need to uncomment the lines you want to use; the other lines can be deleted + +# Git +# .gitattributes export-ignore +# .gitignore export-ignore + +# Tests +# .coveralls.yml export-ignore +# .travis.yml export-ignore +# phpunit.xml.dist export-ignore +# tests/ export-ignore \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f5e259 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# OS files +.DS_Store + +# npm modules +/node_modules + +# files of Composer dependencies that are not needed for the plugin +/vendor/**/.* +/vendor/**/*.json +/vendor/**/*.txt +/vendor/**/*.md +/vendor/**/*.yml +/vendor/**/*.yaml +/vendor/**/*.xml +/vendor/**/*.dist +/vendor/**/readme.php +/vendor/**/LICENSE +/vendor/**/COPYING +/vendor/**/VERSION +/vendor/**/docs/* +/vendor/**/example/* +/vendor/**/examples/* +/vendor/**/test/* +/vendor/**/tests/* +/vendor/**/php4/* +/vendor/getkirby/composer-installer \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 0000000..a176cf0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 hana+nils · Büro für Gestaltung + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100755 index 0000000..a02feac --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +# Kirby Date Methods + +Kirby 3 plugin providing field and page methods for formatting dates and creating PHP date objects. + +## Installation + +### Download + +Download and copy this repository to `/site/plugins/date-methods`. + +### Git submodule + +``` +git submodule add https://github.com/hananils/kirby-date-methods.git site/plugins/date-methods +``` + +### Composer + +``` +composer require hananils/kirby-date-methods +``` + +## Field methods + +Field methods can be called on any field storing date information in a PHP-readable format. + +### toDateTime() + +Returns a `DateTime` representation of the field value, see [supported formats](https://www.php.net/manual/en/datetime.formats.php). + +```php +$page->date()->toDateTime() +``` + +### toDateTimeImmutable() + +Returns a `DateTimeImmutable` representation of the field value, see [supported formats](https://www.php.net/manual/en/datetime.formats.php). + +```php +$page->date()->toDateTimeImmutable() +``` + +### toDateIntervale() + +Returns a `DateInterval` representation of the field value, see [supported formats](https://www.php.net/manual/en/dateinterval.construct.php). + +```php +$page->date()->toDateInterval() +``` + +### toDateDiff(\$to) + +Returns a `DateInterval` object representing the difference between the field's date and the given date. The provided date can either be a `DateTime` object or a PHP-readable string, defaults to the difference to now. + +```php +$page->date()->toDateDiff('+1 month') +``` + +### toFormatted($datetype, $timetype, $timezone, $calendar, \$pattern) + +Returns a localized, formatted date using `IntlDateFormatter`, see [options](https://www.php.net/manual/de/intldateformatter.create.php). The locale is set based on the current Kirby language in a multilangual setup or on the `locale` config setting otherwise. + +```php +// Returns 1. Januar 2020 for 2020-01-01 and de_DE +$page->date()->toFormatted(); +``` + +### toRelative(\$from) + +Returns a human readable time difference to the given date, e. g. `just now`, `2 years ago`, `in 5 minutes`. The given date can be a `DateTime` object or any PHP-readable date string, see [supported formats](https://www.php.net/manual/en/datetime.formats.php). Defaults to now. + +```php +$page->date()->toRelative('next Monday'); +``` + +### toCurrentYear() + +Creates a `DateTime` representation of th field value and returns it with the year set to the current one. + +```php +$page->date()->toCurrentYear(); +``` + +### toCurrentMonth() + +Creates a `DateTime` representation of th field value and returns it with the month set to the current one. + +```php +$page->date()->toCurrentMonth(); +``` + +### toCurrentDay() + +Creates a `DateTime` representation of th field value and returns it with the day set to the current one. + +```php +$page->date()->toCurrentDay(); +``` + +### toAge($on, $format) + +Calculates the difference difference between the field value and the given `on` date. Return the difference in the given format, defaults to years. See [format options](https://www.php.net/manual/de/dateinterval.format.php). Useful to calculate the age of a person. + +```php +// Returns 10 given '2020-08-04' +$page->date()->toAge('2021-08-04') +``` + +## Pages methods + +### toDateRange($fieldStart, $fieldEnd) + +Returns a human-readable date range for the given dates: + +- `$fieldStart`: the start date field name, defaults to 'start' +- `$fieldEnd`: the end date field name, defaults to 'end' + +Returns a human-readable date range for the given dates and times: + +- `$fieldStart`: an array of the start date and time field names, defaults to ['start', 'starttime'] +- `$fieldEnd`: the end date and time field names, defaults to ['end', 'endtime'] + +The formatting is provided by [Ranger](https://github.com/flack/ranger). + +## Helpers + +### dateRelative($to, $from) + +Returns a human readable time difference to the given dates, e. g. `just now`, `2 years ago`, `in 5 minutes`. The given dates can be `DateTime` objects or any PHP-readable date strings, see [supported formats](https://www.php.net/manual/en/datetime.formats.php). + +```php +dateRelative('2019-12-31', 'now'); +``` + +### dateFormatted($locale, $datetype, $timetype, $timezone, $calendar, $pattern) + +Returns a localized, formatted date using `IntlDateFormatter`, see [options](https://www.php.net/manual/de/intldateformatter.create.php). + +```php +dateFormatted('de_DE', '2020-01-01'); +``` + +### dateRange($to, $from) + +Returns a human readable time difference to the given dates, e. g. `just now`, `2 years ago`, `in 5 minutes`. The given dates can be `DateTime` objects or any PHP-readable date strings, see [supported formats](https://www.php.net/manual/en/datetime.formats.php). + +```php +dateRange('2020-01-01', '2020-07-01'); +``` + +### datetime(\$datetime) + +Returns a `DateTime` object from the given date and time string. Directly returns the input if it's a `DateTime` object already. + +## License + +MIT + +## Credits + +[hana+nils · Büro für Gestaltung](https://hananils.de) diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..18d50bf --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "getkirby/pluginkit", + "description": "Kirby Example Plugin", + "type": "kirby-plugin", + "license": "MIT", + "authors": [ + { + "name": "Your Name", + "email": "you@example.com" + } + ], + "require": { + "getkirby/composer-installer": "^1.1", + "openpsa/ranger": "^0.5.1" + }, + "autoload": { + "psr-4": { + "Superwoman\\Superplugin\\": "src/" + } + }, + "config": { + "optimize-autoloader": true + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..10366b5 --- /dev/null +++ b/composer.lock @@ -0,0 +1,300 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6a854b756b235dabb4a788c8218a435f", + "packages": [ + { + "name": "getkirby/composer-installer", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/getkirby/composer-installer.git", + "reference": "2d6b8f5601a31caeeea45623e1643fbb437eb94e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getkirby/composer-installer/zipball/2d6b8f5601a31caeeea45623e1643fbb437eb94e", + "reference": "2d6b8f5601a31caeeea45623e1643fbb437eb94e", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "require-dev": { + "composer/composer": "^1.8", + "phpunit/phpunit": "^7.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Kirby\\ComposerInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "Kirby\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Kirby's custom Composer installer for the Kirby CMS and for Kirby plugins", + "homepage": "https://getkirby.com", + "time": "2019-02-11T20:27:36+00:00" + }, + { + "name": "openpsa/ranger", + "version": "v0.5.1", + "source": { + "type": "git", + "url": "https://github.com/flack/ranger.git", + "reference": "383b70fcea730e65433bf5c4d27ddf4eef4b045f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/flack/ranger/zipball/383b70fcea730e65433bf5c4d27ddf4eef4b045f", + "reference": "383b70fcea730e65433bf5c4d27ddf4eef4b045f", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/intl": "~2.6|~3.4|~4.0|~5.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8" + }, + "suggest": { + "ext-intl": "Needed for usage with other locales than 'en'" + }, + "type": "library", + "autoload": { + "psr-4": { + "OpenPsa\\Ranger\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Flack", + "email": "flack@contentcontrol-berlin.de", + "homepage": "http://www.contentcontrol-berlin.de/" + } + ], + "description": "Formatter for date and time ranges with i18n support", + "keywords": [ + "date range", + "time range" + ], + "time": "2020-05-18T15:52:26+00:00" + }, + { + "name": "symfony/intl", + "version": "v5.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/intl.git", + "reference": "b21d69ebb33adfcb7e6d8b0a0a8799db7090705b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/intl/zipball/b21d69ebb33adfcb7e6d8b0a0a8799db7090705b", + "reference": "b21d69ebb33adfcb7e6d8b0a0a8799db7090705b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "symfony/filesystem": "^4.4|^5.0" + }, + "suggest": { + "ext-intl": "to use the component with locales other than \"en\"" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Intl\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Eriksen Costa", + "email": "eriksen.costa@infranology.com.br" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A PHP replacement layer for the C intl extension that includes additional data from the ICU library.", + "homepage": "https://symfony.com", + "keywords": [ + "i18n", + "icu", + "internationalization", + "intl", + "l10n", + "localization" + ], + "time": "2020-05-30T20:35:19+00:00" + }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "4ef3923e4a86e1b6ef72d42be59dbf7d33a685e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/4ef3923e4a86e1b6ef72d42be59dbf7d33a685e3", + "reference": "4ef3923e4a86e1b6ef72d42be59dbf7d33a685e3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/intl": "~2.3|~3.0|~4.0|~5.0" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's ICU-related data and classes", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "icu", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2020-05-12T16:14:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "5e30b2799bc1ad68f7feb62b60a73743589438dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/5e30b2799bc1ad68f7feb62b60a73743589438dd", + "reference": "5e30b2799bc1ad68f7feb62b60a73743589438dd", + "shasum": "" + }, + "require": { + "php": ">=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2020-05-12T16:47:27+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/index.php b/index.php new file mode 100755 index 0000000..d07dfe8 --- /dev/null +++ b/index.php @@ -0,0 +1,256 @@ + [ + 'en' => [ + 'hananils.date-methods.yearsPast' => '{count} years ago', + 'hananils.date-methods.yearPast' => 'a year ago', + 'hananils.date-methods.monthsPast' => '{count} months ago', + 'hananils.date-methods.weeksPast' => '{count} weeks ago', + 'hananils.date-methods.daysPast' => '{count} days ago', + 'hananils.date-methods.dayPast' => 'yesterday', + 'hananils.date-methods.todayPast' => 'today', + 'hananils.date-methods.hoursPast' => '{count} hours ago', + 'hananils.date-methods.hourPast' => 'an hour ago', + 'hananils.date-methods.minutesPast' => '{count} minutes ago', + 'hananils.date-methods.now' => 'just now', + 'hananils.date-methods.minutesFuture' => 'in {count} minutes', + 'hananils.date-methods.hourFuture' => 'in an hour', + 'hananils.date-methods.hoursFuture' => 'in {count} hours', + 'hananils.date-methods.yesterday' => 'tomorrow', + 'hananils.date-methods.daysFuture' => 'in {count} days', + 'hananils.date-methods.weeksFuture' => 'in {count} weeks', + 'hananils.date-methods.monthsFuture' => 'in {count} months', + 'hananils.date-methods.yearFuture' => 'in a year', + 'hananils.date-methods.yearsFuture' => 'in {count} years', + 'hananils.date-methods.calendarweek' => 'week {week}' + ], + 'de' => [ + 'hananils.date-methods.yearsPast' => 'vor {count} Jahren', + 'hananils.date-methods.yearPast' => 'vor einem Jahr', + 'hananils.date-methods.monthsPast' => 'vor {count} Monaten', + 'hananils.date-methods.weeksPast' => 'vor {count} Wochen', + 'hananils.date-methods.daysPast' => 'vor {count} Tagen', + 'hananils.date-methods.dayPast' => 'gestern', + 'hananils.date-methods.todayPast' => 'heute', + 'hananils.date-methods.hoursPast' => 'vor {count} Stunden', + 'hananils.date-methods.hourPast' => 'vor einer Stunde', + 'hananils.date-methods.minutesPast' => 'vor {count} Minuten', + 'hananils.date-methods.now' => 'gerade eben', + 'hananils.date-methods.minutesFuture' => 'in {count} Minuten', + 'hananils.date-methods.hourFuture' => 'in einer Stunde', + 'hananils.date-methods.hoursFuture' => 'in {count} Stunden', + 'hananils.date-methods.yesterday' => 'morgen', + 'hananils.date-methods.daysFuture' => 'in {count} Tagen', + 'hananils.date-methods.weeksFuture' => 'in {count} Wochen', + 'hananils.date-methods.monthsFuture' => 'in {count} Monaten', + 'hananils.date-methods.yearFuture' => 'in einem Jahr', + 'hananils.date-methods.yearsFuture' => 'in {count} Jahren', + 'hananils.date-methods.calendarweek' => 'KW {week}' + ] + ], + 'fieldMethods' => [ + 'toDateTime' => function ($field) { + return datetime($field->value()); + }, + 'toDateTimeImmutable' => function ($field) { + $datetime = new DateTimeImmutable($field->value()); + + return $datetime; + }, + 'toDateInterval' => function ($field) { + $interval = new DateInterval($field->value()); + + return $interval; + }, + 'toDateDiff' => function ($field, $to = 'now') { + $from = $field->toDateTime(); + $to = datetime($to); + + return $from->diff($to); + }, + 'toFormatted' => function ($field, $datetype = IntlDateFormatter::LONG, $timetype = IntlDateFormatter::NONE, $timezone = null, $calendar = null, $pattern = "") { + if (kirby()->language()) { + $locale = kirby()->language()->locale(LC_ALL); + } else { + $locale = option('locale'); + + if (is_array($locale)) { + $locale = $locale[LC_ALL]; + } + } + + return dateFormatted($locale, $field->toDateTime(), $datetype, $timetype, $timezone, $calendar, $pattern); + }, + 'toRelative' => function ($field, $from = 'now') { + return dateRelative($field->value(), $from); + }, + 'toCurrentYear' => function ($field) { + $today = new DateTime(); + $date = $field->toDateTime(); + $current = new DateTime(); + $current->setDate($today->format('Y'), $date->format('m'), $date->format('d')); + + return $current; + }, + 'toCurrentMonth' => function ($field) { + $today = new DateTime(); + $date = $field->toDateTime(); + $current = new DateTime(); + $current->setDate($date->format('Y'), $today->format('m'), $date->format('d')); + + return $current; + }, + 'toCurrentDay' => function ($field) { + $today = new DateTime(); + $date = $field->toDateTime(); + $current = new DateTime(); + $current->setDate($date->format('Y'), $date->format('m'), $today->format('d')); + + return $current; + }, + 'toAge' => function ($field, $on = 'today', $format = '%Y') { + $birthday = new DateTime($field->value()); + $on = new DateTime($on); + $diff = $birthday->diff($on); + + return $diff->format($format); + } + ], + 'pageMethods' => [ + 'toDateRange' => function ($fieldStart = ['start', 'starttime'], $fieldEnd = ['end', 'endtime']) { + if (is_array($fieldStart)) { + $start = $this->content()->get($fieldStart[0])->toDate('Y-m-d'); + $starttime = $this->content()->get($fieldStart[1])->toDate('H:i'); + } else { + $start = $this->content()->get($fieldStart)->toDate('Y-m-d'); + $starttime = null; + } + + if (is_array($fieldEnd)) { + $end = $this->content()->get($fieldEnd[0])->toDate('Y-m-d'); + $endtime = $this->content()->get($fieldEnd[1])->toDate('H:i'); + } else { + $end = $this->content()->get($fieldEnd)->toDate('Y-m-d'); + $endtime = null; + } + + return dateRange([$start, $starttime], [$end, $endtime]); + } + ] +]); + +function dateRelative($to, $from = 'now') +{ + $from = datetime($from); + $to = datetime($to); + $diff = $from->diff($to); + $direction = $diff->invert ? 'Past' : 'Future'; + $count = null; + + if ($diff->y > 0) { + $id = $diff->y === 1 ? 'year' : 'years'; + $count = $diff->y; + } elseif ($diff->m > 1) { + $id = 'months'; + $count = $diff->m; + } elseif ($diff->days > 13) { + $id = 'weeks'; + $count = round($diff->days / 7); + } elseif ($diff->days > 1) { + $id = 'days'; + $count = $diff->days; + } elseif ($diff->days === 1 || $from->format('d') !== $to->format('d')) { + $id = 'day'; + } elseif ($direction === 'Past' && $diff->h > 3) { + $id = 'today'; + } elseif ($diff->h > 0) { + $id = 'hours'; + $count = $diff->h; + } elseif ($diff->h === 1) { + $id = 'hour'; + } elseif ($diff->i > 5) { + $id = 'minutes'; + $count = $diff->i; + } else { + $id = 'now'; + $direction = ''; + } + + return tt('hananils.date-methods.' . $id . $direction, [ + 'count' => $count + ]); +} + +function dateFormatted($locale, $datetime, $datetype = IntlDateFormatter::LONG, $timetype = IntlDateFormatter::NONE, $timezone = null, $calendar = null, $pattern = "") +{ + $formatter = new IntlDateFormatter($locale, $datetype, $timetype, $timezone, $calendar, $pattern); + + return $formatter->format(datetime($datetime)); +}; + +function dateRange($from = [null, null], $to = [null, null]) +{ + $options = option('hananils.date-methods', [ + 'code' => 'de', + 'rangeseparator' => '–', + 'datetimeseparator' => ', ', + 'datetype' => IntlDateFormatter::LONG, + 'timetype' => IntlDateFormatter::SHORT + ]); + + $ranger = new OpenPsa\Ranger\Ranger($options['code']); + $ranger->setRangeSeparator($options['rangeseparator']); + $ranger->setDateTimeSeparator($options['datetimeseparator']); + $ranger->setDateType($options['datetype']); + $ranger->setTimeType($options['timetype']); + + if (!is_array($from)) { + $from = [$from, null]; + } + if (!is_array($to)) { + $to = [$to, null]; + } + + $start = $end = datetime($from[0]); + if ($to[0] !== null) { + $end = datetime($to[0]); + } + + if (!empty($from[1])) { + list($hours, $minutes) = explode(':', $from[1]); + $start->setTime($hours, $minutes); + + if (!empty($to[1])) { + list($hours, $minutes) = explode(':', $to[1]); + } + + $end->setTime($hours, $minutes); + + $ranger->setTimeType(IntlDateFormatter::SHORT); + } else { + $ranger->setTimeType(IntlDateFormatter::NONE); + } + + $result = $ranger->format( + $start->format('Y-m-d H:i'), + $end->format('Y-m-d H:i') + ); + + if ($options['code'] === 'de') { + $result = preg_replace('/(\d.) – (\d)/', '$1 – $2', $result); + } + + return $result; +} + +function datetime($datetime = 'now') +{ + if (is_a($datetime, 'DateTime') || is_a($datetime, 'DateTimeImmutable')) { + return $datetime; + } + + return new DateTime($datetime); +} diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..cda81cd --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7f1b8d1 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,98 @@ + $vendorDir . '/symfony/intl/Resources/stubs/Collator.php', + 'IntlDateFormatter' => $vendorDir . '/symfony/intl/Resources/stubs/IntlDateFormatter.php', + 'Kirby\\ComposerInstaller\\CmsInstaller' => $vendorDir . '/getkirby/composer-installer/src/ComposerInstaller/CmsInstaller.php', + 'Kirby\\ComposerInstaller\\Installer' => $vendorDir . '/getkirby/composer-installer/src/ComposerInstaller/Installer.php', + 'Kirby\\ComposerInstaller\\Plugin' => $vendorDir . '/getkirby/composer-installer/src/ComposerInstaller/Plugin.php', + 'Kirby\\ComposerInstaller\\PluginInstaller' => $vendorDir . '/getkirby/composer-installer/src/ComposerInstaller/PluginInstaller.php', + 'Locale' => $vendorDir . '/symfony/intl/Resources/stubs/Locale.php', + 'NumberFormatter' => $vendorDir . '/symfony/intl/Resources/stubs/NumberFormatter.php', + 'OpenPsa\\Ranger\\Provider\\DeProvider' => $vendorDir . '/openpsa/ranger/src/Provider/DeProvider.php', + 'OpenPsa\\Ranger\\Provider\\DefaultProvider' => $vendorDir . '/openpsa/ranger/src/Provider/DefaultProvider.php', + 'OpenPsa\\Ranger\\Provider\\Provider' => $vendorDir . '/openpsa/ranger/src/Provider/Provider.php', + 'OpenPsa\\Ranger\\Ranger' => $vendorDir . '/openpsa/ranger/src/Ranger.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Superwoman\\Superplugin\\Superclass' => $baseDir . '/src/Superclass.php', + 'Symfony\\Component\\Intl\\Collator\\Collator' => $vendorDir . '/symfony/intl/Collator/Collator.php', + 'Symfony\\Component\\Intl\\Countries' => $vendorDir . '/symfony/intl/Countries.php', + 'Symfony\\Component\\Intl\\Currencies' => $vendorDir . '/symfony/intl/Currencies.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Compiler\\BundleCompilerInterface' => $vendorDir . '/symfony/intl/Data/Bundle/Compiler/BundleCompilerInterface.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Compiler\\GenrbCompiler' => $vendorDir . '/symfony/intl/Data/Bundle/Compiler/GenrbCompiler.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\BufferedBundleReader' => $vendorDir . '/symfony/intl/Data/Bundle/Reader/BufferedBundleReader.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\BundleEntryReader' => $vendorDir . '/symfony/intl/Data/Bundle/Reader/BundleEntryReader.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\BundleEntryReaderInterface' => $vendorDir . '/symfony/intl/Data/Bundle/Reader/BundleEntryReaderInterface.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\BundleReaderInterface' => $vendorDir . '/symfony/intl/Data/Bundle/Reader/BundleReaderInterface.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\IntlBundleReader' => $vendorDir . '/symfony/intl/Data/Bundle/Reader/IntlBundleReader.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\JsonBundleReader' => $vendorDir . '/symfony/intl/Data/Bundle/Reader/JsonBundleReader.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\PhpBundleReader' => $vendorDir . '/symfony/intl/Data/Bundle/Reader/PhpBundleReader.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Writer\\BundleWriterInterface' => $vendorDir . '/symfony/intl/Data/Bundle/Writer/BundleWriterInterface.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Writer\\JsonBundleWriter' => $vendorDir . '/symfony/intl/Data/Bundle/Writer/JsonBundleWriter.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Writer\\PhpBundleWriter' => $vendorDir . '/symfony/intl/Data/Bundle/Writer/PhpBundleWriter.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Writer\\TextBundleWriter' => $vendorDir . '/symfony/intl/Data/Bundle/Writer/TextBundleWriter.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\AbstractDataGenerator' => $vendorDir . '/symfony/intl/Data/Generator/AbstractDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\CurrencyDataGenerator' => $vendorDir . '/symfony/intl/Data/Generator/CurrencyDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\FallbackTrait' => $vendorDir . '/symfony/intl/Data/Generator/FallbackTrait.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\GeneratorConfig' => $vendorDir . '/symfony/intl/Data/Generator/GeneratorConfig.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\LanguageDataGenerator' => $vendorDir . '/symfony/intl/Data/Generator/LanguageDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\LocaleDataGenerator' => $vendorDir . '/symfony/intl/Data/Generator/LocaleDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\RegionDataGenerator' => $vendorDir . '/symfony/intl/Data/Generator/RegionDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\ScriptDataGenerator' => $vendorDir . '/symfony/intl/Data/Generator/ScriptDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\TimezoneDataGenerator' => $vendorDir . '/symfony/intl/Data/Generator/TimezoneDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Util\\ArrayAccessibleResourceBundle' => $vendorDir . '/symfony/intl/Data/Util/ArrayAccessibleResourceBundle.php', + 'Symfony\\Component\\Intl\\Data\\Util\\LocaleScanner' => $vendorDir . '/symfony/intl/Data/Util/LocaleScanner.php', + 'Symfony\\Component\\Intl\\Data\\Util\\RecursiveArrayAccess' => $vendorDir . '/symfony/intl/Data/Util/RecursiveArrayAccess.php', + 'Symfony\\Component\\Intl\\Data\\Util\\RingBuffer' => $vendorDir . '/symfony/intl/Data/Util/RingBuffer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\AmPmTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/AmPmTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\DayOfWeekTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/DayOfWeekTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\DayOfYearTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/DayOfYearTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\DayTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/DayTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\FullTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/FullTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\Hour1200Transformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/Hour1200Transformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\Hour1201Transformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/Hour1201Transformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\Hour2400Transformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/Hour2400Transformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\Hour2401Transformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/Hour2401Transformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\HourTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/HourTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\MinuteTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/MinuteTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\MonthTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/MonthTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\QuarterTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/QuarterTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\SecondTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/SecondTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\TimezoneTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/TimezoneTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\Transformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/Transformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\YearTransformer' => $vendorDir . '/symfony/intl/DateFormatter/DateFormat/YearTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\IntlDateFormatter' => $vendorDir . '/symfony/intl/DateFormatter/IntlDateFormatter.php', + 'Symfony\\Component\\Intl\\Exception\\BadMethodCallException' => $vendorDir . '/symfony/intl/Exception/BadMethodCallException.php', + 'Symfony\\Component\\Intl\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/intl/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Intl\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/intl/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Intl\\Exception\\MethodArgumentNotImplementedException' => $vendorDir . '/symfony/intl/Exception/MethodArgumentNotImplementedException.php', + 'Symfony\\Component\\Intl\\Exception\\MethodArgumentValueNotImplementedException' => $vendorDir . '/symfony/intl/Exception/MethodArgumentValueNotImplementedException.php', + 'Symfony\\Component\\Intl\\Exception\\MethodNotImplementedException' => $vendorDir . '/symfony/intl/Exception/MethodNotImplementedException.php', + 'Symfony\\Component\\Intl\\Exception\\MissingResourceException' => $vendorDir . '/symfony/intl/Exception/MissingResourceException.php', + 'Symfony\\Component\\Intl\\Exception\\NotImplementedException' => $vendorDir . '/symfony/intl/Exception/NotImplementedException.php', + 'Symfony\\Component\\Intl\\Exception\\OutOfBoundsException' => $vendorDir . '/symfony/intl/Exception/OutOfBoundsException.php', + 'Symfony\\Component\\Intl\\Exception\\ResourceBundleNotFoundException' => $vendorDir . '/symfony/intl/Exception/ResourceBundleNotFoundException.php', + 'Symfony\\Component\\Intl\\Exception\\RuntimeException' => $vendorDir . '/symfony/intl/Exception/RuntimeException.php', + 'Symfony\\Component\\Intl\\Exception\\UnexpectedTypeException' => $vendorDir . '/symfony/intl/Exception/UnexpectedTypeException.php', + 'Symfony\\Component\\Intl\\Globals\\IntlGlobals' => $vendorDir . '/symfony/intl/Globals/IntlGlobals.php', + 'Symfony\\Component\\Intl\\Intl' => $vendorDir . '/symfony/intl/Intl.php', + 'Symfony\\Component\\Intl\\Languages' => $vendorDir . '/symfony/intl/Languages.php', + 'Symfony\\Component\\Intl\\Locale' => $vendorDir . '/symfony/intl/Locale.php', + 'Symfony\\Component\\Intl\\Locale\\Locale' => $vendorDir . '/symfony/intl/Locale/Locale.php', + 'Symfony\\Component\\Intl\\Locales' => $vendorDir . '/symfony/intl/Locales.php', + 'Symfony\\Component\\Intl\\NumberFormatter\\NumberFormatter' => $vendorDir . '/symfony/intl/NumberFormatter/NumberFormatter.php', + 'Symfony\\Component\\Intl\\ResourceBundle' => $vendorDir . '/symfony/intl/ResourceBundle.php', + 'Symfony\\Component\\Intl\\Scripts' => $vendorDir . '/symfony/intl/Scripts.php', + 'Symfony\\Component\\Intl\\Timezones' => $vendorDir . '/symfony/intl/Timezones.php', + 'Symfony\\Component\\Intl\\Util\\GitRepository' => $vendorDir . '/symfony/intl/Util/GitRepository.php', + 'Symfony\\Component\\Intl\\Util\\IcuVersion' => $vendorDir . '/symfony/intl/Util/IcuVersion.php', + 'Symfony\\Component\\Intl\\Util\\IntlTestHelper' => $vendorDir . '/symfony/intl/Util/IntlTestHelper.php', + 'Symfony\\Component\\Intl\\Util\\Version' => $vendorDir . '/symfony/intl/Util/Version.php', + 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..c4c22e9 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,11 @@ + $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '6a47392539ca2329373e0d33e1dba053' => $vendorDir . '/symfony/polyfill-intl-icu/bootstrap.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Component\\Intl\\' => array($vendorDir . '/symfony/intl'), + 'Superwoman\\Superplugin\\' => array($baseDir . '/src'), + 'OpenPsa\\Ranger\\' => array($vendorDir . '/openpsa/ranger/src'), + 'Kirby\\' => array($vendorDir . '/getkirby/composer-installer/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..5dfe2ef --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit8353d4e334a9604aa595127a111bced4::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit8353d4e334a9604aa595127a111bced4::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire8353d4e334a9604aa595127a111bced4($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire8353d4e334a9604aa595127a111bced4($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..c69985a --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,155 @@ + __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '6a47392539ca2329373e0d33e1dba053' => __DIR__ . '/..' . '/symfony/polyfill-intl-icu/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Component\\Intl\\' => 23, + 'Superwoman\\Superplugin\\' => 23, + ), + 'O' => + array ( + 'OpenPsa\\Ranger\\' => 15, + ), + 'K' => + array ( + 'Kirby\\' => 6, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Component\\Intl\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/intl', + ), + 'Superwoman\\Superplugin\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + 'OpenPsa\\Ranger\\' => + array ( + 0 => __DIR__ . '/..' . '/openpsa/ranger/src', + ), + 'Kirby\\' => + array ( + 0 => __DIR__ . '/..' . '/getkirby/composer-installer/src', + ), + ); + + public static $classMap = array ( + 'Collator' => __DIR__ . '/..' . '/symfony/intl/Resources/stubs/Collator.php', + 'IntlDateFormatter' => __DIR__ . '/..' . '/symfony/intl/Resources/stubs/IntlDateFormatter.php', + 'Kirby\\ComposerInstaller\\CmsInstaller' => __DIR__ . '/..' . '/getkirby/composer-installer/src/ComposerInstaller/CmsInstaller.php', + 'Kirby\\ComposerInstaller\\Installer' => __DIR__ . '/..' . '/getkirby/composer-installer/src/ComposerInstaller/Installer.php', + 'Kirby\\ComposerInstaller\\Plugin' => __DIR__ . '/..' . '/getkirby/composer-installer/src/ComposerInstaller/Plugin.php', + 'Kirby\\ComposerInstaller\\PluginInstaller' => __DIR__ . '/..' . '/getkirby/composer-installer/src/ComposerInstaller/PluginInstaller.php', + 'Locale' => __DIR__ . '/..' . '/symfony/intl/Resources/stubs/Locale.php', + 'NumberFormatter' => __DIR__ . '/..' . '/symfony/intl/Resources/stubs/NumberFormatter.php', + 'OpenPsa\\Ranger\\Provider\\DeProvider' => __DIR__ . '/..' . '/openpsa/ranger/src/Provider/DeProvider.php', + 'OpenPsa\\Ranger\\Provider\\DefaultProvider' => __DIR__ . '/..' . '/openpsa/ranger/src/Provider/DefaultProvider.php', + 'OpenPsa\\Ranger\\Provider\\Provider' => __DIR__ . '/..' . '/openpsa/ranger/src/Provider/Provider.php', + 'OpenPsa\\Ranger\\Ranger' => __DIR__ . '/..' . '/openpsa/ranger/src/Ranger.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Superwoman\\Superplugin\\Superclass' => __DIR__ . '/../..' . '/src/Superclass.php', + 'Symfony\\Component\\Intl\\Collator\\Collator' => __DIR__ . '/..' . '/symfony/intl/Collator/Collator.php', + 'Symfony\\Component\\Intl\\Countries' => __DIR__ . '/..' . '/symfony/intl/Countries.php', + 'Symfony\\Component\\Intl\\Currencies' => __DIR__ . '/..' . '/symfony/intl/Currencies.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Compiler\\BundleCompilerInterface' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Compiler/BundleCompilerInterface.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Compiler\\GenrbCompiler' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Compiler/GenrbCompiler.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\BufferedBundleReader' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Reader/BufferedBundleReader.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\BundleEntryReader' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Reader/BundleEntryReader.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\BundleEntryReaderInterface' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Reader/BundleEntryReaderInterface.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\BundleReaderInterface' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Reader/BundleReaderInterface.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\IntlBundleReader' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Reader/IntlBundleReader.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\JsonBundleReader' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Reader/JsonBundleReader.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Reader\\PhpBundleReader' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Reader/PhpBundleReader.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Writer\\BundleWriterInterface' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Writer/BundleWriterInterface.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Writer\\JsonBundleWriter' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Writer/JsonBundleWriter.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Writer\\PhpBundleWriter' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Writer/PhpBundleWriter.php', + 'Symfony\\Component\\Intl\\Data\\Bundle\\Writer\\TextBundleWriter' => __DIR__ . '/..' . '/symfony/intl/Data/Bundle/Writer/TextBundleWriter.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\AbstractDataGenerator' => __DIR__ . '/..' . '/symfony/intl/Data/Generator/AbstractDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\CurrencyDataGenerator' => __DIR__ . '/..' . '/symfony/intl/Data/Generator/CurrencyDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\FallbackTrait' => __DIR__ . '/..' . '/symfony/intl/Data/Generator/FallbackTrait.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\GeneratorConfig' => __DIR__ . '/..' . '/symfony/intl/Data/Generator/GeneratorConfig.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\LanguageDataGenerator' => __DIR__ . '/..' . '/symfony/intl/Data/Generator/LanguageDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\LocaleDataGenerator' => __DIR__ . '/..' . '/symfony/intl/Data/Generator/LocaleDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\RegionDataGenerator' => __DIR__ . '/..' . '/symfony/intl/Data/Generator/RegionDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\ScriptDataGenerator' => __DIR__ . '/..' . '/symfony/intl/Data/Generator/ScriptDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Generator\\TimezoneDataGenerator' => __DIR__ . '/..' . '/symfony/intl/Data/Generator/TimezoneDataGenerator.php', + 'Symfony\\Component\\Intl\\Data\\Util\\ArrayAccessibleResourceBundle' => __DIR__ . '/..' . '/symfony/intl/Data/Util/ArrayAccessibleResourceBundle.php', + 'Symfony\\Component\\Intl\\Data\\Util\\LocaleScanner' => __DIR__ . '/..' . '/symfony/intl/Data/Util/LocaleScanner.php', + 'Symfony\\Component\\Intl\\Data\\Util\\RecursiveArrayAccess' => __DIR__ . '/..' . '/symfony/intl/Data/Util/RecursiveArrayAccess.php', + 'Symfony\\Component\\Intl\\Data\\Util\\RingBuffer' => __DIR__ . '/..' . '/symfony/intl/Data/Util/RingBuffer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\AmPmTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/AmPmTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\DayOfWeekTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/DayOfWeekTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\DayOfYearTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/DayOfYearTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\DayTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/DayTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\FullTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/FullTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\Hour1200Transformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/Hour1200Transformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\Hour1201Transformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/Hour1201Transformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\Hour2400Transformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/Hour2400Transformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\Hour2401Transformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/Hour2401Transformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\HourTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/HourTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\MinuteTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/MinuteTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\MonthTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/MonthTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\QuarterTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/QuarterTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\SecondTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/SecondTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\TimezoneTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/TimezoneTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\Transformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/Transformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\DateFormat\\YearTransformer' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/DateFormat/YearTransformer.php', + 'Symfony\\Component\\Intl\\DateFormatter\\IntlDateFormatter' => __DIR__ . '/..' . '/symfony/intl/DateFormatter/IntlDateFormatter.php', + 'Symfony\\Component\\Intl\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/symfony/intl/Exception/BadMethodCallException.php', + 'Symfony\\Component\\Intl\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/intl/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Intl\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/intl/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Intl\\Exception\\MethodArgumentNotImplementedException' => __DIR__ . '/..' . '/symfony/intl/Exception/MethodArgumentNotImplementedException.php', + 'Symfony\\Component\\Intl\\Exception\\MethodArgumentValueNotImplementedException' => __DIR__ . '/..' . '/symfony/intl/Exception/MethodArgumentValueNotImplementedException.php', + 'Symfony\\Component\\Intl\\Exception\\MethodNotImplementedException' => __DIR__ . '/..' . '/symfony/intl/Exception/MethodNotImplementedException.php', + 'Symfony\\Component\\Intl\\Exception\\MissingResourceException' => __DIR__ . '/..' . '/symfony/intl/Exception/MissingResourceException.php', + 'Symfony\\Component\\Intl\\Exception\\NotImplementedException' => __DIR__ . '/..' . '/symfony/intl/Exception/NotImplementedException.php', + 'Symfony\\Component\\Intl\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/symfony/intl/Exception/OutOfBoundsException.php', + 'Symfony\\Component\\Intl\\Exception\\ResourceBundleNotFoundException' => __DIR__ . '/..' . '/symfony/intl/Exception/ResourceBundleNotFoundException.php', + 'Symfony\\Component\\Intl\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/intl/Exception/RuntimeException.php', + 'Symfony\\Component\\Intl\\Exception\\UnexpectedTypeException' => __DIR__ . '/..' . '/symfony/intl/Exception/UnexpectedTypeException.php', + 'Symfony\\Component\\Intl\\Globals\\IntlGlobals' => __DIR__ . '/..' . '/symfony/intl/Globals/IntlGlobals.php', + 'Symfony\\Component\\Intl\\Intl' => __DIR__ . '/..' . '/symfony/intl/Intl.php', + 'Symfony\\Component\\Intl\\Languages' => __DIR__ . '/..' . '/symfony/intl/Languages.php', + 'Symfony\\Component\\Intl\\Locale' => __DIR__ . '/..' . '/symfony/intl/Locale.php', + 'Symfony\\Component\\Intl\\Locale\\Locale' => __DIR__ . '/..' . '/symfony/intl/Locale/Locale.php', + 'Symfony\\Component\\Intl\\Locales' => __DIR__ . '/..' . '/symfony/intl/Locales.php', + 'Symfony\\Component\\Intl\\NumberFormatter\\NumberFormatter' => __DIR__ . '/..' . '/symfony/intl/NumberFormatter/NumberFormatter.php', + 'Symfony\\Component\\Intl\\ResourceBundle' => __DIR__ . '/..' . '/symfony/intl/ResourceBundle.php', + 'Symfony\\Component\\Intl\\Scripts' => __DIR__ . '/..' . '/symfony/intl/Scripts.php', + 'Symfony\\Component\\Intl\\Timezones' => __DIR__ . '/..' . '/symfony/intl/Timezones.php', + 'Symfony\\Component\\Intl\\Util\\GitRepository' => __DIR__ . '/..' . '/symfony/intl/Util/GitRepository.php', + 'Symfony\\Component\\Intl\\Util\\IcuVersion' => __DIR__ . '/..' . '/symfony/intl/Util/IcuVersion.php', + 'Symfony\\Component\\Intl\\Util\\IntlTestHelper' => __DIR__ . '/..' . '/symfony/intl/Util/IntlTestHelper.php', + 'Symfony\\Component\\Intl\\Util\\Version' => __DIR__ . '/..' . '/symfony/intl/Util/Version.php', + 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit8353d4e334a9604aa595127a111bced4::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit8353d4e334a9604aa595127a111bced4::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit8353d4e334a9604aa595127a111bced4::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/openpsa/ranger/src/Provider/DeProvider.php b/vendor/openpsa/ranger/src/Provider/DeProvider.php new file mode 100644 index 0000000..098e4cc --- /dev/null +++ b/vendor/openpsa/ranger/src/Provider/DeProvider.php @@ -0,0 +1,32 @@ + Ranger::MONTH + || $intl->getDateType() < IntlDateFormatter::MEDIUM) { + $separator = ' ' . trim($separator) . ' '; + } + if ( $best_match == Ranger::MONTH + || ( $intl->getDateType() > IntlDateFormatter::LONG + && $best_match == Ranger::YEAR)) { + return '.' . $separator; + } + return $separator; + } +} diff --git a/vendor/openpsa/ranger/src/Provider/DefaultProvider.php b/vendor/openpsa/ranger/src/Provider/DefaultProvider.php new file mode 100644 index 0000000..ef7efda --- /dev/null +++ b/vendor/openpsa/ranger/src/Provider/DefaultProvider.php @@ -0,0 +1,26 @@ +getDateType() < IntlDateFormatter::MEDIUM) { + return ' ' . trim($separator) . ' '; + } + return $separator; + } +} diff --git a/vendor/openpsa/ranger/src/Provider/Provider.php b/vendor/openpsa/ranger/src/Provider/Provider.php new file mode 100644 index 0000000..dbd6fd3 --- /dev/null +++ b/vendor/openpsa/ranger/src/Provider/Provider.php @@ -0,0 +1,21 @@ + self::ERA, + 'y' => self::YEAR, + 'Y' => self::YEAR, + 'u' => self::YEAR, + 'U' => self::YEAR, + 'r' => self::YEAR, + 'Q' => self::QUARTER, + 'q' => self::QUARTER, + 'M' => self::MONTH, + 'L' => self::MONTH, + 'w' => self::WEEK, + 'W' => self::WEEK, + 'd' => self::DAY, + 'D' => self::DAY, + 'F' => self::DAY, + 'g' => self::DAY, + 'E' => self::DAY, + 'e' => self::DAY, + 'c' => self::DAY, + 'a' => self::AM, + 'h' => self::HOUR, + 'H' => self::HOUR, + 'k' => self::HOUR, + 'K' => self::HOUR, + 'm' => self::MINUTE, + 's' => self::SECOND, + 'S' => self::SECOND, + 'A' => self::SECOND, + 'z' => self::TIMEZONE, + 'Z' => self::TIMEZONE, + 'O' => self::TIMEZONE, + 'v' => self::TIMEZONE, + 'V' => self::TIMEZONE, + 'X' => self::TIMEZONE, + 'x' => self::TIMEZONE + ]; + + /** + * @var string + */ + private $escape_character = "'"; + + /** + * @var array + */ + private $pattern_mask; + + /** + * @var int + */ + private $precision; + + /** + * @var string + */ + private $locale; + + /** + * @var string + */ + private $range_separator = '–'; + + /** + * @var string + */ + private $date_time_separator = ', '; + + /** + * @var int + */ + private $date_type = IntlDateFormatter::MEDIUM; + + /** + * @var int + */ + private $time_type = IntlDateFormatter::NONE; + + /** + * + * @param string $locale + */ + public function __construct($locale) + { + $this->locale = $locale; + } + + /** + * @param int $type One of the IntlDateFormatter constants + * @return self + */ + public function setDateType($type) + { + if ($type !== $this->date_type) { + $this->date_type = $type; + $this->pattern_mask = []; + $this->precision = 0; + } + return $this; + } + + /** + * @param int $type One of the IntlDateFormatter constants + * @return self + */ + public function setTimeType($type) + { + if ($type !== $this->time_type) { + $this->time_type = $type; + $this->pattern_mask = []; + $this->precision = 0; + } + return $this; + } + + /** + * @param string $separator + * @return self + */ + public function setRangeSeparator($separator) + { + $this->range_separator = $separator; + return $this; + } + + /** + * @param string $separator + * @return self + */ + public function setDateTimeSeparator($separator) + { + $this->date_time_separator = $separator; + return $this; + } + + /** + * + * @param mixed $start + * @param mixed $end + * @return string + */ + public function format($start, $end) + { + $start = $this->prepare_date($start); + $end = $this->prepare_date($end); + + $best_match = $this->find_best_match($start, $end); + + $this->parse_pattern(); + + $start_tokens = $this->tokenize($start); + $end_tokens = $this->tokenize($end); + + $left = ''; + foreach ($this->pattern_mask as $i => $part) { + if ($part['delimiter']) { + $left .= $part['content']; + } else { + if ($part['content'] > $best_match) { + break; + } + $left .= $start_tokens[$i]['content']; + } + } + + if ($best_match >= $this->precision) { + // the given dates are identical for the requested rendering + return $left; + } + + $right = ''; + for ($j = count($this->pattern_mask) - 1; $j + 1 > $i; $j--) { + $part = $end_tokens[$j]; + if ($part['type'] == 'delimiter') { + $right = $part['content'] . $right; + } else { + if ($part['type'] > $best_match) { + break; + } + $right = $part['content'] . $right; + } + } + + $left_middle = ''; + $right_middle = ''; + for ($k = $i; $k <= $j; $k++) { + $left_middle .= $start_tokens[$k]['content']; + $right_middle .= $end_tokens[$k]['content']; + } + + return $left . $left_middle . $this->get_range_separator($best_match) . $right_middle . $right; + } + + /** + * @param mixed $input + * @throws InvalidArgumentException + * @return \DateTime + */ + private function prepare_date($input) + { + if ($input instanceof DateTime) { + return $input; + } + if (is_string($input)) { + return new Datetime($input); + } + if (is_int($input)) { + $date = new Datetime; + $date->setTimestamp($input); + return $date; + } + if ($input === null) { + return new Datetime; + } + throw new InvalidArgumentException("Don't know how to handle " . gettype($input)); + } + + /** + * @param int $best_match + * @return string + */ + private function get_range_separator($best_match) + { + $intl = new IntlDateFormatter($this->locale, $this->date_type, $this->time_type); + + $provider_class = 'OpenPsa\\Ranger\\Provider\\' . ucfirst(substr($intl->getLocale(), 0, 2)) . 'Provider'; + + if (!class_exists($provider_class)) { + $provider_class = DefaultProvider::class; + } + $provider = new $provider_class(); + + return $provider->modifySeparator($intl, $best_match, $this->range_separator); + } + + /** + * @param DateTime $date + * @return array + */ + private function tokenize(DateTime $date) + { + $tokens = []; + + $formatted = ""; + if ($this->date_type === IntlDateFormatter::NONE && $this->time_type === IntlDateFormatter::NONE) { + // why would you want this? + return $tokens; + } + + if ($this->date_type !== IntlDateFormatter::NONE) { + $intl = new IntlDateFormatter($this->locale, $this->date_type, IntlDateFormatter::NONE, $date->getTimezone()); + $formatted .= $intl->format((int) $date->format('U')); + if ($this->time_type !== IntlDateFormatter::NONE) { + $formatted .= $this->date_time_separator; + } + } + + if ($this->time_type !== IntlDateFormatter::NONE) { + $intl = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->time_type, $date->getTimezone()); + $formatted .= $intl->format((int) $date->format('U')); + } + + $type = null; + foreach ($this->pattern_mask as $part) { + if ($part['delimiter']) { + $parts = explode($part['content'], $formatted, 2); + + if (count($parts) == 2) { + $tokens[] = ['type' => $type, 'content' => $parts[0]]; + $formatted = $parts[1]; + } + $tokens[] = ['type' => 'delimiter', 'content' => $part['content']]; + } else { + $type = $part['content']; + } + } + if (!$part['delimiter']) { + $tokens[] = ['type' => $type, 'content' => $formatted]; + } + return $tokens; + } + + /** + * + * @param DateTime $start + * @param DateTime $end + * @return int + */ + private function find_best_match(DateTime $start, DateTime $end) + { + // make a copy of end because we might change pieces of it + $end_copy = clone $end; + + // ignore the date if it's not output + if ($this->date_type === IntlDateFormatter::NONE) { + $end_copy->setDate($start->format('Y'), $start->format('m'), $start->format('d')); + } + + $map = [ + 'Y' => self::TIMEZONE, + 'm' => self::YEAR, + 'd' => self::MONTH, + 'a' => self::DAY, + 'H' => self::AM, + 'i' => self::AM, // it makes no sense to display something like 10:00:00 - 30:00... + 's' => self::AM, // it makes no sense to display something like 10:00:00 - 30... + ]; + $best_match = self::SECOND; + + foreach ($map as $part => $score) { + if ($start->format($part) !== $end_copy->format($part)) { + $best_match = $score; + break; + } + } + + //set to same time to avoid DST problems + $end_copy->setTimestamp((int) $start->format('U')); + if ( $start->format('T') !== $end_copy->format('T') + || ( $this->time_type !== IntlDateFormatter::NONE + && $best_match < self::DAY)) { + $best_match = self::NO_MATCH; + } + + return $best_match; + } + + private function parse_pattern() + { + if (!empty($this->pattern_mask)) { + return; + } + + $pattern = ""; + if ($this->date_type !== IntlDateFormatter::NONE) { + $intl = new IntlDateFormatter($this->locale, $this->date_type, IntlDateFormatter::NONE); + $pattern .= $intl->getPattern(); + if ($this->time_type !== IntlDateFormatter::NONE) { + $pattern .= "'" . $this->date_time_separator . "'"; + } + } + + if ($this->time_type !== IntlDateFormatter::NONE) { + $intl = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->time_type); + $pattern .= $intl->getPattern(); + } + + $esc_active = false; + $part = ['content' => '', 'delimiter' => false]; + foreach (str_split($pattern) as $char) { + if ($char == $this->escape_character) { + if ($esc_active) { + $esc_active = false; + // @todo the esc char handling is untested + if ($part['content'] === '') { + //Literal ' + $part['content'] = $char; + } + + $this->push_to_mask($part); + $part = ['content' => '', 'delimiter' => false]; + } else { + $esc_active = true; + $this->push_to_mask($part); + $part = ['content' => '', 'delimiter' => true]; + } + } elseif ($esc_active) { + $part['content'] .= $char; + } elseif (!array_key_exists($char, $this->pattern_characters)) { + if ($part['delimiter'] === false) { + $this->push_to_mask($part); + $part = ['content' => $char, 'delimiter' => true]; + } else { + $part['content'] .= $char; + } + } else { + if ($part['delimiter'] === true) { + $this->push_to_mask($part); + $part = ['content' => $this->pattern_characters[$char], 'delimiter' => false]; + } else { + if ( $part['content'] !== '' + && $part['content'] !== $this->pattern_characters[$char]) { + throw new RuntimeException('missing separator between date parts'); + } + $part['content'] = $this->pattern_characters[$char]; + } + } + } + $this->push_to_mask($part); + } + + /** + * @param array $part + */ + private function push_to_mask(array $part) + { + if ($part['content'] !== '') { + $this->pattern_mask[] = $part; + $this->precision = max($this->precision, $part['content']); + } + } +} diff --git a/vendor/symfony/intl/Collator/Collator.php b/vendor/symfony/intl/Collator/Collator.php new file mode 100644 index 0000000..ff0d258 --- /dev/null +++ b/vendor/symfony/intl/Collator/Collator.php @@ -0,0 +1,295 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Collator; + +use Symfony\Component\Intl\Exception\MethodArgumentValueNotImplementedException; +use Symfony\Component\Intl\Exception\MethodNotImplementedException; +use Symfony\Component\Intl\Globals\IntlGlobals; +use Symfony\Component\Intl\Locale\Locale; + +/** + * Replacement for PHP's native {@link \Collator} class. + * + * The only methods currently supported in this class are: + * + * - {@link \__construct} + * - {@link create} + * - {@link asort} + * - {@link getErrorCode} + * - {@link getErrorMessage} + * - {@link getLocale} + * + * @author Igor Wiedler + * @author Bernhard Schussek + * + * @internal + */ +abstract class Collator +{ + /* Attribute constants */ + const FRENCH_COLLATION = 0; + const ALTERNATE_HANDLING = 1; + const CASE_FIRST = 2; + const CASE_LEVEL = 3; + const NORMALIZATION_MODE = 4; + const STRENGTH = 5; + const HIRAGANA_QUATERNARY_MODE = 6; + const NUMERIC_COLLATION = 7; + + /* Attribute constants values */ + const DEFAULT_VALUE = -1; + + const PRIMARY = 0; + const SECONDARY = 1; + const TERTIARY = 2; + const DEFAULT_STRENGTH = 2; + const QUATERNARY = 3; + const IDENTICAL = 15; + + const OFF = 16; + const ON = 17; + + const SHIFTED = 20; + const NON_IGNORABLE = 21; + + const LOWER_FIRST = 24; + const UPPER_FIRST = 25; + + /* Sorting options */ + const SORT_REGULAR = 0; + const SORT_NUMERIC = 2; + const SORT_STRING = 1; + + /** + * @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en") + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + */ + public function __construct(?string $locale) + { + if ('en' !== $locale && null !== $locale) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'locale', $locale, 'Only the locale "en" is supported'); + } + } + + /** + * Static constructor. + * + * @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en") + * + * @return static + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + */ + public static function create(?string $locale) + { + return new static($locale); + } + + /** + * Sort array maintaining index association. + * + * @param array &$array Input array + * @param int $sortFlag Flags for sorting, can be one of the following: + * Collator::SORT_REGULAR - compare items normally (don't change types) + * Collator::SORT_NUMERIC - compare items numerically + * Collator::SORT_STRING - compare items as strings + * + * @return bool True on success or false on failure + */ + public function asort(array &$array, int $sortFlag = self::SORT_REGULAR) + { + $intlToPlainFlagMap = [ + self::SORT_REGULAR => SORT_REGULAR, + self::SORT_NUMERIC => SORT_NUMERIC, + self::SORT_STRING => SORT_STRING, + ]; + + $plainSortFlag = isset($intlToPlainFlagMap[$sortFlag]) ? $intlToPlainFlagMap[$sortFlag] : self::SORT_REGULAR; + + return asort($array, $plainSortFlag); + } + + /** + * Not supported. Compare two Unicode strings. + * + * @param string $str1 The first string to compare + * @param string $str2 The second string to compare + * + * @return bool|int Return the comparison result or false on failure: + * 1 if $str1 is greater than $str2 + * 0 if $str1 is equal than $str2 + * -1 if $str1 is less than $str2 + * + * @see https://php.net/collator.compare + * + * @throws MethodNotImplementedException + */ + public function compare(string $str1, string $str2) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Get a value of an integer collator attribute. + * + * @param int $attr An attribute specifier, one of the attribute constants + * + * @return bool|int The attribute value on success or false on error + * + * @see https://php.net/collator.getattribute + * + * @throws MethodNotImplementedException + */ + public function getAttribute(int $attr) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns collator's last error code. Always returns the U_ZERO_ERROR class constant value. + * + * @return int The error code from last collator call + */ + public function getErrorCode() + { + return IntlGlobals::U_ZERO_ERROR; + } + + /** + * Returns collator's last error message. Always returns the U_ZERO_ERROR_MESSAGE class constant value. + * + * @return string The error message from last collator call + */ + public function getErrorMessage() + { + return 'U_ZERO_ERROR'; + } + + /** + * Returns the collator's locale. + * + * @param int $type Not supported. The locale name type to return (Locale::VALID_LOCALE or Locale::ACTUAL_LOCALE) + * + * @return string The locale used to create the collator. Currently always + * returns "en". + */ + public function getLocale(int $type = Locale::ACTUAL_LOCALE) + { + return 'en'; + } + + /** + * Not supported. Get sorting key for a string. + * + * @param string $string The string to produce the key from + * + * @return string The collation key for $string + * + * @see https://php.net/collator.getsortkey + * + * @throws MethodNotImplementedException + */ + public function getSortKey(string $string) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Get current collator's strength. + * + * @return bool|int The current collator's strength or false on failure + * + * @see https://php.net/collator.getstrength + * + * @throws MethodNotImplementedException + */ + public function getStrength() + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Set a collator's attribute. + * + * @param int $attr An attribute specifier, one of the attribute constants + * @param int $val The attribute value, one of the attribute value constants + * + * @return bool True on success or false on failure + * + * @see https://php.net/collator.setattribute + * + * @throws MethodNotImplementedException + */ + public function setAttribute(int $attr, int $val) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Set the collator's strength. + * + * @param int $strength Strength to set, possible values: + * Collator::PRIMARY + * Collator::SECONDARY + * Collator::TERTIARY + * Collator::QUATERNARY + * Collator::IDENTICAL + * Collator::DEFAULT + * + * @return bool True on success or false on failure + * + * @see https://php.net/collator.setstrength + * + * @throws MethodNotImplementedException + */ + public function setStrength(int $strength) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Sort array using specified collator and sort keys. + * + * @param array &$arr Array of strings to sort + * + * @return bool True on success or false on failure + * + * @see https://php.net/collator.sortwithsortkeys + * + * @throws MethodNotImplementedException + */ + public function sortWithSortKeys(array &$arr) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Sort array using specified collator. + * + * @param array &$arr Array of string to sort + * @param int $sortFlag Optional sorting type, one of the following: + * Collator::SORT_REGULAR + * Collator::SORT_NUMERIC + * Collator::SORT_STRING + * + * @return bool True on success or false on failure + * + * @see https://php.net/collator.sort + * + * @throws MethodNotImplementedException + */ + public function sort(array &$arr, int $sortFlag = self::SORT_REGULAR) + { + throw new MethodNotImplementedException(__METHOD__); + } +} diff --git a/vendor/symfony/intl/Countries.php b/vendor/symfony/intl/Countries.php new file mode 100644 index 0000000..f3fae27 --- /dev/null +++ b/vendor/symfony/intl/Countries.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +use Symfony\Component\Intl\Exception\MissingResourceException; + +/** + * Gives access to region-related ICU data. + * + * @author Bernhard Schussek + * @author Roland Franssen + */ +final class Countries extends ResourceBundle +{ + /** + * Returns all available countries. + * + * Countries are returned as uppercase ISO 3166 two-letter country codes. + * + * A full table of ISO 3166 country codes can be found here: + * https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes + * + * This list only contains "officially assigned ISO 3166-1 alpha-2" country codes. + * + * @return string[] an array of canonical ISO 3166 alpha-2 country codes + */ + public static function getCountryCodes(): array + { + return self::readEntry(['Regions'], 'meta'); + } + + /** + * Returns all available countries (3 letters). + * + * Countries are returned as uppercase ISO 3166 three-letter country codes. + * + * This list only contains "officially assigned ISO 3166-1 alpha-3" country codes. + * + * @return string[] an array of canonical ISO 3166 alpha-3 country codes + */ + public static function getAlpha3Codes(): array + { + return self::readEntry(['Alpha2ToAlpha3'], 'meta'); + } + + public static function getAlpha3Code(string $alpha2Code): string + { + return self::readEntry(['Alpha2ToAlpha3', $alpha2Code], 'meta'); + } + + public static function getAlpha2Code(string $alpha3Code): string + { + return self::readEntry(['Alpha3ToAlpha2', $alpha3Code], 'meta'); + } + + public static function exists(string $alpha2Code): bool + { + try { + self::readEntry(['Names', $alpha2Code]); + + return true; + } catch (MissingResourceException $e) { + return false; + } + } + + public static function alpha3CodeExists(string $alpha3Code): bool + { + try { + self::getAlpha2Code($alpha3Code); + + return true; + } catch (MissingResourceException $e) { + return false; + } + } + + /** + * Gets the country name from its alpha2 code. + * + * @throws MissingResourceException if the country code does not exist + */ + public static function getName(string $country, string $displayLocale = null): string + { + return self::readEntry(['Names', $country], $displayLocale); + } + + /** + * Gets the country name from its alpha3 code. + * + * @throws MissingResourceException if the country code does not exist + */ + public static function getAlpha3Name(string $alpha3Code, string $displayLocale = null): string + { + return self::getName(self::getAlpha2Code($alpha3Code), $displayLocale); + } + + /** + * Gets the list of country names indexed with alpha2 codes as keys. + * + * @return string[] + */ + public static function getNames($displayLocale = null): array + { + return self::asort(self::readEntry(['Names'], $displayLocale), $displayLocale); + } + + /** + * Gets the list of country names indexed with alpha3 codes as keys. + * + * Same as method getNames, but with alpha3 codes instead of alpha2 codes as keys. + * + * @return string[] + */ + public static function getAlpha3Names($displayLocale = null): array + { + $alpha2Names = self::getNames($displayLocale); + $alpha3Names = []; + foreach ($alpha2Names as $alpha2Code => $name) { + $alpha3Names[self::getAlpha3Code($alpha2Code)] = $name; + } + + return $alpha3Names; + } + + protected static function getPath(): string + { + return Intl::getDataDirectory().'/'.Intl::REGION_DIR; + } +} diff --git a/vendor/symfony/intl/Currencies.php b/vendor/symfony/intl/Currencies.php new file mode 100644 index 0000000..c155c8f --- /dev/null +++ b/vendor/symfony/intl/Currencies.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +use Symfony\Component\Intl\Exception\MissingResourceException; + +/** + * Gives access to currency-related ICU data. + * + * @author Bernhard Schussek + * @author Roland Franssen + */ +final class Currencies extends ResourceBundle +{ + private const INDEX_SYMBOL = 0; + private const INDEX_NAME = 1; + private const INDEX_FRACTION_DIGITS = 0; + private const INDEX_ROUNDING_INCREMENT = 1; + + /** + * @return string[] + */ + public static function getCurrencyCodes(): array + { + return self::readEntry(['Currencies'], 'meta'); + } + + public static function exists(string $currency): bool + { + try { + self::readEntry(['Names', $currency, self::INDEX_NAME]); + + return true; + } catch (MissingResourceException $e) { + return false; + } + } + + /** + * @throws MissingResourceException if the currency code does not exist + */ + public static function getName(string $currency, string $displayLocale = null): string + { + return self::readEntry(['Names', $currency, self::INDEX_NAME], $displayLocale); + } + + /** + * @return string[] + */ + public static function getNames(string $displayLocale = null): array + { + // ==================================================================== + // For reference: It is NOT possible to return names indexed by + // numeric code here, because some numeric codes map to multiple + // 3-letter codes (e.g. 32 => "ARA", "ARP", "ARS") + // ==================================================================== + + $names = self::readEntry(['Names'], $displayLocale); + + if ($names instanceof \Traversable) { + $names = iterator_to_array($names); + } + + array_walk($names, function (&$value) { + $value = $value[self::INDEX_NAME]; + }); + + return self::asort($names, $displayLocale); + } + + /** + * @throws MissingResourceException if the currency code does not exist + */ + public static function getSymbol(string $currency, string $displayLocale = null): string + { + return self::readEntry(['Names', $currency, self::INDEX_SYMBOL], $displayLocale); + } + + public static function getFractionDigits(string $currency): int + { + try { + return self::readEntry(['Meta', $currency, self::INDEX_FRACTION_DIGITS], 'meta'); + } catch (MissingResourceException $e) { + return self::readEntry(['Meta', 'DEFAULT', self::INDEX_FRACTION_DIGITS], 'meta'); + } + } + + /** + * @return float|int + */ + public static function getRoundingIncrement(string $currency) + { + try { + return self::readEntry(['Meta', $currency, self::INDEX_ROUNDING_INCREMENT], 'meta'); + } catch (MissingResourceException $e) { + return self::readEntry(['Meta', 'DEFAULT', self::INDEX_ROUNDING_INCREMENT], 'meta'); + } + } + + /** + * @throws MissingResourceException if the currency code has no numeric code + */ + public static function getNumericCode(string $currency): int + { + return self::readEntry(['Alpha3ToNumeric', $currency], 'meta'); + } + + /** + * @throws MissingResourceException if the numeric code does not exist + */ + public static function forNumericCode(int $numericCode): array + { + return self::readEntry(['NumericToAlpha3', (string) $numericCode], 'meta'); + } + + protected static function getPath(): string + { + return Intl::getDataDirectory().'/'.Intl::CURRENCY_DIR; + } +} diff --git a/vendor/symfony/intl/Data/Bundle/Compiler/BundleCompilerInterface.php b/vendor/symfony/intl/Data/Bundle/Compiler/BundleCompilerInterface.php new file mode 100644 index 0000000..02dd429 --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Compiler/BundleCompilerInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Compiler; + +/** + * Compiles a resource bundle. + * + * @author Bernhard Schussek + * + * @internal + */ +interface BundleCompilerInterface +{ + /** + * Compiles a resource bundle at the given source to the given target + * directory. + */ + public function compile(string $sourcePath, string $targetDir); +} diff --git a/vendor/symfony/intl/Data/Bundle/Compiler/GenrbCompiler.php b/vendor/symfony/intl/Data/Bundle/Compiler/GenrbCompiler.php new file mode 100644 index 0000000..31d7fb9 --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Compiler/GenrbCompiler.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Compiler; + +use Symfony\Component\Intl\Exception\RuntimeException; + +/** + * Compiles .txt resource bundles to binary .res files. + * + * @author Bernhard Schussek + * + * @internal + */ +class GenrbCompiler implements BundleCompilerInterface +{ + private $genrb; + + /** + * Creates a new compiler based on the "genrb" executable. + * + * @param string $genrb Optional. The path to the "genrb" executable + * @param string $envVars Optional. Environment variables to be loaded when running "genrb". + * + * @throws RuntimeException if the "genrb" cannot be found + */ + public function __construct(string $genrb = 'genrb', string $envVars = '') + { + exec('which '.$genrb, $output, $status); + + if (0 !== $status) { + throw new RuntimeException(sprintf('The command "%s" is not installed.', $genrb)); + } + + $this->genrb = ($envVars ? $envVars.' ' : '').$genrb; + } + + /** + * {@inheritdoc} + */ + public function compile(string $sourcePath, string $targetDir) + { + if (is_dir($sourcePath)) { + $sourcePath .= '/*.txt'; + } + + exec($this->genrb.' --quiet -e UTF-8 -d '.$targetDir.' '.$sourcePath, $output, $status); + + if (0 !== $status) { + throw new RuntimeException(sprintf('genrb failed with status %d while compiling "%s" to "%s".', $status, $sourcePath, $targetDir)); + } + } +} diff --git a/vendor/symfony/intl/Data/Bundle/Reader/BufferedBundleReader.php b/vendor/symfony/intl/Data/Bundle/Reader/BufferedBundleReader.php new file mode 100644 index 0000000..4b14512 --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Reader/BufferedBundleReader.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Reader; + +use Symfony\Component\Intl\Data\Util\RingBuffer; + +/** + * @author Bernhard Schussek + * + * @internal + */ +class BufferedBundleReader implements BundleReaderInterface +{ + private $reader; + private $buffer; + + /** + * Buffers a given reader. + * + * @param int $bufferSize The number of entries to store in the buffer + */ + public function __construct(BundleReaderInterface $reader, int $bufferSize) + { + $this->reader = $reader; + $this->buffer = new RingBuffer($bufferSize); + } + + /** + * {@inheritdoc} + */ + public function read(string $path, string $locale) + { + $hash = $path.'//'.$locale; + + if (!isset($this->buffer[$hash])) { + $this->buffer[$hash] = $this->reader->read($path, $locale); + } + + return $this->buffer[$hash]; + } +} diff --git a/vendor/symfony/intl/Data/Bundle/Reader/BundleEntryReader.php b/vendor/symfony/intl/Data/Bundle/Reader/BundleEntryReader.php new file mode 100644 index 0000000..8b7870d --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Reader/BundleEntryReader.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Reader; + +use Symfony\Component\Intl\Data\Util\RecursiveArrayAccess; +use Symfony\Component\Intl\Exception\MissingResourceException; +use Symfony\Component\Intl\Exception\OutOfBoundsException; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; +use Symfony\Component\Intl\Locale; + +/** + * Default implementation of {@link BundleEntryReaderInterface}. + * + * @author Bernhard Schussek + * + * @see BundleEntryReaderInterface + * + * @internal + */ +class BundleEntryReader implements BundleEntryReaderInterface +{ + private $reader; + + /** + * A mapping of locale aliases to locales. + */ + private $localeAliases = []; + + /** + * Creates an entry reader based on the given resource bundle reader. + */ + public function __construct(BundleReaderInterface $reader) + { + $this->reader = $reader; + } + + /** + * Stores a mapping of locale aliases to locales. + * + * This mapping is used when reading entries and merging them with their + * fallback locales. If an entry is read for a locale alias (e.g. "mo") + * that points to a locale with a fallback locale ("ro_MD"), the reader + * can continue at the correct fallback locale ("ro"). + * + * @param array $localeAliases A mapping of locale aliases to locales + */ + public function setLocaleAliases(array $localeAliases) + { + $this->localeAliases = $localeAliases; + } + + /** + * {@inheritdoc} + */ + public function read(string $path, string $locale) + { + return $this->reader->read($path, $locale); + } + + /** + * {@inheritdoc} + */ + public function readEntry(string $path, string $locale, array $indices, bool $fallback = true) + { + $entry = null; + $isMultiValued = false; + $readSucceeded = false; + $exception = null; + $currentLocale = $locale; + $testedLocales = []; + + while (null !== $currentLocale) { + // Resolve any aliases to their target locales + if (isset($this->localeAliases[$currentLocale])) { + $currentLocale = $this->localeAliases[$currentLocale]; + } + + try { + $data = $this->reader->read($path, $currentLocale); + $currentEntry = RecursiveArrayAccess::get($data, $indices); + $readSucceeded = true; + + $isCurrentTraversable = $currentEntry instanceof \Traversable; + $isCurrentMultiValued = $isCurrentTraversable || \is_array($currentEntry); + + // Return immediately if fallback is disabled or we are dealing + // with a scalar non-null entry + if (!$fallback || (!$isCurrentMultiValued && null !== $currentEntry)) { + return $currentEntry; + } + + // ========================================================= + // Fallback is enabled, entry is either multi-valued or NULL + // ========================================================= + + // If entry is multi-valued, convert to array + if ($isCurrentTraversable) { + $currentEntry = iterator_to_array($currentEntry); + } + + // If previously read entry was multi-valued too, merge them + if ($isCurrentMultiValued && $isMultiValued) { + $currentEntry = array_merge($currentEntry, $entry); + } + + // Keep the previous entry if the current entry is NULL + if (null !== $currentEntry) { + $entry = $currentEntry; + } + + // If this or the previous entry was multi-valued, we are dealing + // with a merged, multi-valued entry now + $isMultiValued = $isMultiValued || $isCurrentMultiValued; + } catch (ResourceBundleNotFoundException $e) { + // Continue if there is a fallback locale for the current + // locale + $exception = $e; + } catch (OutOfBoundsException $e) { + // Remember exception and rethrow if we cannot find anything in + // the fallback locales either + $exception = $e; + } + + // Remember which locales we tried + $testedLocales[] = $currentLocale; + + // Check whether fallback is allowed + if (!$fallback) { + break; + } + + // Then determine fallback locale + $currentLocale = Locale::getFallback($currentLocale); + } + + // Multi-valued entry was merged + if ($isMultiValued) { + return $entry; + } + + // Entry is still NULL, but no read error occurred + if ($readSucceeded) { + return $entry; + } + + // Entry is still NULL, read error occurred. Throw an exception + // containing the detailed path and locale + $errorMessage = sprintf( + 'Couldn\'t read the indices [%s] for the locale "%s" in "%s".', + implode('][', $indices), + $locale, + $path + ); + + // Append fallback locales, if any + if (\count($testedLocales) > 1) { + // Remove original locale + array_shift($testedLocales); + + $errorMessage .= sprintf( + ' The indices also couldn\'t be found for the fallback locale(s) "%s".', + implode('", "', $testedLocales) + ); + } + + throw new MissingResourceException($errorMessage, 0, $exception); + } +} diff --git a/vendor/symfony/intl/Data/Bundle/Reader/BundleEntryReaderInterface.php b/vendor/symfony/intl/Data/Bundle/Reader/BundleEntryReaderInterface.php new file mode 100644 index 0000000..c4a0f56 --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Reader/BundleEntryReaderInterface.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Reader; + +use Symfony\Component\Intl\Exception\MissingResourceException; + +/** + * Reads individual entries of a resource file. + * + * @author Bernhard Schussek + * + * @internal + */ +interface BundleEntryReaderInterface extends BundleReaderInterface +{ + /** + * Reads an entry from a resource bundle. + * + * An entry can be selected from the resource bundle by passing the path + * to that entry in the bundle. For example, if the bundle is structured + * like this: + * + * TopLevel + * NestedLevel + * Entry: Value + * + * Then the value can be read by calling: + * + * $reader->readEntry('...', 'en', ['TopLevel', 'NestedLevel', 'Entry']); + * + * @param string $path The path to the resource bundle + * @param string[] $indices The indices to read from the bundle + * @param bool $fallback Whether to merge the value with the value from + * the fallback locale (e.g. "en" for "en_GB"). + * Only applicable if the result is multivalued + * (i.e. array or \ArrayAccess) or cannot be found + * in the requested locale. + * + * @return mixed returns an array or {@link \ArrayAccess} instance for + * complex data and a scalar value for simple data + * + * @throws MissingResourceException If the indices cannot be accessed + */ + public function readEntry(string $path, string $locale, array $indices, bool $fallback = true); +} diff --git a/vendor/symfony/intl/Data/Bundle/Reader/BundleReaderInterface.php b/vendor/symfony/intl/Data/Bundle/Reader/BundleReaderInterface.php new file mode 100644 index 0000000..545be46 --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Reader/BundleReaderInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Reader; + +/** + * Reads resource bundle files. + * + * @author Bernhard Schussek + * + * @internal + */ +interface BundleReaderInterface +{ + /** + * @return mixed returns an array or {@link \ArrayAccess} instance for + * complex data, a scalar value otherwise + */ + public function read(string $path, string $locale); +} diff --git a/vendor/symfony/intl/Data/Bundle/Reader/IntlBundleReader.php b/vendor/symfony/intl/Data/Bundle/Reader/IntlBundleReader.php new file mode 100644 index 0000000..bf9f92f --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Reader/IntlBundleReader.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Reader; + +use Symfony\Component\Intl\Data\Util\ArrayAccessibleResourceBundle; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; + +/** + * Reads binary .res resource bundles. + * + * @author Bernhard Schussek + * + * @internal + */ +class IntlBundleReader implements BundleReaderInterface +{ + /** + * {@inheritdoc} + */ + public function read(string $path, string $locale) + { + // Point for future extension: Modify this class so that it works also + // if the \ResourceBundle class is not available. + try { + // Never enable fallback. We want to know if a bundle cannot be found + $bundle = new \ResourceBundle($locale, $path, false); + } catch (\Exception $e) { + $bundle = null; + } + + // The bundle is NULL if the path does not look like a resource bundle + // (i.e. contain a bunch of *.res files) + if (null === $bundle) { + throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s/%s.res" could not be found.', $path, $locale)); + } + + // Other possible errors are U_USING_FALLBACK_WARNING and U_ZERO_ERROR, + // which are OK for us. + return new ArrayAccessibleResourceBundle($bundle); + } +} diff --git a/vendor/symfony/intl/Data/Bundle/Reader/JsonBundleReader.php b/vendor/symfony/intl/Data/Bundle/Reader/JsonBundleReader.php new file mode 100644 index 0000000..36b578d --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Reader/JsonBundleReader.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Reader; + +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; +use Symfony\Component\Intl\Exception\RuntimeException; + +/** + * Reads .json resource bundles. + * + * @author Bernhard Schussek + * + * @internal + */ +class JsonBundleReader implements BundleReaderInterface +{ + /** + * {@inheritdoc} + */ + public function read(string $path, string $locale) + { + $fileName = $path.'/'.$locale.'.json'; + + // prevent directory traversal attacks + if (\dirname($fileName) !== $path) { + throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s" does not exist.', $fileName)); + } + + if (!file_exists($fileName)) { + throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s" does not exist.', $fileName)); + } + + if (!is_file($fileName)) { + throw new RuntimeException(sprintf('The resource bundle "%s" is not a file.', $fileName)); + } + + $data = json_decode(file_get_contents($fileName), true); + + if (null === $data) { + throw new RuntimeException(sprintf('The resource bundle "%s" contains invalid JSON: ', $fileName).json_last_error_msg()); + } + + return $data; + } +} diff --git a/vendor/symfony/intl/Data/Bundle/Reader/PhpBundleReader.php b/vendor/symfony/intl/Data/Bundle/Reader/PhpBundleReader.php new file mode 100644 index 0000000..432b81b --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Reader/PhpBundleReader.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Reader; + +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; +use Symfony\Component\Intl\Exception\RuntimeException; + +/** + * Reads .php resource bundles. + * + * @author Bernhard Schussek + * + * @internal + */ +class PhpBundleReader implements BundleReaderInterface +{ + /** + * {@inheritdoc} + */ + public function read(string $path, string $locale) + { + $fileName = $path.'/'.$locale.'.php'; + + // prevent directory traversal attacks + if (\dirname($fileName) !== $path) { + throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s" does not exist.', $fileName)); + } + + if (!file_exists($fileName)) { + throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s/%s.php" does not exist.', $path, $locale)); + } + + if (!is_file($fileName)) { + throw new RuntimeException(sprintf('The resource bundle "%s/%s.php" is not a file.', $path, $locale)); + } + + return include $fileName; + } +} diff --git a/vendor/symfony/intl/Data/Bundle/Writer/BundleWriterInterface.php b/vendor/symfony/intl/Data/Bundle/Writer/BundleWriterInterface.php new file mode 100644 index 0000000..80ae789 --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Writer/BundleWriterInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Writer; + +/** + * Writes resource bundle files. + * + * @author Bernhard Schussek + * + * @internal + */ +interface BundleWriterInterface +{ + /** + * Writes data to a resource bundle. + * + * @param mixed $data The data to write + */ + public function write(string $path, string $locale, $data); +} diff --git a/vendor/symfony/intl/Data/Bundle/Writer/JsonBundleWriter.php b/vendor/symfony/intl/Data/Bundle/Writer/JsonBundleWriter.php new file mode 100644 index 0000000..0e114f3 --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Writer/JsonBundleWriter.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Writer; + +/** + * Writes .json resource bundles. + * + * @author Bernhard Schussek + * + * @internal + */ +class JsonBundleWriter implements BundleWriterInterface +{ + /** + * {@inheritdoc} + */ + public function write(string $path, string $locale, $data) + { + if ($data instanceof \Traversable) { + $data = iterator_to_array($data); + } + + array_walk_recursive($data, function (&$value) { + if ($value instanceof \Traversable) { + $value = iterator_to_array($value); + } + }); + + $contents = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)."\n"; + + file_put_contents($path.'/'.$locale.'.json', $contents); + } +} diff --git a/vendor/symfony/intl/Data/Bundle/Writer/PhpBundleWriter.php b/vendor/symfony/intl/Data/Bundle/Writer/PhpBundleWriter.php new file mode 100644 index 0000000..dc071ed --- /dev/null +++ b/vendor/symfony/intl/Data/Bundle/Writer/PhpBundleWriter.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Writer; + +/** + * Writes .php resource bundles. + * + * @author Bernhard Schussek + * + * @internal + */ +class PhpBundleWriter implements BundleWriterInterface +{ + /** + * {@inheritdoc} + */ + public function write(string $path, string $locale, $data) + { + $template = <<<'TEMPLATE' + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Bundle\Writer; + +/** + * Writes .txt resource bundles. + * + * The resulting files can be converted to binary .res files using a + * {@link \Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompilerInterface} + * implementation. + * + * @author Bernhard Schussek + * + * @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt + * + * @internal + */ +class TextBundleWriter implements BundleWriterInterface +{ + /** + * {@inheritdoc} + */ + public function write(string $path, string $locale, $data, bool $fallback = true) + { + $file = fopen($path.'/'.$locale.'.txt', 'w'); + + $this->writeResourceBundle($file, $locale, $data, $fallback); + + fclose($file); + } + + /** + * Writes a "resourceBundle" node. + * + * @param resource $file The file handle to write to + * @param mixed $value The value of the node + * + * @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt + */ + private function writeResourceBundle($file, string $bundleName, $value, bool $fallback) + { + fwrite($file, $bundleName); + + $this->writeTable($file, $value, 0, $fallback); + + fwrite($file, "\n"); + } + + /** + * Writes a "resource" node. + * + * @param resource $file The file handle to write to + * @param mixed $value The value of the node + * + * @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt + */ + private function writeResource($file, $value, int $indentation, bool $requireBraces = true) + { + if (\is_int($value)) { + $this->writeInteger($file, $value); + + return; + } + + if ($value instanceof \Traversable) { + $value = iterator_to_array($value); + } + + if (\is_array($value)) { + $intValues = \count($value) === \count(array_filter($value, 'is_int')); + + $keys = array_keys($value); + + // check that the keys are 0-indexed and ascending + $intKeys = $keys === range(0, \count($keys) - 1); + + if ($intValues && $intKeys) { + $this->writeIntVector($file, $value, $indentation); + + return; + } + + if ($intKeys) { + $this->writeArray($file, $value, $indentation); + + return; + } + + $this->writeTable($file, $value, $indentation); + + return; + } + + if (\is_bool($value)) { + $value = $value ? 'true' : 'false'; + } + + $this->writeString($file, (string) $value, $requireBraces); + } + + /** + * Writes an "integer" node. + * + * @param resource $file The file handle to write to + * + * @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt + */ + private function writeInteger($file, int $value) + { + fprintf($file, ':int{%d}', $value); + } + + /** + * Writes an "intvector" node. + * + * @param resource $file The file handle to write to + * + * @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt + */ + private function writeIntVector($file, array $value, int $indentation) + { + fwrite($file, ":intvector{\n"); + + foreach ($value as $int) { + fprintf($file, "%s%d,\n", str_repeat(' ', $indentation + 1), $int); + } + + fprintf($file, '%s}', str_repeat(' ', $indentation)); + } + + /** + * Writes a "string" node. + * + * @param resource $file The file handle to write to + * + * @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt + */ + private function writeString($file, string $value, bool $requireBraces = true) + { + if ($requireBraces) { + fprintf($file, '{"%s"}', $value); + + return; + } + + fprintf($file, '"%s"', $value); + } + + /** + * Writes an "array" node. + * + * @param resource $file The file handle to write to + * + * @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt + */ + private function writeArray($file, array $value, int $indentation) + { + fwrite($file, "{\n"); + + foreach ($value as $entry) { + fwrite($file, str_repeat(' ', $indentation + 1)); + + $this->writeResource($file, $entry, $indentation + 1, false); + + fwrite($file, ",\n"); + } + + fprintf($file, '%s}', str_repeat(' ', $indentation)); + } + + /** + * Writes a "table" node. + * + * @param resource $file The file handle to write to + */ + private function writeTable($file, iterable $value, int $indentation, bool $fallback = true) + { + if (!$fallback) { + fwrite($file, ':table(nofallback)'); + } + + fwrite($file, "{\n"); + + foreach ($value as $key => $entry) { + fwrite($file, str_repeat(' ', $indentation + 1)); + + // escape colons, otherwise they are interpreted as resource types + if (false !== strpos($key, ':') || false !== strpos($key, ' ')) { + $key = '"'.$key.'"'; + } + + fwrite($file, $key); + + $this->writeResource($file, $entry, $indentation + 1); + + fwrite($file, "\n"); + } + + fprintf($file, '%s}', str_repeat(' ', $indentation)); + } +} diff --git a/vendor/symfony/intl/Data/Generator/AbstractDataGenerator.php b/vendor/symfony/intl/Data/Generator/AbstractDataGenerator.php new file mode 100644 index 0000000..411b0a9 --- /dev/null +++ b/vendor/symfony/intl/Data/Generator/AbstractDataGenerator.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Generator; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface; +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReader; +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface; +use Symfony\Component\Intl\Data\Bundle\Reader\IntlBundleReader; +use Symfony\Component\Intl\Data\Util\LocaleScanner; + +/** + * The rule for compiling the currency bundle. + * + * @author Bernhard Schussek + * + * @internal + */ +abstract class AbstractDataGenerator +{ + private $compiler; + private $dirName; + + public function __construct(BundleCompilerInterface $compiler, string $dirName) + { + $this->compiler = $compiler; + $this->dirName = $dirName; + } + + public function generateData(GeneratorConfig $config) + { + $filesystem = new Filesystem(); + $localeScanner = new LocaleScanner(); + $reader = new BundleEntryReader(new IntlBundleReader()); + + $writers = $config->getBundleWriters(); + $tempDir = sys_get_temp_dir().'/icu-data-'.$this->dirName; + + // Prepare filesystem directories + foreach ($writers as $targetDir => $writer) { + $filesystem->remove($targetDir.'/'.$this->dirName); + $filesystem->mkdir($targetDir.'/'.$this->dirName); + } + + $filesystem->remove($tempDir); + $filesystem->mkdir($tempDir); + + $locales = $this->scanLocales($localeScanner, $config->getSourceDir()); + + $this->compileTemporaryBundles($this->compiler, $config->getSourceDir(), $tempDir); + + $this->preGenerate(); + + foreach ($locales as $locale) { + $localeData = $this->generateDataForLocale($reader, $tempDir, $locale); + + if (null !== $localeData) { + foreach ($writers as $targetDir => $writer) { + $writer->write($targetDir.'/'.$this->dirName, $locale, $localeData); + } + } + } + + $rootData = $this->generateDataForRoot($reader, $tempDir); + + if (null !== $rootData) { + foreach ($writers as $targetDir => $writer) { + $writer->write($targetDir.'/'.$this->dirName, 'root', $rootData); + } + } + + $metaData = $this->generateDataForMeta($reader, $tempDir); + + if (null !== $metaData) { + foreach ($writers as $targetDir => $writer) { + $writer->write($targetDir.'/'.$this->dirName, 'meta', $metaData); + } + } + + // Clean up + $filesystem->remove($tempDir); + } + + /** + * @return string[] + */ + abstract protected function scanLocales(LocaleScanner $scanner, string $sourceDir): array; + + abstract protected function compileTemporaryBundles(BundleCompilerInterface $compiler, string $sourceDir, string $tempDir); + + abstract protected function preGenerate(); + + abstract protected function generateDataForLocale(BundleEntryReaderInterface $reader, string $tempDir, string $displayLocale): ?array; + + abstract protected function generateDataForRoot(BundleEntryReaderInterface $reader, string $tempDir): ?array; + + abstract protected function generateDataForMeta(BundleEntryReaderInterface $reader, string $tempDir): ?array; +} diff --git a/vendor/symfony/intl/Data/Generator/CurrencyDataGenerator.php b/vendor/symfony/intl/Data/Generator/CurrencyDataGenerator.php new file mode 100644 index 0000000..e7c28bc --- /dev/null +++ b/vendor/symfony/intl/Data/Generator/CurrencyDataGenerator.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Generator; + +use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface; +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface; +use Symfony\Component\Intl\Data\Util\ArrayAccessibleResourceBundle; +use Symfony\Component\Intl\Data\Util\LocaleScanner; + +/** + * The rule for compiling the currency bundle. + * + * @author Bernhard Schussek + * + * @internal + */ +class CurrencyDataGenerator extends AbstractDataGenerator +{ + private static $blacklist = [ + 'XBA' => true, // European Composite Unit + 'XBB' => true, // European Monetary Unit + 'XBC' => true, // European Unit of Account (XBC) + 'XBD' => true, // European Unit of Account (XBD) + 'XUA' => true, // ADB Unit of Account + 'XAU' => true, // Gold + 'XAG' => true, // Silver + 'XPT' => true, // Platinum + 'XPD' => true, // Palladium + 'XSU' => true, // Sucre + 'XDR' => true, // Special Drawing Rights + 'XTS' => true, // Testing Currency Code + 'XXX' => true, // Unknown Currency + ]; + + /** + * Collects all available currency codes. + * + * @var string[] + */ + private $currencyCodes = []; + + /** + * {@inheritdoc} + */ + protected function scanLocales(LocaleScanner $scanner, string $sourceDir): array + { + return $scanner->scanLocales($sourceDir.'/curr'); + } + + /** + * {@inheritdoc} + */ + protected function compileTemporaryBundles(BundleCompilerInterface $compiler, string $sourceDir, string $tempDir) + { + $compiler->compile($sourceDir.'/curr', $tempDir); + $compiler->compile($sourceDir.'/misc/currencyNumericCodes.txt', $tempDir); + } + + /** + * {@inheritdoc} + */ + protected function preGenerate() + { + $this->currencyCodes = []; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForLocale(BundleEntryReaderInterface $reader, string $tempDir, string $displayLocale): ?array + { + $localeBundle = $reader->read($tempDir, $displayLocale); + + if (isset($localeBundle['Currencies']) && null !== $localeBundle['Currencies']) { + $data = [ + 'Names' => $this->generateSymbolNamePairs($localeBundle), + ]; + + $this->currencyCodes = array_merge($this->currencyCodes, array_keys($data['Names'])); + + return $data; + } + + return null; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForRoot(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + $rootBundle = $reader->read($tempDir, 'root'); + + return [ + 'Names' => $this->generateSymbolNamePairs($rootBundle), + ]; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForMeta(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + $supplementalDataBundle = $reader->read($tempDir, 'supplementalData'); + $numericCodesBundle = $reader->read($tempDir, 'currencyNumericCodes'); + + $this->currencyCodes = array_unique($this->currencyCodes); + + sort($this->currencyCodes); + + $data = [ + 'Currencies' => $this->currencyCodes, + 'Meta' => $this->generateCurrencyMeta($supplementalDataBundle), + 'Alpha3ToNumeric' => $this->generateAlpha3ToNumericMapping($numericCodesBundle, $this->currencyCodes), + ]; + + $data['NumericToAlpha3'] = $this->generateNumericToAlpha3Mapping($data['Alpha3ToNumeric']); + + return $data; + } + + private function generateSymbolNamePairs(ArrayAccessibleResourceBundle $rootBundle): array + { + $symbolNamePairs = iterator_to_array($rootBundle['Currencies']); + + // Remove unwanted currencies + $symbolNamePairs = array_diff_key($symbolNamePairs, self::$blacklist); + + return $symbolNamePairs; + } + + private function generateCurrencyMeta(ArrayAccessibleResourceBundle $supplementalDataBundle): array + { + // The metadata is already de-duplicated. It contains one key "DEFAULT" + // which is used for currencies that don't have dedicated entries. + return iterator_to_array($supplementalDataBundle['CurrencyMeta']); + } + + private function generateAlpha3ToNumericMapping(ArrayAccessibleResourceBundle $numericCodesBundle, array $currencyCodes): array + { + $alpha3ToNumericMapping = iterator_to_array($numericCodesBundle['codeMap']); + + asort($alpha3ToNumericMapping); + + // Filter unknown currencies (e.g. "AYM") + $alpha3ToNumericMapping = array_intersect_key($alpha3ToNumericMapping, array_flip($currencyCodes)); + + return $alpha3ToNumericMapping; + } + + private function generateNumericToAlpha3Mapping(array $alpha3ToNumericMapping): array + { + $numericToAlpha3Mapping = []; + + foreach ($alpha3ToNumericMapping as $alpha3 => $numeric) { + // Make sure that the mapping is stored as table and not as array + $numeric = (string) $numeric; + + if (!isset($numericToAlpha3Mapping[$numeric])) { + $numericToAlpha3Mapping[$numeric] = []; + } + + $numericToAlpha3Mapping[$numeric][] = $alpha3; + } + + return $numericToAlpha3Mapping; + } +} diff --git a/vendor/symfony/intl/Data/Generator/FallbackTrait.php b/vendor/symfony/intl/Data/Generator/FallbackTrait.php new file mode 100644 index 0000000..30709d9 --- /dev/null +++ b/vendor/symfony/intl/Data/Generator/FallbackTrait.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Generator; + +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface; +use Symfony\Component\Intl\Locale; + +/** + * @author Roland Franssen + * + * @internal + */ +trait FallbackTrait +{ + private $fallbackCache = []; + private $generatingFallback = false; + + /** + * @see AbstractDataGenerator::generateDataForLocale() + */ + abstract protected function generateDataForLocale(BundleEntryReaderInterface $reader, string $tempDir, string $displayLocale): ?array; + + /** + * @see AbstractDataGenerator::generateDataForRoot() + */ + abstract protected function generateDataForRoot(BundleEntryReaderInterface $reader, string $tempDir): ?array; + + private function generateFallbackData(BundleEntryReaderInterface $reader, string $tempDir, string $displayLocale): array + { + if (null === $fallback = Locale::getFallback($displayLocale)) { + return []; + } + + if (isset($this->fallbackCache[$fallback])) { + return $this->fallbackCache[$fallback]; + } + + $prevGeneratingFallback = $this->generatingFallback; + $this->generatingFallback = true; + + try { + $data = 'root' === $fallback ? $this->generateDataForRoot($reader, $tempDir) : $this->generateDataForLocale($reader, $tempDir, $fallback); + } finally { + $this->generatingFallback = $prevGeneratingFallback; + } + + return $this->fallbackCache[$fallback] = $data ?: []; + } +} diff --git a/vendor/symfony/intl/Data/Generator/GeneratorConfig.php b/vendor/symfony/intl/Data/Generator/GeneratorConfig.php new file mode 100644 index 0000000..ddf7db3 --- /dev/null +++ b/vendor/symfony/intl/Data/Generator/GeneratorConfig.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Generator; + +use Symfony\Component\Intl\Data\Bundle\Writer\BundleWriterInterface; + +/** + * Stores contextual information for resource bundle generation. + * + * @author Bernhard Schussek + * + * @internal + */ +class GeneratorConfig +{ + private $sourceDir; + private $icuVersion; + + /** + * @var BundleWriterInterface[] + */ + private $bundleWriters = []; + + public function __construct(string $sourceDir, string $icuVersion) + { + $this->sourceDir = $sourceDir; + $this->icuVersion = $icuVersion; + } + + /** + * Adds a writer to be used during the data conversion. + */ + public function addBundleWriter(string $targetDir, BundleWriterInterface $writer) + { + $this->bundleWriters[$targetDir] = $writer; + } + + /** + * Returns the writers indexed by their output directories. + * + * @return BundleWriterInterface[] + */ + public function getBundleWriters(): array + { + return $this->bundleWriters; + } + + /** + * Returns the directory where the source versions of the resource bundles + * are stored. + * + * @return string An absolute path to a directory + */ + public function getSourceDir(): string + { + return $this->sourceDir; + } + + /** + * Returns the ICU version of the bundles being converted. + * + * @return string The ICU version string + */ + public function getIcuVersion(): string + { + return $this->icuVersion; + } +} diff --git a/vendor/symfony/intl/Data/Generator/LanguageDataGenerator.php b/vendor/symfony/intl/Data/Generator/LanguageDataGenerator.php new file mode 100644 index 0000000..79328af --- /dev/null +++ b/vendor/symfony/intl/Data/Generator/LanguageDataGenerator.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Generator; + +use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface; +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface; +use Symfony\Component\Intl\Data\Util\ArrayAccessibleResourceBundle; +use Symfony\Component\Intl\Data\Util\LocaleScanner; +use Symfony\Component\Intl\Exception\RuntimeException; + +/** + * The rule for compiling the language bundle. + * + * @author Bernhard Schussek + * + * @internal + */ +class LanguageDataGenerator extends AbstractDataGenerator +{ + /** + * Source: https://iso639-3.sil.org/code_tables/639/data. + */ + private static $preferredAlpha2ToAlpha3Mapping = [ + 'ak' => 'aka', + 'ar' => 'ara', + 'ay' => 'aym', + 'az' => 'aze', + 'bo' => 'bod', + 'cr' => 'cre', + 'cs' => 'ces', + 'cy' => 'cym', + 'de' => 'deu', + 'dz' => 'dzo', + 'el' => 'ell', + 'et' => 'est', + 'eu' => 'eus', + 'fa' => 'fas', + 'ff' => 'ful', + 'fr' => 'fra', + 'gn' => 'grn', + 'hy' => 'hye', + 'hr' => 'hrv', + 'ik' => 'ipk', + 'is' => 'isl', + 'iu' => 'iku', + 'ka' => 'kat', + 'kr' => 'kau', + 'kg' => 'kon', + 'kv' => 'kom', + 'ku' => 'kur', + 'lv' => 'lav', + 'mg' => 'mlg', + 'mi' => 'mri', + 'mk' => 'mkd', + 'mn' => 'mon', + 'ms' => 'msa', + 'my' => 'mya', + 'nb' => 'nob', + 'ne' => 'nep', + 'nl' => 'nld', + 'oj' => 'oji', + 'om' => 'orm', + 'or' => 'ori', + 'ps' => 'pus', + 'qu' => 'que', + 'ro' => 'ron', + 'sc' => 'srd', + 'sk' => 'slk', + 'sq' => 'sqi', + 'sr' => 'srp', + 'sw' => 'swa', + 'uz' => 'uzb', + 'yi' => 'yid', + 'za' => 'zha', + 'zh' => 'zho', + ]; + private static $blacklist = [ + 'root' => true, // Absolute root language + 'mul' => true, // Multiple languages + 'mis' => true, // Uncoded language + 'und' => true, // Unknown language + 'zxx' => true, // No linguistic content + ]; + + /** + * Collects all available language codes. + * + * @var string[] + */ + private $languageCodes = []; + + /** + * {@inheritdoc} + */ + protected function scanLocales(LocaleScanner $scanner, string $sourceDir): array + { + return $scanner->scanLocales($sourceDir.'/lang'); + } + + /** + * {@inheritdoc} + */ + protected function compileTemporaryBundles(BundleCompilerInterface $compiler, string $sourceDir, string $tempDir) + { + $compiler->compile($sourceDir.'/lang', $tempDir); + $compiler->compile($sourceDir.'/misc/metadata.txt', $tempDir); + } + + /** + * {@inheritdoc} + */ + protected function preGenerate() + { + $this->languageCodes = []; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForLocale(BundleEntryReaderInterface $reader, string $tempDir, string $displayLocale): ?array + { + $localeBundle = $reader->read($tempDir, $displayLocale); + + // isset() on \ResourceBundle returns true even if the value is null + if (isset($localeBundle['Languages']) && null !== $localeBundle['Languages']) { + $names = []; + $localizedNames = []; + foreach (self::generateLanguageNames($localeBundle) as $language => $name) { + if (false === strpos($language, '_')) { + $this->languageCodes[] = $language; + $names[$language] = $name; + } else { + $localizedNames[$language] = $name; + } + } + $data = [ + 'Names' => $names, + 'LocalizedNames' => $localizedNames, + ]; + + return $data; + } + + return null; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForRoot(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + return null; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForMeta(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + $metadataBundle = $reader->read($tempDir, 'metadata'); + + $this->languageCodes = array_unique($this->languageCodes); + + sort($this->languageCodes); + + return [ + 'Languages' => $this->languageCodes, + 'Alpha3Languages' => $this->generateAlpha3Codes($this->languageCodes, $metadataBundle), + 'Alpha2ToAlpha3' => $this->generateAlpha2ToAlpha3Mapping($metadataBundle), + 'Alpha3ToAlpha2' => $this->generateAlpha3ToAlpha2Mapping($metadataBundle), + ]; + } + + private static function generateLanguageNames(ArrayAccessibleResourceBundle $localeBundle): array + { + return array_diff_key(iterator_to_array($localeBundle['Languages']), self::$blacklist); + } + + private function generateAlpha3Codes(array $languageCodes, ArrayAccessibleResourceBundle $metadataBundle): array + { + $alpha3Codes = array_flip(array_filter($languageCodes, static function (string $language): bool { + return 3 === \strlen($language); + })); + + foreach ($metadataBundle['alias']['language'] as $alias => $data) { + if (3 === \strlen($alias) && 'overlong' === $data['reason']) { + $alpha3Codes[$alias] = true; + } + } + + ksort($alpha3Codes); + + return array_keys($alpha3Codes); + } + + private function generateAlpha2ToAlpha3Mapping(ArrayAccessibleResourceBundle $metadataBundle): array + { + $aliases = iterator_to_array($metadataBundle['alias']['language']); + $alpha2ToAlpha3 = []; + + foreach ($aliases as $alias => $data) { + $language = $data['replacement']; + if (2 === \strlen($language) && 3 === \strlen($alias) && 'overlong' === $data['reason']) { + if (isset(self::$preferredAlpha2ToAlpha3Mapping[$language])) { + // Validate to prevent typos + if (!isset($aliases[self::$preferredAlpha2ToAlpha3Mapping[$language]])) { + throw new RuntimeException('The statically set three-letter mapping '.self::$preferredAlpha2ToAlpha3Mapping[$language].' for the language code '.$language.' seems to be invalid. Typo?'); + } + + $alpha3 = self::$preferredAlpha2ToAlpha3Mapping[$language]; + $alpha2 = $aliases[$alpha3]['replacement']; + + if ($language !== $alpha2) { + throw new RuntimeException('The statically set three-letter mapping '.$alpha3.' for the language code '.$language.' seems to be an alias for '.$alpha2.'. Wrong mapping?'); + } + + $alpha2ToAlpha3[$language] = $alpha3; + } elseif (isset($alpha2ToAlpha3[$language])) { + throw new RuntimeException('Multiple three-letter mappings exist for the language code '.$language.'. Please add one of them to the property $preferredAlpha2ToAlpha3Mapping.'); + } else { + $alpha2ToAlpha3[$language] = $alias; + } + } + } + + asort($alpha2ToAlpha3); + + return $alpha2ToAlpha3; + } + + private function generateAlpha3ToAlpha2Mapping(ArrayAccessibleResourceBundle $metadataBundle): array + { + $alpha3ToAlpha2 = []; + + foreach ($metadataBundle['alias']['language'] as $alias => $data) { + $language = $data['replacement']; + if (2 === \strlen($language) && 3 === \strlen($alias) && 'overlong' === $data['reason']) { + $alpha3ToAlpha2[$alias] = $language; + } + } + + asort($alpha3ToAlpha2); + + return $alpha3ToAlpha2; + } +} diff --git a/vendor/symfony/intl/Data/Generator/LocaleDataGenerator.php b/vendor/symfony/intl/Data/Generator/LocaleDataGenerator.php new file mode 100644 index 0000000..f6e9f67 --- /dev/null +++ b/vendor/symfony/intl/Data/Generator/LocaleDataGenerator.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Generator; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface; +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface; +use Symfony\Component\Intl\Data\Util\LocaleScanner; +use Symfony\Component\Intl\Exception\MissingResourceException; + +/** + * The rule for compiling the locale bundle. + * + * @author Bernhard Schussek + * @author Roland Franssen + * + * @internal + */ +class LocaleDataGenerator extends AbstractDataGenerator +{ + use FallbackTrait; + + private $locales = []; + private $localeAliases = []; + private $localeParents = []; + + /** + * {@inheritdoc} + */ + protected function scanLocales(LocaleScanner $scanner, string $sourceDir): array + { + $this->locales = $scanner->scanLocales($sourceDir.'/locales'); + $this->localeAliases = $scanner->scanAliases($sourceDir.'/locales'); + $this->localeParents = $scanner->scanParents($sourceDir.'/locales'); + + return $this->locales; + } + + /** + * {@inheritdoc} + */ + protected function compileTemporaryBundles(BundleCompilerInterface $compiler, string $sourceDir, string $tempDir) + { + $filesystem = new Filesystem(); + $filesystem->mkdir([ + $tempDir.'/lang', + $tempDir.'/region', + ]); + $compiler->compile($sourceDir.'/lang', $tempDir.'/lang'); + $compiler->compile($sourceDir.'/region', $tempDir.'/region'); + } + + /** + * {@inheritdoc} + */ + protected function preGenerate() + { + // Write parents locale file for the Translation component + file_put_contents( + __DIR__.'/../../../Translation/Resources/data/parents.json', + json_encode($this->localeParents, JSON_PRETTY_PRINT).PHP_EOL + ); + } + + /** + * {@inheritdoc} + */ + protected function generateDataForLocale(BundleEntryReaderInterface $reader, string $tempDir, string $displayLocale): ?array + { + // Don't generate aliases, as they are resolved during runtime + // Unless an alias is needed as fallback for de-duplication purposes + if (isset($this->localeAliases[$displayLocale]) && !$this->generatingFallback) { + return null; + } + + // Generate locale names for all locales that have translations in + // at least the language or the region bundle + $displayFormat = $reader->readEntry($tempDir.'/lang', $displayLocale, ['localeDisplayPattern']); + $pattern = $displayFormat['pattern'] ?? '{0} ({1})'; + $separator = $displayFormat['separator'] ?? '{0}, {1}'; + $localeNames = []; + foreach ($this->locales as $locale) { + // Ensure a normalized list of pure locales + if (\Locale::getAllVariants($locale)) { + continue; + } + + try { + // Generate a locale name in the language of each display locale + // Each locale name has the form: "Language (Script, Region, Variant1, ...) + // Script, Region and Variants are optional. If none of them is + // available, the braces are not printed. + $localeNames[$locale] = $this->generateLocaleName($reader, $tempDir, $locale, $displayLocale, $pattern, $separator); + } catch (MissingResourceException $e) { + // Silently ignore incomplete locale names + // In this case one should configure at least one fallback locale that is complete (e.g. English) during + // runtime. Alternatively a translation for the missing resource can be proposed upstream. + } + } + + $data = [ + 'Names' => $localeNames, + ]; + + // Don't de-duplicate a fallback locale + // Ensures the display locale can be de-duplicated on itself + if ($this->generatingFallback) { + return $data; + } + + // Process again to de-duplicate locale and its fallback locales + // Only keep the differences + $fallbackData = $this->generateFallbackData($reader, $tempDir, $displayLocale); + if (isset($fallbackData['Names'])) { + $data['Names'] = array_diff($data['Names'], $fallbackData['Names']); + } + if (!$data['Names']) { + return null; + } + + return $data; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForRoot(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + return null; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForMeta(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + return [ + 'Locales' => $this->locales, + 'Aliases' => $this->localeAliases, + ]; + } + + private function generateLocaleName(BundleEntryReaderInterface $reader, string $tempDir, string $locale, string $displayLocale, string $pattern, string $separator): string + { + // Apply generic notation using square brackets as described per http://cldr.unicode.org/translation/language-names + $name = str_replace(['(', ')'], ['[', ']'], $reader->readEntry($tempDir.'/lang', $displayLocale, ['Languages', \Locale::getPrimaryLanguage($locale)])); + $extras = []; + + // Discover the name of the script part of the locale + // i.e. in zh_Hans_MO, "Hans" is the script + if ($script = \Locale::getScript($locale)) { + $extras[] = str_replace(['(', ')'], ['[', ']'], $reader->readEntry($tempDir.'/lang', $displayLocale, ['Scripts', $script])); + } + + // Discover the name of the region part of the locale + // i.e. in de_AT, "AT" is the region + if ($region = \Locale::getRegion($locale)) { + if (ctype_alpha($region) && !RegionDataGenerator::isValidCountryCode($region)) { + throw new MissingResourceException(sprintf('Skipping "%s" due an invalid country.', $locale)); + } + + $extras[] = str_replace(['(', ')'], ['[', ']'], $reader->readEntry($tempDir.'/region', $displayLocale, ['Countries', $region])); + } + + if ($extras) { + $extra = array_shift($extras); + foreach ($extras as $part) { + $extra = str_replace(['{0}', '{1}'], [$extra, $part], $separator); + } + + $name = str_replace(['{0}', '{1}'], [$name, $extra], $pattern); + } + + return $name; + } +} diff --git a/vendor/symfony/intl/Data/Generator/RegionDataGenerator.php b/vendor/symfony/intl/Data/Generator/RegionDataGenerator.php new file mode 100644 index 0000000..066cc5f --- /dev/null +++ b/vendor/symfony/intl/Data/Generator/RegionDataGenerator.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Generator; + +use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface; +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface; +use Symfony\Component\Intl\Data\Util\ArrayAccessibleResourceBundle; +use Symfony\Component\Intl\Data\Util\LocaleScanner; +use Symfony\Component\Intl\Exception\RuntimeException; + +/** + * The rule for compiling the region bundle. + * + * @author Bernhard Schussek + * + * @see http://source.icu-project.org/repos/icu/icu4j/trunk/main/classes/core/src/com/ibm/icu/util/Region.java + * + * @internal + */ +class RegionDataGenerator extends AbstractDataGenerator +{ + /** + * Source: https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes. + */ + private static $preferredAlpha2ToAlpha3Mapping = [ + 'CD' => 'COD', + 'DE' => 'DEU', + 'FR' => 'FRA', + 'MM' => 'MMR', + 'TL' => 'TLS', + 'YE' => 'YEM', + ]; + + private static $blacklist = [ + // Exceptional reservations + 'AC' => true, // Ascension Island + 'CP' => true, // Clipperton Island + 'DG' => true, // Diego Garcia + 'EA' => true, // Ceuta & Melilla + 'EU' => true, // European Union + 'EZ' => true, // Eurozone + 'IC' => true, // Canary Islands + 'TA' => true, // Tristan da Cunha + 'UN' => true, // United Nations + // User-assigned + 'QO' => true, // Outlying Oceania + 'XA' => true, // Pseudo-Accents + 'XB' => true, // Pseudo-Bidi + 'XK' => true, // Kosovo + // Misc + 'ZZ' => true, // Unknown Region + ]; + + /** + * Collects all available language codes. + * + * @var string[] + */ + private $regionCodes = []; + + public static function isValidCountryCode($region) + { + if (isset(self::$blacklist[$region])) { + return false; + } + + // WORLD/CONTINENT/SUBCONTINENT/GROUPING + if (ctype_digit($region) || \is_int($region)) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function scanLocales(LocaleScanner $scanner, string $sourceDir): array + { + return $scanner->scanLocales($sourceDir.'/region'); + } + + /** + * {@inheritdoc} + */ + protected function compileTemporaryBundles(BundleCompilerInterface $compiler, string $sourceDir, string $tempDir) + { + $compiler->compile($sourceDir.'/region', $tempDir); + $compiler->compile($sourceDir.'/misc/metadata.txt', $tempDir); + } + + /** + * {@inheritdoc} + */ + protected function preGenerate() + { + $this->regionCodes = []; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForLocale(BundleEntryReaderInterface $reader, string $tempDir, string $displayLocale): ?array + { + $localeBundle = $reader->read($tempDir, $displayLocale); + + // isset() on \ResourceBundle returns true even if the value is null + if (isset($localeBundle['Countries']) && null !== $localeBundle['Countries']) { + $data = [ + 'Names' => $this->generateRegionNames($localeBundle), + ]; + + $this->regionCodes = array_merge($this->regionCodes, array_keys($data['Names'])); + + return $data; + } + + return null; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForRoot(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + return null; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForMeta(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + $metadataBundle = $reader->read($tempDir, 'metadata'); + + $this->regionCodes = array_unique($this->regionCodes); + + sort($this->regionCodes); + + $alpha2ToAlpha3 = $this->generateAlpha2ToAlpha3Mapping(array_flip($this->regionCodes), $metadataBundle); + $alpha3ToAlpha2 = array_flip($alpha2ToAlpha3); + asort($alpha3ToAlpha2); + + return [ + 'Regions' => $this->regionCodes, + 'Alpha2ToAlpha3' => $alpha2ToAlpha3, + 'Alpha3ToAlpha2' => $alpha3ToAlpha2, + ]; + } + + protected function generateRegionNames(ArrayAccessibleResourceBundle $localeBundle): array + { + $unfilteredRegionNames = iterator_to_array($localeBundle['Countries']); + $regionNames = []; + + foreach ($unfilteredRegionNames as $region => $regionName) { + if (!self::isValidCountryCode($region)) { + continue; + } + + $regionNames[$region] = $regionName; + } + + return $regionNames; + } + + private function generateAlpha2ToAlpha3Mapping(array $countries, ArrayAccessibleResourceBundle $metadataBundle): array + { + $aliases = iterator_to_array($metadataBundle['alias']['territory']); + $alpha2ToAlpha3 = []; + + foreach ($aliases as $alias => $data) { + $country = $data['replacement']; + if (2 === \strlen($country) && 3 === \strlen($alias) && 'overlong' === $data['reason']) { + if (isset(self::$preferredAlpha2ToAlpha3Mapping[$country])) { + // Validate to prevent typos + if (!isset($aliases[self::$preferredAlpha2ToAlpha3Mapping[$country]])) { + throw new RuntimeException('The statically set three-letter mapping '.self::$preferredAlpha2ToAlpha3Mapping[$country].' for the country code '.$country.' seems to be invalid. Typo?'); + } + + $alpha3 = self::$preferredAlpha2ToAlpha3Mapping[$country]; + $alpha2 = $aliases[$alpha3]['replacement']; + + if ($country !== $alpha2) { + throw new RuntimeException('The statically set three-letter mapping '.$alpha3.' for the country code '.$country.' seems to be an alias for '.$alpha2.'. Wrong mapping?'); + } + + $alpha2ToAlpha3[$country] = $alpha3; + } elseif (isset($alpha2ToAlpha3[$country])) { + throw new RuntimeException('Multiple three-letter mappings exist for the country code '.$country.'. Please add one of them to the property $preferredAlpha2ToAlpha3Mapping.'); + } elseif (isset($countries[$country]) && self::isValidCountryCode($alias)) { + $alpha2ToAlpha3[$country] = $alias; + } + } + } + + asort($alpha2ToAlpha3); + + return $alpha2ToAlpha3; + } +} diff --git a/vendor/symfony/intl/Data/Generator/ScriptDataGenerator.php b/vendor/symfony/intl/Data/Generator/ScriptDataGenerator.php new file mode 100644 index 0000000..9c26284 --- /dev/null +++ b/vendor/symfony/intl/Data/Generator/ScriptDataGenerator.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Generator; + +use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface; +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface; +use Symfony\Component\Intl\Data\Util\LocaleScanner; + +/** + * The rule for compiling the script bundle. + * + * @author Bernhard Schussek + * + * @internal + */ +class ScriptDataGenerator extends AbstractDataGenerator +{ + private static $blacklist = [ + 'Zzzz' => true, // Unknown Script + ]; + + /** + * Collects all available language codes. + * + * @var string[] + */ + private $scriptCodes = []; + + /** + * {@inheritdoc} + */ + protected function scanLocales(LocaleScanner $scanner, string $sourceDir): array + { + return $scanner->scanLocales($sourceDir.'/lang'); + } + + /** + * {@inheritdoc} + */ + protected function compileTemporaryBundles(BundleCompilerInterface $compiler, string $sourceDir, string $tempDir) + { + $compiler->compile($sourceDir.'/lang', $tempDir); + } + + /** + * {@inheritdoc} + */ + protected function preGenerate() + { + $this->scriptCodes = []; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForLocale(BundleEntryReaderInterface $reader, string $tempDir, string $displayLocale): ?array + { + $localeBundle = $reader->read($tempDir, $displayLocale); + + // isset() on \ResourceBundle returns true even if the value is null + if (isset($localeBundle['Scripts']) && null !== $localeBundle['Scripts']) { + $data = [ + 'Names' => array_diff_key(iterator_to_array($localeBundle['Scripts']), self::$blacklist), + ]; + + $this->scriptCodes = array_merge($this->scriptCodes, array_keys($data['Names'])); + + return $data; + } + + return null; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForRoot(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + return null; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForMeta(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + $this->scriptCodes = array_unique($this->scriptCodes); + + sort($this->scriptCodes); + + return [ + 'Scripts' => $this->scriptCodes, + ]; + } +} diff --git a/vendor/symfony/intl/Data/Generator/TimezoneDataGenerator.php b/vendor/symfony/intl/Data/Generator/TimezoneDataGenerator.php new file mode 100644 index 0000000..54101f4 --- /dev/null +++ b/vendor/symfony/intl/Data/Generator/TimezoneDataGenerator.php @@ -0,0 +1,292 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Generator; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface; +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface; +use Symfony\Component\Intl\Data\Util\ArrayAccessibleResourceBundle; +use Symfony\Component\Intl\Data\Util\LocaleScanner; +use Symfony\Component\Intl\Exception\MissingResourceException; +use Symfony\Component\Intl\Locale; + +/** + * The rule for compiling the zone bundle. + * + * @author Roland Franssen + * + * @internal + */ +class TimezoneDataGenerator extends AbstractDataGenerator +{ + use FallbackTrait; + + /** + * Collects all available zone IDs. + * + * @var string[] + */ + private $zoneIds = []; + private $zoneToCountryMapping = []; + private $localeAliases = []; + + /** + * {@inheritdoc} + */ + protected function scanLocales(LocaleScanner $scanner, string $sourceDir): array + { + $this->localeAliases = $scanner->scanAliases($sourceDir.'/locales'); + + return $scanner->scanLocales($sourceDir.'/zone'); + } + + /** + * {@inheritdoc} + */ + protected function compileTemporaryBundles(BundleCompilerInterface $compiler, string $sourceDir, string $tempDir) + { + $filesystem = new Filesystem(); + $filesystem->mkdir($tempDir.'/region'); + $compiler->compile($sourceDir.'/region', $tempDir.'/region'); + $compiler->compile($sourceDir.'/zone', $tempDir); + $compiler->compile($sourceDir.'/misc/timezoneTypes.txt', $tempDir); + $compiler->compile($sourceDir.'/misc/metaZones.txt', $tempDir); + $compiler->compile($sourceDir.'/misc/windowsZones.txt', $tempDir); + } + + /** + * {@inheritdoc} + */ + protected function preGenerate() + { + $this->zoneIds = []; + $this->zoneToCountryMapping = []; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForLocale(BundleEntryReaderInterface $reader, string $tempDir, string $displayLocale): ?array + { + if (!$this->zoneToCountryMapping) { + $this->zoneToCountryMapping = self::generateZoneToCountryMapping($reader->read($tempDir, 'windowsZones')); + } + + // Don't generate aliases, as they are resolved during runtime + // Unless an alias is needed as fallback for de-duplication purposes + if (isset($this->localeAliases[$displayLocale]) && !$this->generatingFallback) { + return null; + } + + $localeBundle = $reader->read($tempDir, $displayLocale); + + if (!isset($localeBundle['zoneStrings']) || null === $localeBundle['zoneStrings']) { + return null; + } + + $data = [ + 'Names' => $this->generateZones($reader, $tempDir, $displayLocale), + 'Meta' => self::generateZoneMetadata($localeBundle), + ]; + + // Don't de-duplicate a fallback locale + // Ensures the display locale can be de-duplicated on itself + if ($this->generatingFallback) { + return $data; + } + + // Process again to de-duplicate locales and their fallback locales + // Only keep the differences + $fallback = $this->generateFallbackData($reader, $tempDir, $displayLocale); + if (isset($fallback['Names'])) { + $data['Names'] = array_diff($data['Names'], $fallback['Names']); + } + if (isset($fallback['Meta'])) { + $data['Meta'] = array_diff($data['Meta'], $fallback['Meta']); + } + if (!$data['Names'] && !$data['Meta']) { + return null; + } + + $this->zoneIds = array_merge($this->zoneIds, array_keys($data['Names'])); + + return $data; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForRoot(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + $rootBundle = $reader->read($tempDir, 'root'); + + return [ + 'Meta' => self::generateZoneMetadata($rootBundle), + ]; + } + + /** + * {@inheritdoc} + */ + protected function generateDataForMeta(BundleEntryReaderInterface $reader, string $tempDir): ?array + { + $rootBundle = $reader->read($tempDir, 'root'); + + $this->zoneIds = array_unique($this->zoneIds); + + sort($this->zoneIds); + ksort($this->zoneToCountryMapping); + + $data = [ + 'Zones' => $this->zoneIds, + 'ZoneToCountry' => $this->zoneToCountryMapping, + 'CountryToZone' => self::generateCountryToZoneMapping($this->zoneToCountryMapping), + ]; + + return $data; + } + + private function generateZones(BundleEntryReaderInterface $reader, string $tempDir, string $locale): array + { + $typeBundle = $reader->read($tempDir, 'timezoneTypes'); + $available = []; + foreach ($typeBundle['typeMap']['timezone'] as $zone => $_) { + if ('Etc:Unknown' === $zone || preg_match('~^Etc:GMT[-+]\d+$~', $zone)) { + continue; + } + + $available[$zone] = true; + } + + $metaBundle = $reader->read($tempDir, 'metaZones'); + $metazones = []; + foreach ($metaBundle['metazoneInfo'] as $zone => $info) { + foreach ($info as $metazone) { + $metazones[$zone] = $metazone->get(0); + } + } + + $regionFormat = $reader->readEntry($tempDir, $locale, ['zoneStrings', 'regionFormat']); + $fallbackFormat = $reader->readEntry($tempDir, $locale, ['zoneStrings', 'fallbackFormat']); + $resolveName = function (string $id, string $city = null) use ($reader, $tempDir, $locale, $regionFormat, $fallbackFormat): ?string { + // Resolve default name as described per http://cldr.unicode.org/translation/timezones + if (isset($this->zoneToCountryMapping[$id])) { + try { + $country = $reader->readEntry($tempDir.'/region', $locale, ['Countries', $this->zoneToCountryMapping[$id]]); + } catch (MissingResourceException $e) { + return null; + } + + $name = str_replace('{0}', $country, $regionFormat); + + return null === $city ? $name : str_replace(['{0}', '{1}'], [$city, $name], $fallbackFormat); + } + if (null !== $city) { + return str_replace('{0}', $city, $regionFormat); + } + + return null; + }; + $accessor = static function (array $indices, array ...$fallbackIndices) use ($locale, $reader, $tempDir) { + foreach (\func_get_args() as $indices) { + try { + return $reader->readEntry($tempDir, $locale, $indices); + } catch (MissingResourceException $e) { + } + } + + return null; + }; + $zones = []; + foreach (array_keys($available) as $zone) { + // lg: long generic, e.g. "Central European Time" + // ls: long specific (not DST), e.g. "Central European Standard Time" + // ld: long DST, e.g. "Central European Summer Time" + // ec: example city, e.g. "Amsterdam" + $name = $accessor(['zoneStrings', $zone, 'lg'], ['zoneStrings', $zone, 'ls']); + $city = $accessor(['zoneStrings', $zone, 'ec']); + $id = str_replace(':', '/', $zone); + + if (null === $name && isset($metazones[$zone])) { + $meta = 'meta:'.$metazones[$zone]; + $name = $accessor(['zoneStrings', $meta, 'lg'], ['zoneStrings', $meta, 'ls']); + } + + // Infer a default English named city for all locales + // Ensures each timezone ID has a distinctive name + if (null === $city && 0 !== strrpos($zone, 'Etc:') && false !== $i = strrpos($zone, ':')) { + $city = str_replace('_', ' ', substr($zone, $i + 1)); + } + if (null === $name) { + $name = $resolveName($id, $city); + $city = null; + } + if (null === $name) { + continue; + } + + // Ensure no duplicated content is generated + if (null !== $city && false === mb_stripos(str_replace('-', ' ', $name), str_replace('-', ' ', $city))) { + $name = str_replace(['{0}', '{1}'], [$city, $name], $fallbackFormat); + } + + $zones[$id] = $name; + } + + return $zones; + } + + private static function generateZoneMetadata(ArrayAccessibleResourceBundle $localeBundle): array + { + $metadata = []; + if (isset($localeBundle['zoneStrings']['gmtFormat'])) { + $metadata['GmtFormat'] = str_replace('{0}', '%s', $localeBundle['zoneStrings']['gmtFormat']); + } + if (isset($localeBundle['zoneStrings']['hourFormat'])) { + $hourFormat = explode(';', str_replace(['HH', 'mm', 'H', 'm'], ['%02d', '%02d', '%d', '%d'], $localeBundle['zoneStrings']['hourFormat']), 2); + $metadata['HourFormatPos'] = $hourFormat[0]; + $metadata['HourFormatNeg'] = $hourFormat[1]; + } + + return $metadata; + } + + private static function generateZoneToCountryMapping(ArrayAccessibleResourceBundle $windowsZoneBundle): array + { + $mapping = []; + + foreach ($windowsZoneBundle['mapTimezones'] as $zoneInfo) { + foreach ($zoneInfo as $region => $zones) { + if (RegionDataGenerator::isValidCountryCode($region)) { + $mapping += array_fill_keys(explode(' ', $zones), $region); + } + } + } + + ksort($mapping); + + return $mapping; + } + + private static function generateCountryToZoneMapping(array $zoneToCountryMapping): array + { + $mapping = []; + + foreach ($zoneToCountryMapping as $zone => $country) { + $mapping[$country][] = $zone; + } + + ksort($mapping); + + return $mapping; + } +} diff --git a/vendor/symfony/intl/Data/Util/ArrayAccessibleResourceBundle.php b/vendor/symfony/intl/Data/Util/ArrayAccessibleResourceBundle.php new file mode 100644 index 0000000..037f704 --- /dev/null +++ b/vendor/symfony/intl/Data/Util/ArrayAccessibleResourceBundle.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Util; + +use Symfony\Component\Intl\Exception\BadMethodCallException; + +/** + * Work-around for a bug in PHP's \ResourceBundle implementation. + * + * More information can be found on https://bugs.php.net/64356. + * This class can be removed once that bug is fixed. + * + * @author Bernhard Schussek + * + * @internal + */ +class ArrayAccessibleResourceBundle implements \ArrayAccess, \IteratorAggregate, \Countable +{ + private $bundleImpl; + + public function __construct(\ResourceBundle $bundleImpl) + { + $this->bundleImpl = $bundleImpl; + } + + public function get($offset) + { + $value = $this->bundleImpl->get($offset); + + return $value instanceof \ResourceBundle ? new static($value) : $value; + } + + public function offsetExists($offset): bool + { + return null !== $this->bundleImpl->get($offset); + } + + public function offsetGet($offset) + { + return $this->get($offset); + } + + public function offsetSet($offset, $value) + { + throw new BadMethodCallException('Resource bundles cannot be modified.'); + } + + public function offsetUnset($offset) + { + throw new BadMethodCallException('Resource bundles cannot be modified.'); + } + + public function getIterator(): \Traversable + { + return $this->bundleImpl; + } + + public function count(): int + { + return $this->bundleImpl->count(); + } + + public function getErrorCode() + { + return $this->bundleImpl->getErrorCode(); + } + + public function getErrorMessage() + { + return $this->bundleImpl->getErrorMessage(); + } +} diff --git a/vendor/symfony/intl/Data/Util/LocaleScanner.php b/vendor/symfony/intl/Data/Util/LocaleScanner.php new file mode 100644 index 0000000..7123d5b --- /dev/null +++ b/vendor/symfony/intl/Data/Util/LocaleScanner.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Util; + +/** + * Scans a directory with data files for locales. + * + * The name of each file with the extension ".txt" is considered, if it "looks" + * like a locale: + * + * - the name must start with two letters; + * - the two letters may optionally be followed by an underscore and any + * sequence of other symbols. + * + * For example, "de" and "de_DE" are considered to be locales. "root" and "meta" + * are not. + * + * @author Bernhard Schussek + * + * @internal + */ +class LocaleScanner +{ + /** + * Returns all locales found in the given directory. + * + * @return array An array of locales. The result also contains locales that + * are in fact just aliases for other locales. Use + * {@link scanAliases()} to determine which of the locales + * are aliases + */ + public function scanLocales(string $sourceDir): array + { + $locales = glob($sourceDir.'/*.txt', GLOB_NOSORT); + + // Remove file extension and sort + array_walk($locales, function (&$locale) { $locale = basename($locale, '.txt'); }); + + // Remove non-locales + $locales = array_filter($locales, function ($locale) { + return preg_match('/^[a-z]{2}(_.+)?$/', $locale); + }); + + sort($locales); + + return $locales; + } + + /** + * Returns all locale aliases found in the given directory. + * + * @return array An array with the locale aliases as keys and the aliased + * locales as values + */ + public function scanAliases(string $sourceDir): array + { + $locales = $this->scanLocales($sourceDir); + $aliases = []; + + // Delete locales that are no aliases + foreach ($locales as $locale) { + $content = file_get_contents($sourceDir.'/'.$locale.'.txt'); + + // Aliases contain the text "%%ALIAS" followed by the aliased locale + if (preg_match('/"%%ALIAS"\{"([^"]+)"\}/', $content, $matches)) { + $aliases[$locale] = $matches[1]; + } + } + + return $aliases; + } + + /** + * Returns all locale parents found in the given directory. + */ + public function scanParents(string $sourceDir): array + { + $locales = $this->scanLocales($sourceDir); + $fallbacks = []; + + foreach ($locales as $locale) { + $content = file_get_contents($sourceDir.'/'.$locale.'.txt'); + + // Aliases contain the text "%%PARENT" followed by the aliased locale + if (preg_match('/%%Parent{"([^"]+)"}/', $content, $matches)) { + $fallbacks[$locale] = $matches[1]; + } + } + + return $fallbacks; + } +} diff --git a/vendor/symfony/intl/Data/Util/RecursiveArrayAccess.php b/vendor/symfony/intl/Data/Util/RecursiveArrayAccess.php new file mode 100644 index 0000000..afc2e09 --- /dev/null +++ b/vendor/symfony/intl/Data/Util/RecursiveArrayAccess.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Util; + +use Symfony\Component\Intl\Exception\OutOfBoundsException; + +/** + * @author Bernhard Schussek + * + * @internal + */ +class RecursiveArrayAccess +{ + public static function get($array, array $indices) + { + foreach ($indices as $index) { + // Use array_key_exists() for arrays, isset() otherwise + if (\is_array($array)) { + if (\array_key_exists($index, $array)) { + $array = $array[$index]; + continue; + } + } elseif ($array instanceof \ArrayAccess) { + if (isset($array[$index])) { + $array = $array[$index]; + continue; + } + } + + throw new OutOfBoundsException(sprintf('The index "%s" does not exist.', $index)); + } + + return $array; + } + + private function __construct() + { + } +} diff --git a/vendor/symfony/intl/Data/Util/RingBuffer.php b/vendor/symfony/intl/Data/Util/RingBuffer.php new file mode 100644 index 0000000..db33a4f --- /dev/null +++ b/vendor/symfony/intl/Data/Util/RingBuffer.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Data\Util; + +use Symfony\Component\Intl\Exception\OutOfBoundsException; + +/** + * Implements a ring buffer. + * + * A ring buffer is an array-like structure with a fixed size. If the buffer + * is full, the next written element overwrites the first bucket in the buffer, + * then the second and so on. + * + * @author Bernhard Schussek + * + * @internal + */ +class RingBuffer implements \ArrayAccess +{ + private $values = []; + + private $indices = []; + + private $cursor = 0; + + private $size; + + public function __construct(int $size) + { + $this->size = $size; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($key): bool + { + return isset($this->indices[$key]); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($key) + { + if (!isset($this->indices[$key])) { + throw new OutOfBoundsException(sprintf('The index "%s" does not exist.', $key)); + } + + return $this->values[$this->indices[$key]]; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($key, $value) + { + if (false !== ($keyToRemove = array_search($this->cursor, $this->indices))) { + unset($this->indices[$keyToRemove]); + } + + $this->values[$this->cursor] = $value; + $this->indices[$key] = $this->cursor; + + $this->cursor = ($this->cursor + 1) % $this->size; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($key) + { + if (isset($this->indices[$key])) { + $this->values[$this->indices[$key]] = null; + unset($this->indices[$key]); + } + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/AmPmTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/AmPmTransformer.php new file mode 100644 index 0000000..36a1294 --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/AmPmTransformer.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for AM/PM markers format. + * + * @author Igor Wiedler + * + * @internal + */ +class AmPmTransformer extends Transformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + return $dateTime->format('A'); + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return 'AM|PM'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'marker' => $matched, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/DayOfWeekTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/DayOfWeekTransformer.php new file mode 100644 index 0000000..f18abb9 --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/DayOfWeekTransformer.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for day of week format. + * + * @author Igor Wiedler + * + * @internal + */ +class DayOfWeekTransformer extends Transformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + $dayOfWeek = $dateTime->format('l'); + switch ($length) { + case 4: + return $dayOfWeek; + case 5: + return $dayOfWeek[0]; + case 6: + return substr($dayOfWeek, 0, 2); + default: + return substr($dayOfWeek, 0, 3); + } + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + switch ($length) { + case 4: + return 'Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday'; + case 5: + return '[MTWFS]'; + case 6: + return 'Mo|Tu|We|Th|Fr|Sa|Su'; + default: + return 'Mon|Tue|Wed|Thu|Fri|Sat|Sun'; + } + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return []; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/DayOfYearTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/DayOfYearTransformer.php new file mode 100644 index 0000000..93c09d8 --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/DayOfYearTransformer.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for day of year format. + * + * @author Igor Wiedler + * + * @internal + */ +class DayOfYearTransformer extends Transformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + $dayOfYear = (int) $dateTime->format('z') + 1; + + return $this->padLeft($dayOfYear, $length); + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return '\d{'.$length.'}'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return []; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/DayTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/DayTransformer.php new file mode 100644 index 0000000..91676d3 --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/DayTransformer.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for day format. + * + * @author Igor Wiedler + * + * @internal + */ +class DayTransformer extends Transformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + return $this->padLeft($dateTime->format('j'), $length); + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return 1 === $length ? '\d{1,2}' : '\d{1,'.$length.'}'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'day' => (int) $matched, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/FullTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/FullTransformer.php new file mode 100644 index 0000000..2641528 --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/FullTransformer.php @@ -0,0 +1,312 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +use Symfony\Component\Intl\Exception\NotImplementedException; +use Symfony\Component\Intl\Globals\IntlGlobals; + +/** + * Parser and formatter for date formats. + * + * @author Igor Wiedler + * + * @internal + */ +class FullTransformer +{ + private $quoteMatch = "'(?:[^']+|'')*'"; + private $implementedChars = 'MLydQqhDEaHkKmsz'; + private $notImplementedChars = 'GYuwWFgecSAZvVW'; + private $regExp; + + /** + * @var Transformer[] + */ + private $transformers; + + private $pattern; + private $timezone; + + /** + * @param string $pattern The pattern to be used to format and/or parse values + * @param string $timezone The timezone to perform the date/time calculations + */ + public function __construct(string $pattern, string $timezone) + { + $this->pattern = $pattern; + $this->timezone = $timezone; + + $implementedCharsMatch = $this->buildCharsMatch($this->implementedChars); + $notImplementedCharsMatch = $this->buildCharsMatch($this->notImplementedChars); + $this->regExp = "/($this->quoteMatch|$implementedCharsMatch|$notImplementedCharsMatch)/"; + + $this->transformers = [ + 'M' => new MonthTransformer(), + 'L' => new MonthTransformer(), + 'y' => new YearTransformer(), + 'd' => new DayTransformer(), + 'q' => new QuarterTransformer(), + 'Q' => new QuarterTransformer(), + 'h' => new Hour1201Transformer(), + 'D' => new DayOfYearTransformer(), + 'E' => new DayOfWeekTransformer(), + 'a' => new AmPmTransformer(), + 'H' => new Hour2400Transformer(), + 'K' => new Hour1200Transformer(), + 'k' => new Hour2401Transformer(), + 'm' => new MinuteTransformer(), + 's' => new SecondTransformer(), + 'z' => new TimezoneTransformer(), + ]; + } + + /** + * Format a DateTime using ICU dateformat pattern. + * + * @return string The formatted value + */ + public function format(\DateTime $dateTime): string + { + $formatted = preg_replace_callback($this->regExp, function ($matches) use ($dateTime) { + return $this->formatReplace($matches[0], $dateTime); + }, $this->pattern); + + return $formatted; + } + + /** + * Return the formatted ICU value for the matched date characters. + * + * @throws NotImplementedException When it encounters a not implemented date character + */ + private function formatReplace(string $dateChars, \DateTime $dateTime): string + { + $length = \strlen($dateChars); + + if ($this->isQuoteMatch($dateChars)) { + return $this->replaceQuoteMatch($dateChars); + } + + if (isset($this->transformers[$dateChars[0]])) { + $transformer = $this->transformers[$dateChars[0]]; + + return $transformer->format($dateTime, $length); + } + + // handle unimplemented characters + if (false !== strpos($this->notImplementedChars, $dateChars[0])) { + throw new NotImplementedException(sprintf('Unimplemented date character "%s" in format "%s".', $dateChars[0], $this->pattern)); + } + + return ''; + } + + /** + * Parse a pattern based string to a timestamp value. + * + * @param \DateTime $dateTime A configured DateTime object to use to perform the date calculation + * @param string $value String to convert to a time value + * + * @return int|false The corresponding Unix timestamp + * + * @throws \InvalidArgumentException When the value can not be matched with pattern + */ + public function parse(\DateTime $dateTime, string $value) + { + $reverseMatchingRegExp = $this->getReverseMatchingRegExp($this->pattern); + $reverseMatchingRegExp = '/^'.$reverseMatchingRegExp.'$/'; + + $options = []; + + if (preg_match($reverseMatchingRegExp, $value, $matches)) { + $matches = $this->normalizeArray($matches); + + foreach ($this->transformers as $char => $transformer) { + if (isset($matches[$char])) { + $length = \strlen($matches[$char]['pattern']); + $options = array_merge($options, $transformer->extractDateOptions($matches[$char]['value'], $length)); + } + } + + // reset error code and message + IntlGlobals::setError(IntlGlobals::U_ZERO_ERROR); + + return $this->calculateUnixTimestamp($dateTime, $options); + } + + // behave like the intl extension + IntlGlobals::setError(IntlGlobals::U_PARSE_ERROR, 'Date parsing failed'); + + return false; + } + + /** + * Retrieve a regular expression to match with a formatted value. + * + * @return string The reverse matching regular expression with named captures being formed by the + * transformer index in the $transformer array + */ + private function getReverseMatchingRegExp(string $pattern): string + { + $escapedPattern = preg_quote($pattern, '/'); + + // ICU 4.8 recognizes slash ("/") in a value to be parsed as a dash ("-") and vice-versa + // when parsing a date/time value + $escapedPattern = preg_replace('/\\\[\-|\/]/', '[\/\-]', $escapedPattern); + + $reverseMatchingRegExp = preg_replace_callback($this->regExp, function ($matches) { + $length = \strlen($matches[0]); + $transformerIndex = $matches[0][0]; + + $dateChars = $matches[0]; + if ($this->isQuoteMatch($dateChars)) { + return $this->replaceQuoteMatch($dateChars); + } + + if (isset($this->transformers[$transformerIndex])) { + $transformer = $this->transformers[$transformerIndex]; + $captureName = str_repeat($transformerIndex, $length); + + return "(?P<$captureName>".$transformer->getReverseMatchingRegExp($length).')'; + } + + return null; + }, $escapedPattern); + + return $reverseMatchingRegExp; + } + + /** + * Check if the first char of a string is a single quote. + */ + private function isQuoteMatch(string $quoteMatch): bool + { + return "'" === $quoteMatch[0]; + } + + /** + * Replaces single quotes at the start or end of a string with two single quotes. + */ + private function replaceQuoteMatch(string $quoteMatch): string + { + if (preg_match("/^'+$/", $quoteMatch)) { + return str_replace("''", "'", $quoteMatch); + } + + return str_replace("''", "'", substr($quoteMatch, 1, -1)); + } + + /** + * Builds a chars match regular expression. + */ + private function buildCharsMatch(string $specialChars): string + { + $specialCharsArray = str_split($specialChars); + + $specialCharsMatch = implode('|', array_map(function ($char) { + return $char.'+'; + }, $specialCharsArray)); + + return $specialCharsMatch; + } + + /** + * Normalize a preg_replace match array, removing the numeric keys and returning an associative array + * with the value and pattern values for the matched Transformer. + */ + private function normalizeArray(array $data): array + { + $ret = []; + + foreach ($data as $key => $value) { + if (!\is_string($key)) { + continue; + } + + $ret[$key[0]] = [ + 'value' => $value, + 'pattern' => $key, + ]; + } + + return $ret; + } + + /** + * Calculates the Unix timestamp based on the matched values by the reverse matching regular + * expression of parse(). + * + * @return bool|int The calculated timestamp or false if matched date is invalid + */ + private function calculateUnixTimestamp(\DateTime $dateTime, array $options) + { + $options = $this->getDefaultValueForOptions($options); + + $year = $options['year']; + $month = $options['month']; + $day = $options['day']; + $hour = $options['hour']; + $hourInstance = $options['hourInstance']; + $minute = $options['minute']; + $second = $options['second']; + $marker = $options['marker']; + $timezone = $options['timezone']; + + // If month is false, return immediately (intl behavior) + if (false === $month) { + IntlGlobals::setError(IntlGlobals::U_PARSE_ERROR, 'Date parsing failed'); + + return false; + } + + // Normalize hour + if ($hourInstance instanceof HourTransformer) { + $hour = $hourInstance->normalizeHour($hour, $marker); + } + + // Set the timezone if different from the default one + if (null !== $timezone && $timezone !== $this->timezone) { + $dateTime->setTimezone(new \DateTimeZone($timezone)); + } + + // Normalize yy year + preg_match_all($this->regExp, $this->pattern, $matches); + if (\in_array('yy', $matches[0])) { + $dateTime->setTimestamp(time()); + $year = $year > (int) $dateTime->format('y') + 20 ? 1900 + $year : 2000 + $year; + } + + $dateTime->setDate($year, $month, $day); + $dateTime->setTime($hour, $minute, $second); + + return $dateTime->getTimestamp(); + } + + /** + * Add sensible default values for missing items in the extracted date/time options array. The values + * are base in the beginning of the Unix era. + */ + private function getDefaultValueForOptions(array $options): array + { + return [ + 'year' => isset($options['year']) ? $options['year'] : 1970, + 'month' => isset($options['month']) ? $options['month'] : 1, + 'day' => isset($options['day']) ? $options['day'] : 1, + 'hour' => isset($options['hour']) ? $options['hour'] : 0, + 'hourInstance' => isset($options['hourInstance']) ? $options['hourInstance'] : null, + 'minute' => isset($options['minute']) ? $options['minute'] : 0, + 'second' => isset($options['second']) ? $options['second'] : 0, + 'marker' => isset($options['marker']) ? $options['marker'] : null, + 'timezone' => isset($options['timezone']) ? $options['timezone'] : null, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/Hour1200Transformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/Hour1200Transformer.php new file mode 100644 index 0000000..59d10fe --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/Hour1200Transformer.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for 12 hour format (0-11). + * + * @author Igor Wiedler + * + * @internal + */ +class Hour1200Transformer extends HourTransformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + $hourOfDay = $dateTime->format('g'); + $hourOfDay = '12' === $hourOfDay ? '0' : $hourOfDay; + + return $this->padLeft($hourOfDay, $length); + } + + /** + * {@inheritdoc} + */ + public function normalizeHour(int $hour, string $marker = null): int + { + if ('PM' === $marker) { + $hour += 12; + } + + return $hour; + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return '\d{1,2}'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'hour' => (int) $matched, + 'hourInstance' => $this, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/Hour1201Transformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/Hour1201Transformer.php new file mode 100644 index 0000000..f090ba4 --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/Hour1201Transformer.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for 12 hour format (1-12). + * + * @author Igor Wiedler + * + * @internal + */ +class Hour1201Transformer extends HourTransformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + return $this->padLeft($dateTime->format('g'), $length); + } + + /** + * {@inheritdoc} + */ + public function normalizeHour(int $hour, string $marker = null): int + { + if ('PM' !== $marker && 12 === $hour) { + $hour = 0; + } elseif ('PM' === $marker && 12 !== $hour) { + // If PM and hour is not 12 (1-12), sum 12 hour + $hour += 12; + } + + return $hour; + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return '\d{1,2}'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'hour' => (int) $matched, + 'hourInstance' => $this, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/Hour2400Transformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/Hour2400Transformer.php new file mode 100644 index 0000000..9ca6dfa --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/Hour2400Transformer.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for 24 hour format (0-23). + * + * @author Igor Wiedler + * + * @internal + */ +class Hour2400Transformer extends HourTransformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + return $this->padLeft($dateTime->format('G'), $length); + } + + /** + * {@inheritdoc} + */ + public function normalizeHour(int $hour, string $marker = null): int + { + if ('AM' === $marker) { + $hour = 0; + } elseif ('PM' === $marker) { + $hour = 12; + } + + return $hour; + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return '\d{1,2}'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'hour' => (int) $matched, + 'hourInstance' => $this, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/Hour2401Transformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/Hour2401Transformer.php new file mode 100644 index 0000000..e4db51c --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/Hour2401Transformer.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for 24 hour format (1-24). + * + * @author Igor Wiedler + * + * @internal + */ +class Hour2401Transformer extends HourTransformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + $hourOfDay = $dateTime->format('G'); + $hourOfDay = '0' === $hourOfDay ? '24' : $hourOfDay; + + return $this->padLeft($hourOfDay, $length); + } + + /** + * {@inheritdoc} + */ + public function normalizeHour(int $hour, string $marker = null): int + { + if ((null === $marker && 24 === $hour) || 'AM' === $marker) { + $hour = 0; + } elseif ('PM' === $marker) { + $hour = 12; + } + + return $hour; + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return '\d{1,2}'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'hour' => (int) $matched, + 'hourInstance' => $this, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/HourTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/HourTransformer.php new file mode 100644 index 0000000..349cd79 --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/HourTransformer.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Base class for hour transformers. + * + * @author Eriksen Costa + * + * @internal + */ +abstract class HourTransformer extends Transformer +{ + /** + * Returns a normalized hour value suitable for the hour transformer type. + * + * @param int $hour The hour value + * @param string $marker An optional AM/PM marker + * + * @return int The normalized hour value + */ + abstract public function normalizeHour(int $hour, string $marker = null): int; +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/MinuteTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/MinuteTransformer.php new file mode 100644 index 0000000..32f60d0 --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/MinuteTransformer.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for minute format. + * + * @author Igor Wiedler + * + * @internal + */ +class MinuteTransformer extends Transformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + $minuteOfHour = (int) $dateTime->format('i'); + + return $this->padLeft($minuteOfHour, $length); + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return 1 === $length ? '\d{1,2}' : '\d{'.$length.'}'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'minute' => (int) $matched, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/MonthTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/MonthTransformer.php new file mode 100644 index 0000000..fcc4c4f --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/MonthTransformer.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for month format. + * + * @author Igor Wiedler + * + * @internal + */ +class MonthTransformer extends Transformer +{ + protected static $months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + + /** + * Short months names (first 3 letters). + */ + protected static $shortMonths = []; + + /** + * Flipped $months array, $name => $index. + */ + protected static $flippedMonths = []; + + /** + * Flipped $shortMonths array, $name => $index. + */ + protected static $flippedShortMonths = []; + + public function __construct() + { + if (0 === \count(self::$shortMonths)) { + self::$shortMonths = array_map(function ($month) { + return substr($month, 0, 3); + }, self::$months); + + self::$flippedMonths = array_flip(self::$months); + self::$flippedShortMonths = array_flip(self::$shortMonths); + } + } + + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + $matchLengthMap = [ + 1 => 'n', + 2 => 'm', + 3 => 'M', + 4 => 'F', + ]; + + if (isset($matchLengthMap[$length])) { + return $dateTime->format($matchLengthMap[$length]); + } + + if (5 === $length) { + return substr($dateTime->format('M'), 0, 1); + } + + return $this->padLeft($dateTime->format('m'), $length); + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + switch ($length) { + case 1: + $regExp = '\d{1,2}'; + break; + case 3: + $regExp = implode('|', self::$shortMonths); + break; + case 4: + $regExp = implode('|', self::$months); + break; + case 5: + $regExp = '[JFMASOND]'; + break; + default: + $regExp = '\d{1,'.$length.'}'; + break; + } + + return $regExp; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + if (!is_numeric($matched)) { + if (3 === $length) { + $matched = self::$flippedShortMonths[$matched] + 1; + } elseif (4 === $length) { + $matched = self::$flippedMonths[$matched] + 1; + } elseif (5 === $length) { + // IntlDateFormatter::parse() always returns false for MMMMM or LLLLL + $matched = false; + } + } else { + $matched = (int) $matched; + } + + return [ + 'month' => $matched, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/QuarterTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/QuarterTransformer.php new file mode 100644 index 0000000..6efea71 --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/QuarterTransformer.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for quarter format. + * + * @author Igor Wiedler + * + * @internal + */ +class QuarterTransformer extends Transformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + $month = (int) $dateTime->format('n'); + $quarter = (int) floor(($month - 1) / 3) + 1; + switch ($length) { + case 1: + case 2: + return $this->padLeft($quarter, $length); + case 3: + return 'Q'.$quarter; + default: + $map = [1 => '1st quarter', 2 => '2nd quarter', 3 => '3rd quarter', 4 => '4th quarter']; + + return $map[$quarter]; + } + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + switch ($length) { + case 1: + case 2: + return '\d{'.$length.'}'; + case 3: + return 'Q\d'; + default: + return '(?:1st|2nd|3rd|4th) quarter'; + } + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return []; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/SecondTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/SecondTransformer.php new file mode 100644 index 0000000..bce89af --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/SecondTransformer.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for the second format. + * + * @author Igor Wiedler + * + * @internal + */ +class SecondTransformer extends Transformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + $secondOfMinute = (int) $dateTime->format('s'); + + return $this->padLeft($secondOfMinute, $length); + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return 1 === $length ? '\d{1,2}' : '\d{'.$length.'}'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'second' => (int) $matched, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/TimezoneTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/TimezoneTransformer.php new file mode 100644 index 0000000..4a4e38e --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/TimezoneTransformer.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +use Symfony\Component\Intl\Exception\NotImplementedException; + +/** + * Parser and formatter for time zone format. + * + * @author Igor Wiedler + * + * @internal + */ +class TimezoneTransformer extends Transformer +{ + /** + * {@inheritdoc} + * + * @throws NotImplementedException When time zone is different than UTC or GMT (Etc/GMT) + */ + public function format(\DateTime $dateTime, int $length): string + { + $timeZone = substr($dateTime->getTimezone()->getName(), 0, 3); + + if (!\in_array($timeZone, ['Etc', 'UTC', 'GMT'])) { + throw new NotImplementedException('Time zone different than GMT or UTC is not supported as a formatting output.'); + } + + if ('Etc' === $timeZone) { + // i.e. Etc/GMT+1, Etc/UTC, Etc/Zulu + $timeZone = substr($dateTime->getTimezone()->getName(), 4); + } + + // From ICU >= 59.1 GMT and UTC are no longer unified + if (\in_array($timeZone, ['UTC', 'UCT', 'Universal', 'Zulu'])) { + // offset is not supported with UTC + return $length > 3 ? 'Coordinated Universal Time' : 'UTC'; + } + + $offset = (int) $dateTime->format('O'); + + // From ICU >= 4.8, the zero offset is no more used, example: GMT instead of GMT+00:00 + if (0 === $offset) { + return $length > 3 ? 'Greenwich Mean Time' : 'GMT'; + } + + if ($length > 3) { + return $dateTime->format('\G\M\TP'); + } + + return sprintf('GMT%s%d', ($offset >= 0 ? '+' : ''), $offset / 100); + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return 'GMT[+-]\d{2}:?\d{2}'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'timezone' => self::getEtcTimeZoneId($matched), + ]; + } + + /** + * Get an Etc/GMT timezone identifier for the specified timezone. + * + * The PHP documentation for timezones states to not use the 'Other' time zones because them exists + * "for backwards compatibility". However all Etc/GMT time zones are in the tz database 'etcetera' file, + * which indicates they are not deprecated (neither are old names). + * + * Only GMT, Etc/Universal, Etc/Zulu, Etc/Greenwich, Etc/GMT-0, Etc/GMT+0 and Etc/GMT0 are old names and + * are linked to Etc/GMT or Etc/UTC. + * + * @param string $formattedTimeZone A GMT timezone string (GMT-03:00, e.g.) + * + * @return string A timezone identifier + * + * @see https://php.net/timezones.others + * + * @throws NotImplementedException When the GMT time zone have minutes offset different than zero + * @throws \InvalidArgumentException When the value can not be matched with pattern + */ + public static function getEtcTimeZoneId(string $formattedTimeZone): string + { + if (preg_match('/GMT(?P[+-])(?P\d{2}):?(?P\d{2})/', $formattedTimeZone, $matches)) { + $hours = (int) $matches['hours']; + $minutes = (int) $matches['minutes']; + $signal = '-' === $matches['signal'] ? '+' : '-'; + + if (0 < $minutes) { + throw new NotImplementedException(sprintf('It is not possible to use a GMT time zone with minutes offset different than zero (0). GMT time zone tried: "%s".', $formattedTimeZone)); + } + + return 'Etc/GMT'.(0 !== $hours ? $signal.$hours : ''); + } + + throw new \InvalidArgumentException(sprintf('The GMT time zone "%s" does not match with the supported formats GMT[+-]HH:MM or GMT[+-]HHMM.', $formattedTimeZone)); + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/Transformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/Transformer.php new file mode 100644 index 0000000..1a9a3ef --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/Transformer.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for date formats. + * + * @author Igor Wiedler + * + * @internal + */ +abstract class Transformer +{ + /** + * Format a value using a configured DateTime as date/time source. + * + * @param \DateTime $dateTime A DateTime object to be used to generate the formatted value + * @param int $length The formatted value string length + * + * @return string The formatted value + */ + abstract public function format(\DateTime $dateTime, int $length): string; + + /** + * Returns a reverse matching regular expression of a string generated by format(). + * + * @param int $length The length of the value to be reverse matched + * + * @return string The reverse matching regular expression + */ + abstract public function getReverseMatchingRegExp(int $length): string; + + /** + * Extract date options from a matched value returned by the processing of the reverse matching + * regular expression. + * + * @param string $matched The matched value + * @param int $length The length of the Transformer pattern string + * + * @return array An associative array + */ + abstract public function extractDateOptions(string $matched, int $length): array; + + /** + * Pad a string with zeros to the left. + * + * @param string $value The string to be padded + * @param int $length The length to pad + * + * @return string The padded string + */ + protected function padLeft(string $value, int $length): string + { + return str_pad($value, $length, '0', STR_PAD_LEFT); + } +} diff --git a/vendor/symfony/intl/DateFormatter/DateFormat/YearTransformer.php b/vendor/symfony/intl/DateFormatter/DateFormat/YearTransformer.php new file mode 100644 index 0000000..8718094 --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/DateFormat/YearTransformer.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter\DateFormat; + +/** + * Parser and formatter for year format. + * + * @author Igor Wiedler + * + * @internal + */ +class YearTransformer extends Transformer +{ + /** + * {@inheritdoc} + */ + public function format(\DateTime $dateTime, int $length): string + { + if (2 === $length) { + return $dateTime->format('y'); + } + + return $this->padLeft($dateTime->format('Y'), $length); + } + + /** + * {@inheritdoc} + */ + public function getReverseMatchingRegExp(int $length): string + { + return 2 === $length ? '\d{2}' : '\d{1,4}'; + } + + /** + * {@inheritdoc} + */ + public function extractDateOptions(string $matched, int $length): array + { + return [ + 'year' => (int) $matched, + ]; + } +} diff --git a/vendor/symfony/intl/DateFormatter/IntlDateFormatter.php b/vendor/symfony/intl/DateFormatter/IntlDateFormatter.php new file mode 100644 index 0000000..fee4f0a --- /dev/null +++ b/vendor/symfony/intl/DateFormatter/IntlDateFormatter.php @@ -0,0 +1,615 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\DateFormatter; + +use Symfony\Component\Intl\DateFormatter\DateFormat\FullTransformer; +use Symfony\Component\Intl\Exception\MethodArgumentNotImplementedException; +use Symfony\Component\Intl\Exception\MethodArgumentValueNotImplementedException; +use Symfony\Component\Intl\Exception\MethodNotImplementedException; +use Symfony\Component\Intl\Globals\IntlGlobals; +use Symfony\Component\Intl\Locale\Locale; + +/** + * Replacement for PHP's native {@link \IntlDateFormatter} class. + * + * The only methods currently supported in this class are: + * + * - {@link __construct} + * - {@link create} + * - {@link format} + * - {@link getCalendar} + * - {@link getDateType} + * - {@link getErrorCode} + * - {@link getErrorMessage} + * - {@link getLocale} + * - {@link getPattern} + * - {@link getTimeType} + * - {@link getTimeZoneId} + * - {@link isLenient} + * - {@link parse} + * - {@link setLenient} + * - {@link setPattern} + * - {@link setTimeZoneId} + * - {@link setTimeZone} + * + * @author Igor Wiedler + * @author Bernhard Schussek + * + * @internal + */ +abstract class IntlDateFormatter +{ + /** + * The error code from the last operation. + * + * @var int + */ + protected $errorCode = IntlGlobals::U_ZERO_ERROR; + + /** + * The error message from the last operation. + * + * @var string + */ + protected $errorMessage = 'U_ZERO_ERROR'; + + /* date/time format types */ + const NONE = -1; + const FULL = 0; + const LONG = 1; + const MEDIUM = 2; + const SHORT = 3; + + /* calendar formats */ + const TRADITIONAL = 0; + const GREGORIAN = 1; + + /** + * Patterns used to format the date when no pattern is provided. + */ + private $defaultDateFormats = [ + self::NONE => '', + self::FULL => 'EEEE, MMMM d, y', + self::LONG => 'MMMM d, y', + self::MEDIUM => 'MMM d, y', + self::SHORT => 'M/d/yy', + ]; + + /** + * Patterns used to format the time when no pattern is provided. + */ + private $defaultTimeFormats = [ + self::FULL => 'h:mm:ss a zzzz', + self::LONG => 'h:mm:ss a z', + self::MEDIUM => 'h:mm:ss a', + self::SHORT => 'h:mm a', + ]; + + private $datetype; + private $timetype; + + /** + * @var string + */ + private $pattern; + + /** + * @var \DateTimeZone + */ + private $dateTimeZone; + + /** + * @var bool + */ + private $uninitializedTimeZoneId = false; + + /** + * @var string + */ + private $timeZoneId; + + /** + * @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en") + * @param int|null $datetype Type of date formatting, one of the format type constants + * @param int|null $timetype Type of time formatting, one of the format type constants + * @param \IntlTimeZone|\DateTimeZone|string|null $timezone Timezone identifier + * @param int $calendar Calendar to use for formatting or parsing. The only currently + * supported value is IntlDateFormatter::GREGORIAN (or null using the default calendar, i.e. "GREGORIAN") + * @param string|null $pattern Optional pattern to use when formatting + * + * @see https://php.net/intldateformatter.create + * @see http://userguide.icu-project.org/formatparse/datetime + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + * @throws MethodArgumentValueNotImplementedException When $calendar different than GREGORIAN is passed + */ + public function __construct(?string $locale, ?int $datetype, ?int $timetype, $timezone = null, ?int $calendar = self::GREGORIAN, string $pattern = null) + { + if ('en' !== $locale && null !== $locale) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'locale', $locale, 'Only the locale "en" is supported'); + } + + if (self::GREGORIAN !== $calendar && null !== $calendar) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'calendar', $calendar, 'Only the GREGORIAN calendar is supported'); + } + + $this->datetype = null !== $datetype ? $datetype : self::FULL; + $this->timetype = null !== $timetype ? $timetype : self::FULL; + + $this->setPattern($pattern); + $this->setTimeZone($timezone); + } + + /** + * Static constructor. + * + * @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en") + * @param int|null $datetype Type of date formatting, one of the format type constants + * @param int|null $timetype Type of time formatting, one of the format type constants + * @param \IntlTimeZone|\DateTimeZone|string|null $timezone Timezone identifier + * @param int $calendar Calendar to use for formatting or parsing; default is Gregorian + * One of the calendar constants + * @param string|null $pattern Optional pattern to use when formatting + * + * @return static + * + * @see https://php.net/intldateformatter.create + * @see http://userguide.icu-project.org/formatparse/datetime + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + * @throws MethodArgumentValueNotImplementedException When $calendar different than GREGORIAN is passed + */ + public static function create(?string $locale, ?int $datetype, ?int $timetype, $timezone = null, int $calendar = self::GREGORIAN, ?string $pattern = null) + { + return new static($locale, $datetype, $timetype, $timezone, $calendar, $pattern); + } + + /** + * Format the date/time value (timestamp) as a string. + * + * @param int|\DateTimeInterface $timestamp The timestamp to format + * + * @return string|bool The formatted value or false if formatting failed + * + * @see https://php.net/intldateformatter.format + * + * @throws MethodArgumentValueNotImplementedException If one of the formatting characters is not implemented + */ + public function format($timestamp) + { + // intl allows timestamps to be passed as arrays - we don't + if (\is_array($timestamp)) { + $message = 'Only integer Unix timestamps and DateTime objects are supported'; + + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'timestamp', $timestamp, $message); + } + + // behave like the intl extension + $argumentError = null; + if (!\is_int($timestamp) && !$timestamp instanceof \DateTimeInterface) { + $argumentError = sprintf('datefmt_format: string \'%s\' is not numeric, which would be required for it to be a valid date', $timestamp); + } + + if (null !== $argumentError) { + IntlGlobals::setError(IntlGlobals::U_ILLEGAL_ARGUMENT_ERROR, $argumentError); + $this->errorCode = IntlGlobals::getErrorCode(); + $this->errorMessage = IntlGlobals::getErrorMessage(); + + return false; + } + + if ($timestamp instanceof \DateTimeInterface) { + $timestamp = $timestamp->getTimestamp(); + } + + $transformer = new FullTransformer($this->getPattern(), $this->getTimeZoneId()); + $formatted = $transformer->format($this->createDateTime($timestamp)); + + // behave like the intl extension + IntlGlobals::setError(IntlGlobals::U_ZERO_ERROR); + $this->errorCode = IntlGlobals::getErrorCode(); + $this->errorMessage = IntlGlobals::getErrorMessage(); + + return $formatted; + } + + /** + * Not supported. Formats an object. + * + * @param mixed $format + * @param string $locale + * + * @return string The formatted value + * + * @see https://php.net/intldateformatter.formatobject + * + * @throws MethodNotImplementedException + */ + public function formatObject(object $object, $format = null, string $locale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns the formatter's calendar. + * + * @return int The calendar being used by the formatter. Currently always returns + * IntlDateFormatter::GREGORIAN. + * + * @see https://php.net/intldateformatter.getcalendar + */ + public function getCalendar() + { + return self::GREGORIAN; + } + + /** + * Not supported. Returns the formatter's calendar object. + * + * @return object The calendar's object being used by the formatter + * + * @see https://php.net/intldateformatter.getcalendarobject + * + * @throws MethodNotImplementedException + */ + public function getCalendarObject() + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns the formatter's datetype. + * + * @return int The current value of the formatter + * + * @see https://php.net/intldateformatter.getdatetype + */ + public function getDateType() + { + return $this->datetype; + } + + /** + * Returns formatter's last error code. Always returns the U_ZERO_ERROR class constant value. + * + * @return int The error code from last formatter call + * + * @see https://php.net/intldateformatter.geterrorcode + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * Returns formatter's last error message. Always returns the U_ZERO_ERROR_MESSAGE class constant value. + * + * @return string The error message from last formatter call + * + * @see https://php.net/intldateformatter.geterrormessage + */ + public function getErrorMessage() + { + return $this->errorMessage; + } + + /** + * Returns the formatter's locale. + * + * @param int $type Not supported. The locale name type to return (Locale::VALID_LOCALE or Locale::ACTUAL_LOCALE) + * + * @return string The locale used to create the formatter. Currently always + * returns "en". + * + * @see https://php.net/intldateformatter.getlocale + */ + public function getLocale(int $type = Locale::ACTUAL_LOCALE) + { + return 'en'; + } + + /** + * Returns the formatter's pattern. + * + * @return string The pattern string used by the formatter + * + * @see https://php.net/intldateformatter.getpattern + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * Returns the formatter's time type. + * + * @return int The time type used by the formatter + * + * @see https://php.net/intldateformatter.gettimetype + */ + public function getTimeType() + { + return $this->timetype; + } + + /** + * Returns the formatter's timezone identifier. + * + * @return string The timezone identifier used by the formatter + * + * @see https://php.net/intldateformatter.gettimezoneid + */ + public function getTimeZoneId() + { + if (!$this->uninitializedTimeZoneId) { + return $this->timeZoneId; + } + + return date_default_timezone_get(); + } + + /** + * Not supported. Returns the formatter's timezone. + * + * @return mixed The timezone used by the formatter + * + * @see https://php.net/intldateformatter.gettimezone + * + * @throws MethodNotImplementedException + */ + public function getTimeZone() + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns whether the formatter is lenient. + * + * @return bool Currently always returns false + * + * @see https://php.net/intldateformatter.islenient + * + * @throws MethodNotImplementedException + */ + public function isLenient() + { + return false; + } + + /** + * Not supported. Parse string to a field-based time value. + * + * @param string $value String to convert to a time value + * @param int $position Position at which to start the parsing in $value (zero-based) + * If no error occurs before $value is consumed, $parse_pos will + * contain -1 otherwise it will contain the position at which parsing + * ended. If $parse_pos > strlen($value), the parse fails immediately. + * + * @return string Localtime compatible array of integers: contains 24 hour clock value in tm_hour field + * + * @see https://php.net/intldateformatter.localtime + * + * @throws MethodNotImplementedException + */ + public function localtime(string $value, int &$position = 0) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Parse string to a timestamp value. + * + * @param string $value String to convert to a time value + * @param int $position Not supported. Position at which to start the parsing in $value (zero-based) + * If no error occurs before $value is consumed, $parse_pos will + * contain -1 otherwise it will contain the position at which parsing + * ended. If $parse_pos > strlen($value), the parse fails immediately. + * + * @return int|false Parsed value as a timestamp + * + * @see https://php.net/intldateformatter.parse + * + * @throws MethodArgumentNotImplementedException When $position different than null, behavior not implemented + */ + public function parse(string $value, int &$position = null) + { + // We don't calculate the position when parsing the value + if (null !== $position) { + throw new MethodArgumentNotImplementedException(__METHOD__, 'position'); + } + + $dateTime = $this->createDateTime(0); + $transformer = new FullTransformer($this->getPattern(), $this->getTimeZoneId()); + + $timestamp = $transformer->parse($dateTime, $value); + + // behave like the intl extension. FullTransformer::parse() set the proper error + $this->errorCode = IntlGlobals::getErrorCode(); + $this->errorMessage = IntlGlobals::getErrorMessage(); + + return $timestamp; + } + + /** + * Not supported. Set the formatter's calendar. + * + * @param string $calendar The calendar to use. Default is IntlDateFormatter::GREGORIAN + * + * @return bool true on success or false on failure + * + * @see https://php.net/intldateformatter.setcalendar + * + * @throws MethodNotImplementedException + */ + public function setCalendar(string $calendar) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Set the leniency of the parser. + * + * Define if the parser is strict or lenient in interpreting inputs that do not match the pattern + * exactly. Enabling lenient parsing allows the parser to accept otherwise flawed date or time + * patterns, parsing as much as possible to obtain a value. Extra space, unrecognized tokens, or + * invalid values ("February 30th") are not accepted. + * + * @param bool $lenient Sets whether the parser is lenient or not. Currently + * only false (strict) is supported. + * + * @return bool true on success or false on failure + * + * @see https://php.net/intldateformatter.setlenient + * + * @throws MethodArgumentValueNotImplementedException When $lenient is true + */ + public function setLenient(bool $lenient) + { + if ($lenient) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'lenient', $lenient, 'Only the strict parser is supported'); + } + + return true; + } + + /** + * Set the formatter's pattern. + * + * @param string|null $pattern A pattern string in conformance with the ICU IntlDateFormatter documentation + * + * @return bool true on success or false on failure + * + * @see https://php.net/intldateformatter.setpattern + * @see http://userguide.icu-project.org/formatparse/datetime + */ + public function setPattern(?string $pattern) + { + if (null === $pattern) { + $pattern = $this->getDefaultPattern(); + } + + $this->pattern = $pattern; + + return true; + } + + /** + * Set the formatter's timezone identifier. + * + * @param string|null $timeZoneId The time zone ID string of the time zone to use. + * If NULL or the empty string, the default time zone for the + * runtime is used. + * + * @return bool true on success or false on failure + * + * @see https://php.net/intldateformatter.settimezoneid + */ + public function setTimeZoneId(?string $timeZoneId) + { + if (null === $timeZoneId) { + $timeZoneId = date_default_timezone_get(); + + $this->uninitializedTimeZoneId = true; + } + + // Backup original passed time zone + $timeZone = $timeZoneId; + + // Get an Etc/GMT time zone that is accepted for \DateTimeZone + if ('GMT' !== $timeZoneId && 0 === strpos($timeZoneId, 'GMT')) { + try { + $timeZoneId = DateFormat\TimezoneTransformer::getEtcTimeZoneId($timeZoneId); + } catch (\InvalidArgumentException $e) { + // Does nothing, will fallback to UTC + } + } + + try { + $this->dateTimeZone = new \DateTimeZone($timeZoneId); + if ('GMT' !== $timeZoneId && $this->dateTimeZone->getName() !== $timeZoneId) { + $timeZone = $this->getTimeZoneId(); + } + } catch (\Exception $e) { + $timeZoneId = $timeZone = $this->getTimeZoneId(); + $this->dateTimeZone = new \DateTimeZone($timeZoneId); + } + + $this->timeZoneId = $timeZone; + + return true; + } + + /** + * This method was added in PHP 5.5 as replacement for `setTimeZoneId()`. + * + * @param \IntlTimeZone|\DateTimeZone|string|null $timeZone + * + * @return bool true on success or false on failure + * + * @see https://php.net/intldateformatter.settimezone + */ + public function setTimeZone($timeZone) + { + if ($timeZone instanceof \IntlTimeZone) { + $timeZone = $timeZone->getID(); + } + + if ($timeZone instanceof \DateTimeZone) { + $timeZone = $timeZone->getName(); + + // DateTimeZone returns the GMT offset timezones without the leading GMT, while our parsing requires it. + if (!empty($timeZone) && ('+' === $timeZone[0] || '-' === $timeZone[0])) { + $timeZone = 'GMT'.$timeZone; + } + } + + return $this->setTimeZoneId($timeZone); + } + + /** + * Create and returns a DateTime object with the specified timestamp and with the + * current time zone. + * + * @return \DateTime + */ + protected function createDateTime(int $timestamp) + { + $dateTime = new \DateTime(); + $dateTime->setTimestamp($timestamp); + $dateTime->setTimezone($this->dateTimeZone); + + return $dateTime; + } + + /** + * Returns a pattern string based in the datetype and timetype values. + * + * @return string + */ + protected function getDefaultPattern() + { + $pattern = ''; + if (self::NONE !== $this->datetype) { + $pattern = $this->defaultDateFormats[$this->datetype]; + } + if (self::NONE !== $this->timetype) { + if (self::FULL === $this->datetype || self::LONG === $this->datetype) { + $pattern .= ' \'at\' '; + } elseif (self::NONE !== $this->datetype) { + $pattern .= ', '; + } + $pattern .= $this->defaultTimeFormats[$this->timetype]; + } + + return $pattern; + } +} diff --git a/vendor/symfony/intl/Exception/BadMethodCallException.php b/vendor/symfony/intl/Exception/BadMethodCallException.php new file mode 100644 index 0000000..ca79729 --- /dev/null +++ b/vendor/symfony/intl/Exception/BadMethodCallException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Base BadMethodCallException for the Intl component. + * + * @author Bernhard Schussek + */ +class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/intl/Exception/ExceptionInterface.php b/vendor/symfony/intl/Exception/ExceptionInterface.php new file mode 100644 index 0000000..0a73682 --- /dev/null +++ b/vendor/symfony/intl/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Base ExceptionInterface for the Intl component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/intl/Exception/InvalidArgumentException.php b/vendor/symfony/intl/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..10f69ee --- /dev/null +++ b/vendor/symfony/intl/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * InvalidArgumentException for the Intl component. + * + * @author Bernhard Schussek + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/intl/Exception/MethodArgumentNotImplementedException.php b/vendor/symfony/intl/Exception/MethodArgumentNotImplementedException.php new file mode 100644 index 0000000..bd6e479 --- /dev/null +++ b/vendor/symfony/intl/Exception/MethodArgumentNotImplementedException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * @author Eriksen Costa + */ +class MethodArgumentNotImplementedException extends NotImplementedException +{ + /** + * @param string $methodName The method name that raised the exception + * @param string $argName The argument name that is not implemented + */ + public function __construct(string $methodName, string $argName) + { + $message = sprintf('The %s() method\'s argument $%s behavior is not implemented.', $methodName, $argName); + parent::__construct($message); + } +} diff --git a/vendor/symfony/intl/Exception/MethodArgumentValueNotImplementedException.php b/vendor/symfony/intl/Exception/MethodArgumentValueNotImplementedException.php new file mode 100644 index 0000000..ee9ebe5 --- /dev/null +++ b/vendor/symfony/intl/Exception/MethodArgumentValueNotImplementedException.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * @author Eriksen Costa + */ +class MethodArgumentValueNotImplementedException extends NotImplementedException +{ + /** + * @param string $methodName The method name that raised the exception + * @param string $argName The argument name + * @param mixed $argValue The argument value that is not implemented + * @param string $additionalMessage An optional additional message to append to the exception message + */ + public function __construct(string $methodName, string $argName, $argValue, string $additionalMessage = '') + { + $message = sprintf( + 'The %s() method\'s argument $%s value %s behavior is not implemented.%s', + $methodName, + $argName, + var_export($argValue, true), + '' !== $additionalMessage ? ' '.$additionalMessage.'. ' : '' + ); + + parent::__construct($message); + } +} diff --git a/vendor/symfony/intl/Exception/MethodNotImplementedException.php b/vendor/symfony/intl/Exception/MethodNotImplementedException.php new file mode 100644 index 0000000..6a45d71 --- /dev/null +++ b/vendor/symfony/intl/Exception/MethodNotImplementedException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * @author Eriksen Costa + */ +class MethodNotImplementedException extends NotImplementedException +{ + /** + * @param string $methodName The name of the method + */ + public function __construct(string $methodName) + { + parent::__construct(sprintf('The %s() is not implemented.', $methodName)); + } +} diff --git a/vendor/symfony/intl/Exception/MissingResourceException.php b/vendor/symfony/intl/Exception/MissingResourceException.php new file mode 100644 index 0000000..e2eb3f2 --- /dev/null +++ b/vendor/symfony/intl/Exception/MissingResourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Thrown when an invalid entry of a resource bundle was requested. + * + * @author Bernhard Schussek + */ +class MissingResourceException extends RuntimeException +{ +} diff --git a/vendor/symfony/intl/Exception/NotImplementedException.php b/vendor/symfony/intl/Exception/NotImplementedException.php new file mode 100644 index 0000000..1413886 --- /dev/null +++ b/vendor/symfony/intl/Exception/NotImplementedException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Base exception class for not implemented behaviors of the intl extension in the Locale component. + * + * @author Eriksen Costa + */ +class NotImplementedException extends RuntimeException +{ + const INTL_INSTALL_MESSAGE = 'Please install the "intl" extension for full localization capabilities.'; + + /** + * @param string $message The exception message. A note to install the intl extension is appended to this string + */ + public function __construct(string $message) + { + parent::__construct($message.' '.self::INTL_INSTALL_MESSAGE); + } +} diff --git a/vendor/symfony/intl/Exception/OutOfBoundsException.php b/vendor/symfony/intl/Exception/OutOfBoundsException.php new file mode 100644 index 0000000..2727141 --- /dev/null +++ b/vendor/symfony/intl/Exception/OutOfBoundsException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Base OutOfBoundsException for the Intl component. + * + * @author Bernhard Schussek + */ +class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/intl/Exception/ResourceBundleNotFoundException.php b/vendor/symfony/intl/Exception/ResourceBundleNotFoundException.php new file mode 100644 index 0000000..59da5ec --- /dev/null +++ b/vendor/symfony/intl/Exception/ResourceBundleNotFoundException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * @author Bernhard Schussek + */ +class ResourceBundleNotFoundException extends RuntimeException +{ +} diff --git a/vendor/symfony/intl/Exception/RuntimeException.php b/vendor/symfony/intl/Exception/RuntimeException.php new file mode 100644 index 0000000..9893714 --- /dev/null +++ b/vendor/symfony/intl/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * RuntimeException for the Intl component. + * + * @author Bernhard Schussek + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/intl/Exception/UnexpectedTypeException.php b/vendor/symfony/intl/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000..180d477 --- /dev/null +++ b/vendor/symfony/intl/Exception/UnexpectedTypeException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Thrown when a method argument had an unexpected type. + * + * @author Bernhard Schussek + */ +class UnexpectedTypeException extends InvalidArgumentException +{ + public function __construct($value, string $expectedType) + { + parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, get_debug_type($value))); + } +} diff --git a/vendor/symfony/intl/Globals/IntlGlobals.php b/vendor/symfony/intl/Globals/IntlGlobals.php new file mode 100644 index 0000000..d353368 --- /dev/null +++ b/vendor/symfony/intl/Globals/IntlGlobals.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Globals; + +/** + * Provides fake static versions of the global functions in the intl extension. + * + * @author Bernhard Schussek + * + * @internal + */ +abstract class IntlGlobals +{ + /** + * Indicates that no error occurred. + */ + const U_ZERO_ERROR = 0; + + /** + * Indicates that an invalid argument was passed. + */ + const U_ILLEGAL_ARGUMENT_ERROR = 1; + + /** + * Indicates that the parse() operation failed. + */ + const U_PARSE_ERROR = 9; + + /** + * All known error codes. + */ + private static $errorCodes = [ + self::U_ZERO_ERROR => 'U_ZERO_ERROR', + self::U_ILLEGAL_ARGUMENT_ERROR => 'U_ILLEGAL_ARGUMENT_ERROR', + self::U_PARSE_ERROR => 'U_PARSE_ERROR', + ]; + + /** + * The error code of the last operation. + */ + private static $errorCode = self::U_ZERO_ERROR; + + /** + * The error code of the last operation. + */ + private static $errorMessage = 'U_ZERO_ERROR'; + + /** + * Returns whether the error code indicates a failure. + * + * @param int $errorCode The error code returned by IntlGlobals::getErrorCode() + */ + public static function isFailure(int $errorCode): bool + { + return isset(self::$errorCodes[$errorCode]) + && $errorCode > self::U_ZERO_ERROR; + } + + /** + * Returns the error code of the last operation. + * + * Returns IntlGlobals::U_ZERO_ERROR if no error occurred. + * + * @return int + */ + public static function getErrorCode() + { + return self::$errorCode; + } + + /** + * Returns the error message of the last operation. + * + * Returns "U_ZERO_ERROR" if no error occurred. + */ + public static function getErrorMessage(): string + { + return self::$errorMessage; + } + + /** + * Returns the symbolic name for a given error code. + * + * @param int $code The error code returned by IntlGlobals::getErrorCode() + */ + public static function getErrorName(int $code): string + { + return self::$errorCodes[$code] ?? '[BOGUS UErrorCode]'; + } + + /** + * Sets the current error. + * + * @param int $code One of the error constants in this class + * @param string $message The ICU class error message + * + * @throws \InvalidArgumentException If the code is not one of the error constants in this class + */ + public static function setError(int $code, string $message = '') + { + if (!isset(self::$errorCodes[$code])) { + throw new \InvalidArgumentException(sprintf('No such error code: "%s".', $code)); + } + + self::$errorMessage = $message ? sprintf('%s: %s', $message, self::$errorCodes[$code]) : self::$errorCodes[$code]; + self::$errorCode = $code; + } +} diff --git a/vendor/symfony/intl/Intl.php b/vendor/symfony/intl/Intl.php new file mode 100644 index 0000000..eb3bd58 --- /dev/null +++ b/vendor/symfony/intl/Intl.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +/** + * Gives access to internationalization data. + * + * @author Bernhard Schussek + */ +final class Intl +{ + /** + * The number of resource bundles to buffer. Loading the same resource + * bundle for n locales takes up n spots in the buffer. + */ + const BUFFER_SIZE = 10; + + /** + * The directory name of the currency data. + */ + const CURRENCY_DIR = 'currencies'; + + /** + * The directory name of the language data. + */ + const LANGUAGE_DIR = 'languages'; + + /** + * The directory name of the script data. + */ + const SCRIPT_DIR = 'scripts'; + + /** + * The directory name of the locale data. + */ + const LOCALE_DIR = 'locales'; + + /** + * The directory name of the region data. + */ + const REGION_DIR = 'regions'; + + /** + * The directory name of the zone data. + */ + public const TIMEZONE_DIR = 'timezones'; + + /** + * @var string|bool|null + */ + private static $icuVersion = false; + + /** + * @var string + */ + private static $icuDataVersion = false; + + /** + * Returns whether the intl extension is installed. + * + * @return bool Returns true if the intl extension is installed, false otherwise + */ + public static function isExtensionLoaded(): bool + { + return class_exists('\ResourceBundle'); + } + + /** + * Returns the version of the installed ICU library. + * + * @return string|null The ICU version or NULL if it could not be determined + */ + public static function getIcuVersion(): ?string + { + if (false === self::$icuVersion) { + if (!self::isExtensionLoaded()) { + self::$icuVersion = self::getIcuStubVersion(); + } elseif (\defined('INTL_ICU_VERSION')) { + self::$icuVersion = INTL_ICU_VERSION; + } else { + try { + $reflector = new \ReflectionExtension('intl'); + ob_start(); + $reflector->info(); + $output = strip_tags(ob_get_clean()); + preg_match('/^ICU version (?:=>)?(.*)$/m', $output, $matches); + + self::$icuVersion = trim($matches[1]); + } catch (\ReflectionException $e) { + self::$icuVersion = null; + } + } + } + + return self::$icuVersion; + } + + /** + * Returns the version of the installed ICU data. + * + * @return string The version of the installed ICU data + */ + public static function getIcuDataVersion(): string + { + if (false === self::$icuDataVersion) { + self::$icuDataVersion = trim(file_get_contents(self::getDataDirectory().'/version.txt')); + } + + return self::$icuDataVersion; + } + + /** + * Returns the ICU version that the stub classes mimic. + * + * @return string The ICU version of the stub classes + */ + public static function getIcuStubVersion(): string + { + return '67.1'; + } + + /** + * Returns the absolute path to the data directory. + * + * @return string The absolute path to the data directory + */ + public static function getDataDirectory(): string + { + return __DIR__.'/Resources/data'; + } + + /** + * This class must not be instantiated. + */ + private function __construct() + { + } +} diff --git a/vendor/symfony/intl/Languages.php b/vendor/symfony/intl/Languages.php new file mode 100644 index 0000000..847b07a --- /dev/null +++ b/vendor/symfony/intl/Languages.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +use Symfony\Component\Intl\Exception\MissingResourceException; + +/** + * Gives access to language-related ICU data. + * + * @author Bernhard Schussek + * @author Roland Franssen + */ +final class Languages extends ResourceBundle +{ + /** + * Returns all available languages as two-letter codes. + * + * Languages are returned as lowercase ISO 639-1 two-letter language codes. + * For languages that don't have a two-letter code, the ISO 639-2 + * three-letter code is used instead. + * + * A full table of ISO 639 language codes can be found here: + * http://www-01.sil.org/iso639-3/codes.asp + * + * @return string[] an array of canonical ISO 639-1 language codes + */ + public static function getLanguageCodes(): array + { + return self::readEntry(['Languages'], 'meta'); + } + + public static function exists(string $language): bool + { + try { + self::readEntry(['Names', $language]); + + return true; + } catch (MissingResourceException $e) { + return false; + } + } + + /** + * Gets the language name from its alpha2 code. + * + * A full locale may be passed to obtain a more localized language name, e.g. "American English" for "en_US". + * + * @throws MissingResourceException if the language code does not exist + */ + public static function getName(string $language, string $displayLocale = null): string + { + try { + return self::readEntry(['Names', $language], $displayLocale); + } catch (MissingResourceException $e) { + try { + return self::readEntry(['LocalizedNames', $language], $displayLocale); + } catch (MissingResourceException $e) { + if (false !== $i = strrpos($language, '_')) { + return self::getName(substr($language, 0, $i), $displayLocale); + } + + throw $e; + } + } + } + + /** + * Gets the list of language names indexed with alpha2 codes as keys. + * + * @return string[] + */ + public static function getNames(string $displayLocale = null): array + { + return self::asort(self::readEntry(['Names'], $displayLocale), $displayLocale); + } + + /** + * Returns the ISO 639-2 three-letter code of a language, given a two-letter code. + * + * @throws MissingResourceException if the language has no corresponding three-letter code + */ + public static function getAlpha3Code(string $language): string + { + return self::readEntry(['Alpha2ToAlpha3', $language], 'meta'); + } + + /** + * Returns the ISO 639-1 two-letter code of a language, given a three letter code. + * + * @throws MissingResourceException if the language has no corresponding three-letter code + */ + public static function getAlpha2Code(string $language): string + { + return self::readEntry(['Alpha3ToAlpha2', $language], 'meta'); + } + + /** + * Returns all available languages as three-letter codes. + * + * Languages are returned as lowercase ISO 639-2 three-letter language codes. + * + * @return string[] an array of canonical ISO 639-2 language codes + */ + public static function getAlpha3Codes(): array + { + return self::readEntry(['Alpha3Languages'], 'meta'); + } + + /** + * @param string $language ISO 639-2 three-letter language code + */ + public static function alpha3CodeExists(string $language): bool + { + try { + self::getAlpha2Code($language); + + return true; + } catch (MissingResourceException $e) { + static $cache; + if (null === $cache) { + $cache = array_flip(self::getAlpha3Codes()); + } + + return isset($cache[$language]); + } + } + + /** + * Gets the language name from its ISO 639-2 three-letter code. + * + * @throws MissingResourceException if the country code does not exists + */ + public static function getAlpha3Name(string $language, string $displayLocale = null): string + { + try { + return self::getName(self::getAlpha2Code($language), $displayLocale); + } catch (MissingResourceException $e) { + if (3 === \strlen($language)) { + return self::getName($language, $displayLocale); + } + + throw $e; + } + } + + /** + * Gets the list of language names indexed with ISO 639-2 three-letter codes as keys. + * + * Same as method getNames, but with ISO 639-2 three-letter codes instead of ISO 639-1 codes as keys. + * + * @return string[] + */ + public static function getAlpha3Names($displayLocale = null): array + { + $alpha2Names = self::getNames($displayLocale); + $alpha3Names = []; + foreach ($alpha2Names as $alpha2Code => $name) { + if (3 === \strlen($alpha2Code)) { + $alpha3Names[$alpha2Code] = $name; + continue; + } + try { + $alpha3Names[self::getAlpha3Code($alpha2Code)] = $name; + } catch (MissingResourceException $e) { + } + } + + return $alpha3Names; + } + + protected static function getPath(): string + { + return Intl::getDataDirectory().'/'.Intl::LANGUAGE_DIR; + } +} diff --git a/vendor/symfony/intl/Locale.php b/vendor/symfony/intl/Locale.php new file mode 100644 index 0000000..8cc457b --- /dev/null +++ b/vendor/symfony/intl/Locale.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +/** + * Provides access to locale-related data. + * + * @author Bernhard Schussek + * + * @internal + */ +final class Locale extends \Locale +{ + /** + * @var string|null + */ + private static $defaultFallback = 'en'; + + /** + * Sets the default fallback locale. + * + * The default fallback locale is used as fallback for locales that have no + * fallback otherwise. + * + * @param string|null $locale The default fallback locale + * + * @see getFallback() + */ + public static function setDefaultFallback(?string $locale) + { + self::$defaultFallback = $locale; + } + + /** + * Returns the default fallback locale. + * + * @return string|null The default fallback locale + * + * @see setDefaultFallback() + * @see getFallback() + */ + public static function getDefaultFallback(): ?string + { + return self::$defaultFallback; + } + + /** + * Returns the fallback locale for a given locale. + * + * For example, the fallback of "fr_FR" is "fr". The fallback of "fr" is + * the default fallback locale configured with {@link setDefaultFallback()}. + * The default fallback locale has no fallback. + * + * @return string|null The ICU locale code of the fallback locale, or null + * if no fallback exists + */ + public static function getFallback(string $locale): ?string + { + if (\function_exists('locale_parse')) { + $localeSubTags = locale_parse($locale); + if (1 === \count($localeSubTags)) { + if ('root' !== self::$defaultFallback && self::$defaultFallback === $localeSubTags['language']) { + return 'root'; + } + + // Don't return default fallback for "root", "meta" or others + // Normal locales have two or three letters + if (\strlen($locale) < 4) { + return self::$defaultFallback; + } + + return null; + } + + array_pop($localeSubTags); + + $fallback = locale_compose($localeSubTags); + + return false !== $fallback ? $fallback : null; + } + + if (false !== $pos = strrpos($locale, '_')) { + return substr($locale, 0, $pos); + } + + if (false !== $pos = strrpos($locale, '-')) { + return substr($locale, 0, $pos); + } + + if ('root' !== self::$defaultFallback && self::$defaultFallback === $locale) { + return 'root'; + } + + // Don't return default fallback for "root", "meta" or others + // Normal locales have two or three letters + return \strlen($locale) < 4 ? self::$defaultFallback : null; + } + + /** + * This class must not be instantiated. + */ + private function __construct() + { + } +} diff --git a/vendor/symfony/intl/Locale/Locale.php b/vendor/symfony/intl/Locale/Locale.php new file mode 100644 index 0000000..6fbe50e --- /dev/null +++ b/vendor/symfony/intl/Locale/Locale.php @@ -0,0 +1,349 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Locale; + +use Symfony\Component\Intl\Exception\MethodNotImplementedException; + +/** + * Replacement for PHP's native {@link \Locale} class. + * + * The only methods supported in this class are `getDefault` and `canonicalize`. + * All other methods will throw an exception when used. + * + * @author Eriksen Costa + * @author Bernhard Schussek + * + * @internal + */ +abstract class Locale +{ + const DEFAULT_LOCALE = null; + + /* Locale method constants */ + const ACTUAL_LOCALE = 0; + const VALID_LOCALE = 1; + + /* Language tags constants */ + const LANG_TAG = 'language'; + const EXTLANG_TAG = 'extlang'; + const SCRIPT_TAG = 'script'; + const REGION_TAG = 'region'; + const VARIANT_TAG = 'variant'; + const GRANDFATHERED_LANG_TAG = 'grandfathered'; + const PRIVATE_TAG = 'private'; + + /** + * Not supported. Returns the best available locale based on HTTP "Accept-Language" header according to RFC 2616. + * + * @param string $header The string containing the "Accept-Language" header value + * + * @return string The corresponding locale code + * + * @see https://php.net/locale.acceptfromhttp + * + * @throws MethodNotImplementedException + */ + public static function acceptFromHttp(string $header) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns a canonicalized locale string. + * + * This polyfill doesn't implement the full-spec algorithm. It only + * canonicalizes locale strings handled by the `LocaleBundle` class. + * + * @return string + */ + public static function canonicalize(string $locale) + { + if ('' === $locale || '.' === $locale[0]) { + return self::getDefault(); + } + + if (!preg_match('/^([a-z]{2})[-_]([a-z]{2})(?:([a-z]{2})(?:[-_]([a-z]{2}))?)?(?:\..*)?$/i', $locale, $m)) { + return $locale; + } + + if (!empty($m[4])) { + return strtolower($m[1]).'_'.ucfirst(strtolower($m[2].$m[3])).'_'.strtoupper($m[4]); + } + + if (!empty($m[3])) { + return strtolower($m[1]).'_'.ucfirst(strtolower($m[2].$m[3])); + } + + return strtolower($m[1]).'_'.strtoupper($m[2]); + } + + /** + * Not supported. Returns a correctly ordered and delimited locale code. + * + * @param array $subtags A keyed array where the keys identify the particular locale code subtag + * + * @return string The corresponding locale code + * + * @see https://php.net/locale.composelocale + * + * @throws MethodNotImplementedException + */ + public static function composeLocale(array $subtags) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Checks if a language tag filter matches with locale. + * + * @param string $langtag The language tag to check + * @param string $locale The language range to check against + * + * @return string The corresponding locale code + * + * @see https://php.net/locale.filtermatches + * + * @throws MethodNotImplementedException + */ + public static function filterMatches(string $langtag, string $locale, bool $canonicalize = false) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the variants for the input locale. + * + * @param string $locale The locale to extract the variants from + * + * @return array The locale variants + * + * @see https://php.net/locale.getallvariants + * + * @throws MethodNotImplementedException + */ + public static function getAllVariants(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Returns the default locale. + * + * @return string The default locale code. Always returns 'en' + * + * @see https://php.net/locale.getdefault + */ + public static function getDefault() + { + return 'en'; + } + + /** + * Not supported. Returns the localized display name for the locale language. + * + * @param string $locale The locale code to return the display language from + * @param string $inLocale Optional format locale code to use to display the language name + * + * @return string The localized language display name + * + * @see https://php.net/locale.getdisplaylanguage + * + * @throws MethodNotImplementedException + */ + public static function getDisplayLanguage(string $locale, string $inLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the localized display name for the locale. + * + * @param string $locale The locale code to return the display locale name from + * @param string $inLocale Optional format locale code to use to display the locale name + * + * @return string The localized locale display name + * + * @see https://php.net/locale.getdisplayname + * + * @throws MethodNotImplementedException + */ + public static function getDisplayName(string $locale, string $inLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the localized display name for the locale region. + * + * @param string $locale The locale code to return the display region from + * @param string $inLocale Optional format locale code to use to display the region name + * + * @return string The localized region display name + * + * @see https://php.net/locale.getdisplayregion + * + * @throws MethodNotImplementedException + */ + public static function getDisplayRegion(string $locale, string $inLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the localized display name for the locale script. + * + * @param string $locale The locale code to return the display script from + * @param string $inLocale Optional format locale code to use to display the script name + * + * @return string The localized script display name + * + * @see https://php.net/locale.getdisplayscript + * + * @throws MethodNotImplementedException + */ + public static function getDisplayScript(string $locale, string $inLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the localized display name for the locale variant. + * + * @param string $locale The locale code to return the display variant from + * @param string $inLocale Optional format locale code to use to display the variant name + * + * @return string The localized variant display name + * + * @see https://php.net/locale.getdisplayvariant + * + * @throws MethodNotImplementedException + */ + public static function getDisplayVariant(string $locale, string $inLocale = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the keywords for the locale. + * + * @param string $locale The locale code to extract the keywords from + * + * @return array Associative array with the extracted variants + * + * @see https://php.net/locale.getkeywords + * + * @throws MethodNotImplementedException + */ + public static function getKeywords(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the primary language for the locale. + * + * @param string $locale The locale code to extract the language code from + * + * @return string|null The extracted language code or null in case of error + * + * @see https://php.net/locale.getprimarylanguage + * + * @throws MethodNotImplementedException + */ + public static function getPrimaryLanguage(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the region for the locale. + * + * @param string $locale The locale code to extract the region code from + * + * @return string|null The extracted region code or null if not present + * + * @see https://php.net/locale.getregion + * + * @throws MethodNotImplementedException + */ + public static function getRegion(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the script for the locale. + * + * @param string $locale The locale code to extract the script code from + * + * @return string|null The extracted script code or null if not present + * + * @see https://php.net/locale.getscript + * + * @throws MethodNotImplementedException + */ + public static function getScript(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns the closest language tag for the locale. + * + * @param array $langtag A list of the language tags to compare to locale + * @param string $locale The locale to use as the language range when matching + * @param bool $canonicalize If true, the arguments will be converted to canonical form before matching + * @param string $default The locale to use if no match is found + * + * @see https://php.net/locale.lookup + * + * @throws MethodNotImplementedException + */ + public static function lookup(array $langtag, string $locale, bool $canonicalize = false, string $default = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns an associative array of locale identifier subtags. + * + * @param string $locale The locale code to extract the subtag array from + * + * @return array Associative array with the extracted subtags + * + * @see https://php.net/locale.parselocale + * + * @throws MethodNotImplementedException + */ + public static function parseLocale(string $locale) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Sets the default runtime locale. + * + * @return bool true on success or false on failure + * + * @see https://php.net/locale.setdefault + * + * @throws MethodNotImplementedException + */ + public static function setDefault(string $locale) + { + if ('en' !== $locale) { + throw new MethodNotImplementedException(__METHOD__); + } + + return true; + } +} diff --git a/vendor/symfony/intl/Locales.php b/vendor/symfony/intl/Locales.php new file mode 100644 index 0000000..1b2d0d2 --- /dev/null +++ b/vendor/symfony/intl/Locales.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +use Symfony\Component\Intl\Exception\MissingResourceException; + +/** + * Gives access to locale-related ICU data. + * + * @author Bernhard Schussek + * @author Roland Franssen + */ +final class Locales extends ResourceBundle +{ + /** + * @return string[] + */ + public static function getLocales(): array + { + return self::readEntry(['Locales'], 'meta'); + } + + /** + * @return string[] + */ + public static function getAliases(): array + { + return self::readEntry(['Aliases'], 'meta'); + } + + public static function exists(string $locale): bool + { + try { + self::readEntry(['Names', $locale]); + + return true; + } catch (MissingResourceException $e) { + return \in_array($locale, self::getAliases(), true); + } + } + + /** + * @throws MissingResourceException if the locale does not exist + */ + public static function getName(string $locale, string $displayLocale = null): string + { + try { + return self::readEntry(['Names', $locale], $displayLocale); + } catch (MissingResourceException $e) { + if (false === $aliased = array_search($locale, self::getAliases(), true)) { + throw $e; + } + + return self::readEntry(['Names', $aliased], $displayLocale); + } + } + + /** + * @return string[] + */ + public static function getNames($displayLocale = null): array + { + return self::asort(self::readEntry(['Names'], $displayLocale), $displayLocale); + } + + protected static function getPath(): string + { + return Intl::getDataDirectory().'/'.Intl::LOCALE_DIR; + } +} diff --git a/vendor/symfony/intl/NumberFormatter/NumberFormatter.php b/vendor/symfony/intl/NumberFormatter/NumberFormatter.php new file mode 100644 index 0000000..aacd11b --- /dev/null +++ b/vendor/symfony/intl/NumberFormatter/NumberFormatter.php @@ -0,0 +1,854 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\NumberFormatter; + +use Symfony\Component\Intl\Currencies; +use Symfony\Component\Intl\Exception\MethodArgumentNotImplementedException; +use Symfony\Component\Intl\Exception\MethodArgumentValueNotImplementedException; +use Symfony\Component\Intl\Exception\MethodNotImplementedException; +use Symfony\Component\Intl\Exception\NotImplementedException; +use Symfony\Component\Intl\Globals\IntlGlobals; +use Symfony\Component\Intl\Locale\Locale; + +/** + * Replacement for PHP's native {@link \NumberFormatter} class. + * + * The only methods currently supported in this class are: + * + * - {@link __construct} + * - {@link create} + * - {@link formatCurrency} + * - {@link format} + * - {@link getAttribute} + * - {@link getErrorCode} + * - {@link getErrorMessage} + * - {@link getLocale} + * - {@link parse} + * - {@link setAttribute} + * + * @author Eriksen Costa + * @author Bernhard Schussek + * + * @internal + */ +abstract class NumberFormatter +{ + /* Format style constants */ + const PATTERN_DECIMAL = 0; + const DECIMAL = 1; + const CURRENCY = 2; + const PERCENT = 3; + const SCIENTIFIC = 4; + const SPELLOUT = 5; + const ORDINAL = 6; + const DURATION = 7; + const PATTERN_RULEBASED = 9; + const IGNORE = 0; + const DEFAULT_STYLE = 1; + + /* Format type constants */ + const TYPE_DEFAULT = 0; + const TYPE_INT32 = 1; + const TYPE_INT64 = 2; + const TYPE_DOUBLE = 3; + const TYPE_CURRENCY = 4; + + /* Numeric attribute constants */ + const PARSE_INT_ONLY = 0; + const GROUPING_USED = 1; + const DECIMAL_ALWAYS_SHOWN = 2; + const MAX_INTEGER_DIGITS = 3; + const MIN_INTEGER_DIGITS = 4; + const INTEGER_DIGITS = 5; + const MAX_FRACTION_DIGITS = 6; + const MIN_FRACTION_DIGITS = 7; + const FRACTION_DIGITS = 8; + const MULTIPLIER = 9; + const GROUPING_SIZE = 10; + const ROUNDING_MODE = 11; + const ROUNDING_INCREMENT = 12; + const FORMAT_WIDTH = 13; + const PADDING_POSITION = 14; + const SECONDARY_GROUPING_SIZE = 15; + const SIGNIFICANT_DIGITS_USED = 16; + const MIN_SIGNIFICANT_DIGITS = 17; + const MAX_SIGNIFICANT_DIGITS = 18; + const LENIENT_PARSE = 19; + + /* Text attribute constants */ + const POSITIVE_PREFIX = 0; + const POSITIVE_SUFFIX = 1; + const NEGATIVE_PREFIX = 2; + const NEGATIVE_SUFFIX = 3; + const PADDING_CHARACTER = 4; + const CURRENCY_CODE = 5; + const DEFAULT_RULESET = 6; + const PUBLIC_RULESETS = 7; + + /* Format symbol constants */ + const DECIMAL_SEPARATOR_SYMBOL = 0; + const GROUPING_SEPARATOR_SYMBOL = 1; + const PATTERN_SEPARATOR_SYMBOL = 2; + const PERCENT_SYMBOL = 3; + const ZERO_DIGIT_SYMBOL = 4; + const DIGIT_SYMBOL = 5; + const MINUS_SIGN_SYMBOL = 6; + const PLUS_SIGN_SYMBOL = 7; + const CURRENCY_SYMBOL = 8; + const INTL_CURRENCY_SYMBOL = 9; + const MONETARY_SEPARATOR_SYMBOL = 10; + const EXPONENTIAL_SYMBOL = 11; + const PERMILL_SYMBOL = 12; + const PAD_ESCAPE_SYMBOL = 13; + const INFINITY_SYMBOL = 14; + const NAN_SYMBOL = 15; + const SIGNIFICANT_DIGIT_SYMBOL = 16; + const MONETARY_GROUPING_SEPARATOR_SYMBOL = 17; + + /* Rounding mode values used by NumberFormatter::setAttribute() with NumberFormatter::ROUNDING_MODE attribute */ + const ROUND_CEILING = 0; + const ROUND_FLOOR = 1; + const ROUND_DOWN = 2; + const ROUND_UP = 3; + const ROUND_HALFEVEN = 4; + const ROUND_HALFDOWN = 5; + const ROUND_HALFUP = 6; + + /* Pad position values used by NumberFormatter::setAttribute() with NumberFormatter::PADDING_POSITION attribute */ + const PAD_BEFORE_PREFIX = 0; + const PAD_AFTER_PREFIX = 1; + const PAD_BEFORE_SUFFIX = 2; + const PAD_AFTER_SUFFIX = 3; + + /** + * The error code from the last operation. + * + * @var int + */ + protected $errorCode = IntlGlobals::U_ZERO_ERROR; + + /** + * The error message from the last operation. + * + * @var string + */ + protected $errorMessage = 'U_ZERO_ERROR'; + + /** + * @var int + */ + private $style; + + /** + * Default values for the en locale. + */ + private $attributes = [ + self::FRACTION_DIGITS => 0, + self::GROUPING_USED => 1, + self::ROUNDING_MODE => self::ROUND_HALFEVEN, + ]; + + /** + * Holds the initialized attributes code. + */ + private $initializedAttributes = []; + + /** + * The supported styles to the constructor $styles argument. + */ + private static $supportedStyles = [ + 'CURRENCY' => self::CURRENCY, + 'DECIMAL' => self::DECIMAL, + ]; + + /** + * Supported attributes to the setAttribute() $attr argument. + */ + private static $supportedAttributes = [ + 'FRACTION_DIGITS' => self::FRACTION_DIGITS, + 'GROUPING_USED' => self::GROUPING_USED, + 'ROUNDING_MODE' => self::ROUNDING_MODE, + ]; + + /** + * The available rounding modes for setAttribute() usage with + * NumberFormatter::ROUNDING_MODE. NumberFormatter::ROUND_DOWN + * and NumberFormatter::ROUND_UP does not have a PHP only equivalent. + */ + private static $roundingModes = [ + 'ROUND_HALFEVEN' => self::ROUND_HALFEVEN, + 'ROUND_HALFDOWN' => self::ROUND_HALFDOWN, + 'ROUND_HALFUP' => self::ROUND_HALFUP, + 'ROUND_CEILING' => self::ROUND_CEILING, + 'ROUND_FLOOR' => self::ROUND_FLOOR, + 'ROUND_DOWN' => self::ROUND_DOWN, + 'ROUND_UP' => self::ROUND_UP, + ]; + + /** + * The mapping between NumberFormatter rounding modes to the available + * modes in PHP's round() function. + * + * @see https://php.net/round + */ + private static $phpRoundingMap = [ + self::ROUND_HALFDOWN => PHP_ROUND_HALF_DOWN, + self::ROUND_HALFEVEN => PHP_ROUND_HALF_EVEN, + self::ROUND_HALFUP => PHP_ROUND_HALF_UP, + ]; + + /** + * The list of supported rounding modes which aren't available modes in + * PHP's round() function, but there's an equivalent. Keys are rounding + * modes, values does not matter. + */ + private static $customRoundingList = [ + self::ROUND_CEILING => true, + self::ROUND_FLOOR => true, + self::ROUND_DOWN => true, + self::ROUND_UP => true, + ]; + + /** + * The maximum value of the integer type in 32 bit platforms. + */ + private static $int32Max = 2147483647; + + /** + * The maximum value of the integer type in 64 bit platforms. + * + * @var int|float + */ + private static $int64Max = 9223372036854775807; + + private static $enSymbols = [ + self::DECIMAL => ['.', ',', ';', '%', '0', '#', '-', '+', '¤', '¤¤', '.', 'E', '‰', '*', '∞', 'NaN', '@', ','], + self::CURRENCY => ['.', ',', ';', '%', '0', '#', '-', '+', '¤', '¤¤', '.', 'E', '‰', '*', '∞', 'NaN', '@', ','], + ]; + + private static $enTextAttributes = [ + self::DECIMAL => ['', '', '-', '', ' ', 'XXX', ''], + self::CURRENCY => ['¤', '', '-¤', '', ' ', 'XXX'], + ]; + + /** + * @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en") + * @param int $style Style of the formatting, one of the format style constants. + * The only supported styles are NumberFormatter::DECIMAL + * and NumberFormatter::CURRENCY. + * @param string $pattern Not supported. A pattern string in case $style is NumberFormat::PATTERN_DECIMAL or + * NumberFormat::PATTERN_RULEBASED. It must conform to the syntax + * described in the ICU DecimalFormat or ICU RuleBasedNumberFormat documentation + * + * @see https://php.net/numberformatter.create + * @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1DecimalFormat.html#details + * @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1RuleBasedNumberFormat.html#details + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + * @throws MethodArgumentValueNotImplementedException When the $style is not supported + * @throws MethodArgumentNotImplementedException When the pattern value is different than null + */ + public function __construct(?string $locale = 'en', int $style = null, string $pattern = null) + { + if ('en' !== $locale && null !== $locale) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'locale', $locale, 'Only the locale "en" is supported'); + } + + if (!\in_array($style, self::$supportedStyles)) { + $message = sprintf('The available styles are: %s.', implode(', ', array_keys(self::$supportedStyles))); + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'style', $style, $message); + } + + if (null !== $pattern) { + throw new MethodArgumentNotImplementedException(__METHOD__, 'pattern'); + } + + $this->style = $style; + } + + /** + * Static constructor. + * + * @param string|null $locale The locale code. The only supported locale is "en" (or null using the default locale, i.e. "en") + * @param int $style Style of the formatting, one of the format style constants. + * The only currently supported styles are NumberFormatter::DECIMAL + * and NumberFormatter::CURRENCY. + * @param string $pattern Not supported. A pattern string in case $style is NumberFormat::PATTERN_DECIMAL or + * NumberFormat::PATTERN_RULEBASED. It must conform to the syntax + * described in the ICU DecimalFormat or ICU RuleBasedNumberFormat documentation + * + * @return static + * + * @see https://php.net/numberformatter.create + * @see http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details + * @see http://www.icu-project.org/apiref/icu4c/classRuleBasedNumberFormat.html#_details + * + * @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed + * @throws MethodArgumentValueNotImplementedException When the $style is not supported + * @throws MethodArgumentNotImplementedException When the pattern value is different than null + */ + public static function create(?string $locale = 'en', int $style = null, string $pattern = null) + { + return new static($locale, $style, $pattern); + } + + /** + * Format a currency value. + * + * @param string $currency The 3-letter ISO 4217 currency code indicating the currency to use + * + * @return string The formatted currency value + * + * @see https://php.net/numberformatter.formatcurrency + * @see https://en.wikipedia.org/wiki/ISO_4217#Active_codes + */ + public function formatCurrency(float $value, string $currency) + { + if (self::DECIMAL === $this->style) { + return $this->format($value); + } + + $symbol = Currencies::getSymbol($currency, 'en'); + $fractionDigits = Currencies::getFractionDigits($currency); + + $value = $this->roundCurrency($value, $currency); + + $negative = false; + if (0 > $value) { + $negative = true; + $value *= -1; + } + + $value = $this->formatNumber($value, $fractionDigits); + + // There's a non-breaking space after the currency code (i.e. CRC 100), but not if the currency has a symbol (i.e. £100). + $ret = $symbol.(mb_strlen($symbol, 'UTF-8') > 2 ? "\xc2\xa0" : '').$value; + + return $negative ? '-'.$ret : $ret; + } + + /** + * Format a number. + * + * @param int|float $value The value to format + * @param int $type Type of the formatting, one of the format type constants. + * Only type NumberFormatter::TYPE_DEFAULT is currently supported. + * + * @return bool|string The formatted value or false on error + * + * @see https://php.net/numberformatter.format + * + * @throws NotImplementedException If the method is called with the class $style 'CURRENCY' + * @throws MethodArgumentValueNotImplementedException If the $type is different than TYPE_DEFAULT + */ + public function format($value, int $type = self::TYPE_DEFAULT) + { + // The original NumberFormatter does not support this format type + if (self::TYPE_CURRENCY === $type) { + trigger_error(__METHOD__.'(): Unsupported format type '.$type, E_USER_WARNING); + + return false; + } + + if (self::CURRENCY === $this->style) { + throw new NotImplementedException(sprintf('"%s()" method does not support the formatting of currencies (instance with CURRENCY style). "%s".', __METHOD__, NotImplementedException::INTL_INSTALL_MESSAGE)); + } + + // Only the default type is supported. + if (self::TYPE_DEFAULT !== $type) { + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'type', $type, 'Only TYPE_DEFAULT is supported'); + } + + $fractionDigits = $this->getAttribute(self::FRACTION_DIGITS); + + $value = $this->round($value, $fractionDigits); + $value = $this->formatNumber($value, $fractionDigits); + + // behave like the intl extension + $this->resetError(); + + return $value; + } + + /** + * Returns an attribute value. + * + * @param int $attr An attribute specifier, one of the numeric attribute constants + * + * @return int|false The attribute value on success or false on error + * + * @see https://php.net/numberformatter.getattribute + */ + public function getAttribute(int $attr) + { + return isset($this->attributes[$attr]) ? $this->attributes[$attr] : null; + } + + /** + * Returns formatter's last error code. Always returns the U_ZERO_ERROR class constant value. + * + * @return int The error code from last formatter call + * + * @see https://php.net/numberformatter.geterrorcode + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * Returns formatter's last error message. Always returns the U_ZERO_ERROR_MESSAGE class constant value. + * + * @return string The error message from last formatter call + * + * @see https://php.net/numberformatter.geterrormessage + */ + public function getErrorMessage() + { + return $this->errorMessage; + } + + /** + * Returns the formatter's locale. + * + * The parameter $type is currently ignored. + * + * @param int $type Not supported. The locale name type to return (Locale::VALID_LOCALE or Locale::ACTUAL_LOCALE) + * + * @return string The locale used to create the formatter. Currently always + * returns "en". + * + * @see https://php.net/numberformatter.getlocale + */ + public function getLocale(int $type = Locale::ACTUAL_LOCALE) + { + return 'en'; + } + + /** + * Not supported. Returns the formatter's pattern. + * + * @return string|false The pattern string used by the formatter or false on error + * + * @see https://php.net/numberformatter.getpattern + * + * @throws MethodNotImplementedException + */ + public function getPattern() + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Returns a formatter symbol value. + * + * @param int $attr A symbol specifier, one of the format symbol constants + * + * @return string|false The symbol value or false on error + * + * @see https://php.net/numberformatter.getsymbol + */ + public function getSymbol(int $attr) + { + return \array_key_exists($this->style, self::$enSymbols) && \array_key_exists($attr, self::$enSymbols[$this->style]) ? self::$enSymbols[$this->style][$attr] : false; + } + + /** + * Not supported. Returns a formatter text attribute value. + * + * @param int $attr An attribute specifier, one of the text attribute constants + * + * @return string|false The attribute value or false on error + * + * @see https://php.net/numberformatter.gettextattribute + */ + public function getTextAttribute(int $attr) + { + return \array_key_exists($this->style, self::$enTextAttributes) && \array_key_exists($attr, self::$enTextAttributes[$this->style]) ? self::$enTextAttributes[$this->style][$attr] : false; + } + + /** + * Not supported. Parse a currency number. + * + * @param string $value The value to parse + * @param string $currency Parameter to receive the currency name (reference) + * @param int $position Offset to begin the parsing on return this value will hold the offset at which the parsing ended + * + * @return float|false The parsed numeric value or false on error + * + * @see https://php.net/numberformatter.parsecurrency + * + * @throws MethodNotImplementedException + */ + public function parseCurrency(string $value, string &$currency, int &$position = null) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Parse a number. + * + * @param string $value The value to parse + * @param int $type Type of the formatting, one of the format type constants. NumberFormatter::TYPE_DOUBLE by default. + * @param int $position Offset to begin the parsing on return this value will hold the offset at which the parsing ended + * + * @return int|float|false The parsed value or false on error + * + * @see https://php.net/numberformatter.parse + */ + public function parse(string $value, int $type = self::TYPE_DOUBLE, int &$position = 0) + { + if (self::TYPE_DEFAULT === $type || self::TYPE_CURRENCY === $type) { + trigger_error(__METHOD__.'(): Unsupported format type '.$type, E_USER_WARNING); + + return false; + } + + // Any invalid number at the end of the string is removed. + // Only numbers and the fraction separator is expected in the string. + // If grouping is used, grouping separator also becomes a valid character. + $groupingMatch = $this->getAttribute(self::GROUPING_USED) ? '|(?P\d++(,{1}\d+)++(\.\d*+)?)' : ''; + if (preg_match("/^-?(?:\.\d++{$groupingMatch}|\d++(\.\d*+)?)/", $value, $matches)) { + $value = $matches[0]; + $position = \strlen($value); + // value is not valid if grouping is used, but digits are not grouped in groups of three + if ($error = isset($matches['grouping']) && !preg_match('/^-?(?:\d{1,3}+)?(?:(?:,\d{3})++|\d*+)(?:\.\d*+)?$/', $value)) { + // the position on error is 0 for positive and 1 for negative numbers + $position = 0 === strpos($value, '-') ? 1 : 0; + } + } else { + $error = true; + $position = 0; + } + + if ($error) { + IntlGlobals::setError(IntlGlobals::U_PARSE_ERROR, 'Number parsing failed'); + $this->errorCode = IntlGlobals::getErrorCode(); + $this->errorMessage = IntlGlobals::getErrorMessage(); + + return false; + } + + $value = str_replace(',', '', $value); + $value = $this->convertValueDataType($value, $type); + + // behave like the intl extension + $this->resetError(); + + return $value; + } + + /** + * Set an attribute. + * + * @param int $attr An attribute specifier, one of the numeric attribute constants. + * The only currently supported attributes are NumberFormatter::FRACTION_DIGITS, + * NumberFormatter::GROUPING_USED and NumberFormatter::ROUNDING_MODE. + * + * @return bool true on success or false on failure + * + * @see https://php.net/numberformatter.setattribute + * + * @throws MethodArgumentValueNotImplementedException When the $attr is not supported + * @throws MethodArgumentValueNotImplementedException When the $value is not supported + */ + public function setAttribute(int $attr, int $value) + { + if (!\in_array($attr, self::$supportedAttributes)) { + $message = sprintf( + 'The available attributes are: %s', + implode(', ', array_keys(self::$supportedAttributes)) + ); + + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'attr', $value, $message); + } + + if (self::$supportedAttributes['ROUNDING_MODE'] === $attr && $this->isInvalidRoundingMode($value)) { + $message = sprintf( + 'The supported values for ROUNDING_MODE are: %s', + implode(', ', array_keys(self::$roundingModes)) + ); + + throw new MethodArgumentValueNotImplementedException(__METHOD__, 'attr', $value, $message); + } + + if (self::$supportedAttributes['GROUPING_USED'] === $attr) { + $value = $this->normalizeGroupingUsedValue($value); + } + + if (self::$supportedAttributes['FRACTION_DIGITS'] === $attr) { + $value = $this->normalizeFractionDigitsValue($value); + if ($value < 0) { + // ignore negative values but do not raise an error + return true; + } + } + + $this->attributes[$attr] = $value; + $this->initializedAttributes[$attr] = true; + + return true; + } + + /** + * Not supported. Set the formatter's pattern. + * + * @param string $pattern A pattern string in conformance with the ICU DecimalFormat documentation + * + * @return bool true on success or false on failure + * + * @see https://php.net/numberformatter.setpattern + * @see http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details + * + * @throws MethodNotImplementedException + */ + public function setPattern(string $pattern) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Set the formatter's symbol. + * + * @param int $attr A symbol specifier, one of the format symbol constants + * @param string $value The value for the symbol + * + * @return bool true on success or false on failure + * + * @see https://php.net/numberformatter.setsymbol + * + * @throws MethodNotImplementedException + */ + public function setSymbol(int $attr, string $value) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Not supported. Set a text attribute. + * + * @param int $attr An attribute specifier, one of the text attribute constants + * @param string $value The attribute value + * + * @return bool true on success or false on failure + * + * @see https://php.net/numberformatter.settextattribute + * + * @throws MethodNotImplementedException + */ + public function setTextAttribute(int $attr, string $value) + { + throw new MethodNotImplementedException(__METHOD__); + } + + /** + * Set the error to the default U_ZERO_ERROR. + */ + protected function resetError() + { + IntlGlobals::setError(IntlGlobals::U_ZERO_ERROR); + $this->errorCode = IntlGlobals::getErrorCode(); + $this->errorMessage = IntlGlobals::getErrorMessage(); + } + + /** + * Rounds a currency value, applying increment rounding if applicable. + * + * When a currency have a rounding increment, an extra round is made after the first one. The rounding factor is + * determined in the ICU data and is explained as of: + * + * "the rounding increment is given in units of 10^(-fraction_digits)" + * + * The only actual rounding data as of this writing, is CHF. + * + * @see http://en.wikipedia.org/wiki/Swedish_rounding + * @see http://www.docjar.com/html/api/com/ibm/icu/util/Currency.java.html#1007 + */ + private function roundCurrency(float $value, string $currency): float + { + $fractionDigits = Currencies::getFractionDigits($currency); + $roundingIncrement = Currencies::getRoundingIncrement($currency); + + // Round with the formatter rounding mode + $value = $this->round($value, $fractionDigits); + + // Swiss rounding + if (0 < $roundingIncrement && 0 < $fractionDigits) { + $roundingFactor = $roundingIncrement / pow(10, $fractionDigits); + $value = round($value / $roundingFactor) * $roundingFactor; + } + + return $value; + } + + /** + * Rounds a value. + * + * @param int|float $value The value to round + * + * @return int|float The rounded value + */ + private function round($value, int $precision) + { + $precision = $this->getUninitializedPrecision($value, $precision); + + $roundingModeAttribute = $this->getAttribute(self::ROUNDING_MODE); + if (isset(self::$phpRoundingMap[$roundingModeAttribute])) { + $value = round($value, $precision, self::$phpRoundingMap[$roundingModeAttribute]); + } elseif (isset(self::$customRoundingList[$roundingModeAttribute])) { + $roundingCoef = pow(10, $precision); + $value *= $roundingCoef; + $value = (float) (string) $value; + + switch ($roundingModeAttribute) { + case self::ROUND_CEILING: + $value = ceil($value); + break; + case self::ROUND_FLOOR: + $value = floor($value); + break; + case self::ROUND_UP: + $value = $value > 0 ? ceil($value) : floor($value); + break; + case self::ROUND_DOWN: + $value = $value > 0 ? floor($value) : ceil($value); + break; + } + + $value /= $roundingCoef; + } + + return $value; + } + + /** + * Formats a number. + * + * @param int|float $value The numeric value to format + */ + private function formatNumber($value, int $precision): string + { + $precision = $this->getUninitializedPrecision($value, $precision); + + return number_format($value, $precision, '.', $this->getAttribute(self::GROUPING_USED) ? ',' : ''); + } + + /** + * Returns the precision value if the DECIMAL style is being used and the FRACTION_DIGITS attribute is uninitialized. + * + * @param int|float $value The value to get the precision from if the FRACTION_DIGITS attribute is uninitialized + */ + private function getUninitializedPrecision($value, int $precision): int + { + if (self::CURRENCY === $this->style) { + return $precision; + } + + if (!$this->isInitializedAttribute(self::FRACTION_DIGITS)) { + preg_match('/.*\.(.*)/', (string) $value, $digits); + if (isset($digits[1])) { + $precision = \strlen($digits[1]); + } + } + + return $precision; + } + + /** + * Check if the attribute is initialized (value set by client code). + */ + private function isInitializedAttribute(string $attr): bool + { + return isset($this->initializedAttributes[$attr]); + } + + /** + * Returns the numeric value using the $type to convert to the right data type. + * + * @param mixed $value The value to be converted + * + * @return int|float|false The converted value + */ + private function convertValueDataType($value, int $type) + { + if (self::TYPE_DOUBLE === $type) { + $value = (float) $value; + } elseif (self::TYPE_INT32 === $type) { + $value = $this->getInt32Value($value); + } elseif (self::TYPE_INT64 === $type) { + $value = $this->getInt64Value($value); + } + + return $value; + } + + /** + * Convert the value data type to int or returns false if the value is out of the integer value range. + * + * @return int|false The converted value + */ + private function getInt32Value($value) + { + if ($value > self::$int32Max || $value < -self::$int32Max - 1) { + return false; + } + + return (int) $value; + } + + /** + * Convert the value data type to int or returns false if the value is out of the integer value range. + * + * @return int|float|false The converted value + */ + private function getInt64Value($value) + { + if ($value > self::$int64Max || $value < -self::$int64Max - 1) { + return false; + } + + if (PHP_INT_SIZE !== 8 && ($value > self::$int32Max || $value < -self::$int32Max - 1)) { + return (float) $value; + } + + return (int) $value; + } + + /** + * Check if the rounding mode is invalid. + */ + private function isInvalidRoundingMode(int $value): bool + { + if (\in_array($value, self::$roundingModes, true)) { + return false; + } + + return true; + } + + /** + * Returns the normalized value for the GROUPING_USED attribute. Any value that can be converted to int will be + * cast to Boolean and then to int again. This way, negative values are converted to 1 and string values to 0. + */ + private function normalizeGroupingUsedValue($value): int + { + return (int) (bool) (int) $value; + } + + /** + * Returns the normalized value for the FRACTION_DIGITS attribute. + */ + private function normalizeFractionDigitsValue($value): int + { + return (int) $value; + } +} diff --git a/vendor/symfony/intl/ResourceBundle.php b/vendor/symfony/intl/ResourceBundle.php new file mode 100644 index 0000000..4611583 --- /dev/null +++ b/vendor/symfony/intl/ResourceBundle.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +use Symfony\Component\Intl\Data\Bundle\Reader\BufferedBundleReader; +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReader; +use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface; +use Symfony\Component\Intl\Data\Bundle\Reader\JsonBundleReader; + +/** + * @author Roland Franssen + * + * @internal + */ +abstract class ResourceBundle +{ + private static $entryReader; + + abstract protected static function getPath(): string; + + /** + * Reads an entry from a resource bundle. + * + * @see BundleEntryReaderInterface::readEntry() + * + * @param string[] $indices The indices to read from the bundle + * @param string $locale The locale to read + * @param bool $fallback Whether to merge the value with the value from + * the fallback locale (e.g. "en" for "en_GB"). + * Only applicable if the result is multivalued + * (i.e. array or \ArrayAccess) or cannot be found + * in the requested locale. + * + * @return mixed returns an array or {@link \ArrayAccess} instance for + * complex data and a scalar value for simple data + */ + final protected static function readEntry(array $indices, string $locale = null, bool $fallback = true) + { + if (null === self::$entryReader) { + self::$entryReader = new BundleEntryReader(new BufferedBundleReader( + new JsonBundleReader(), + Intl::BUFFER_SIZE + )); + + $localeAliases = self::$entryReader->readEntry(Intl::getDataDirectory().'/'.Intl::LOCALE_DIR, 'meta', ['Aliases']); + self::$entryReader->setLocaleAliases($localeAliases instanceof \Traversable ? iterator_to_array($localeAliases) : $localeAliases); + } + + return self::$entryReader->readEntry(static::getPath(), $locale ?? \Locale::getDefault(), $indices, $fallback); + } + + final protected static function asort(iterable $list, string $locale = null): array + { + if ($list instanceof \Traversable) { + $list = iterator_to_array($list); + } + + $collator = new \Collator($locale ?? \Locale::getDefault()); + $collator->asort($list); + + return $list; + } + + private function __construct() + { + } +} diff --git a/vendor/symfony/intl/Resources/bin/autoload.php b/vendor/symfony/intl/Resources/bin/autoload.php new file mode 100644 index 0000000..14167a5 --- /dev/null +++ b/vendor/symfony/intl/Resources/bin/autoload.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$autoload = __DIR__.'/../../vendor/autoload.php'; + +if (!file_exists($autoload)) { + bailout('You should run "composer install" in the component before running this script.'); +} + +require_once $autoload; diff --git a/vendor/symfony/intl/Resources/bin/common.php b/vendor/symfony/intl/Resources/bin/common.php new file mode 100644 index 0000000..8ebf9fd --- /dev/null +++ b/vendor/symfony/intl/Resources/bin/common.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +define('LINE_WIDTH', 75); + +define('LINE', str_repeat('-', LINE_WIDTH)."\n"); + +function bailout($message) +{ + echo wordwrap($message, LINE_WIDTH)." Aborting.\n"; + + exit(1); +} + +function strip_minor_versions($version) +{ + preg_match('/^(?P[0-9]\.[0-9]|[0-9]{2,})/', $version, $matches); + + return $matches['version']; +} + +function centered($text) +{ + $padding = (int) ((LINE_WIDTH - strlen($text)) / 2); + + return str_repeat(' ', $padding).$text; +} + +function cd($dir) +{ + if (false === chdir($dir)) { + bailout("Could not switch to directory $dir."); + } +} + +function run($command) +{ + exec($command, $output, $status); + + if (0 !== $status) { + $output = implode("\n", $output); + echo "Error while running:\n ".getcwd().'$ '.$command."\nOutput:\n".LINE."$output\n".LINE; + + bailout("\"$command\" failed."); + } +} + +function get_icu_version_from_genrb($genrb) +{ + exec($genrb.' --version - 2>&1', $output, $status); + + if (0 !== $status) { + bailout($genrb.' failed.'); + } + + if (!preg_match('/ICU version ([\d\.]+)/', implode('', $output), $matches)) { + return null; + } + + return $matches[1]; +} + +error_reporting(E_ALL); + +set_error_handler(function ($type, $msg, $file, $line) { + throw new \ErrorException($msg, 0, $type, $file, $line); +}); + +set_exception_handler(function (\Throwable $exception) { + echo "\n"; + + $cause = $exception; + $root = true; + + while (null !== $cause) { + if (!$root) { + echo "Caused by\n"; + } + + echo get_class($cause).': '.$cause->getMessage()."\n"; + echo "\n"; + echo $cause->getFile().':'.$cause->getLine()."\n"; + echo $cause->getTraceAsString()."\n"; + + $cause = $cause->getPrevious(); + $root = false; + } +}); diff --git a/vendor/symfony/intl/Resources/bin/compile b/vendor/symfony/intl/Resources/bin/compile new file mode 100755 index 0000000..d665583 --- /dev/null +++ b/vendor/symfony/intl/Resources/bin/compile @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +[[ $1 == force ]] && docker pull jakzal/php-intl +[[ ! -d /tmp/symfony/icu ]] && mkdir -p /tmp/symfony/icu + +docker run \ + -it --rm --name symfony-intl \ + -u $(id -u):$(id -g) \ + -v /tmp/symfony/icu:/tmp \ + -v $(pwd):/symfony \ + -w /symfony \ + jakzal/php-intl:latest \ + php src/Symfony/Component/Intl/Resources/bin/update-data.php diff --git a/vendor/symfony/intl/Resources/bin/update-data.php b/vendor/symfony/intl/Resources/bin/update-data.php new file mode 100644 index 0000000..4fc4b2c --- /dev/null +++ b/vendor/symfony/intl/Resources/bin/update-data.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Intl\Data\Bundle\Compiler\GenrbCompiler; +use Symfony\Component\Intl\Data\Bundle\Writer\JsonBundleWriter; +use Symfony\Component\Intl\Data\Generator\CurrencyDataGenerator; +use Symfony\Component\Intl\Data\Generator\GeneratorConfig; +use Symfony\Component\Intl\Data\Generator\LanguageDataGenerator; +use Symfony\Component\Intl\Data\Generator\LocaleDataGenerator; +use Symfony\Component\Intl\Data\Generator\RegionDataGenerator; +use Symfony\Component\Intl\Data\Generator\ScriptDataGenerator; +use Symfony\Component\Intl\Data\Generator\TimezoneDataGenerator; +use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Locale; +use Symfony\Component\Intl\Util\GitRepository; + +require_once __DIR__.'/common.php'; +require_once __DIR__.'/autoload.php'; + +$argc = $_SERVER['argc']; +$argv = $_SERVER['argv']; + +if ($argc > 3 || 2 === $argc && '-h' === $argv[1]) { + bailout(<<<'MESSAGE' +Usage: php update-data.php + +Updates the ICU data for Symfony to the latest version of ICU. + +If you downloaded the git repository before, you can pass the path to the +repository source in the first optional argument. + +If you also built the repository before, you can pass the directory where that +build is stored in the second parameter. The build directory needs to contain +the subdirectories bin/ and lib/. + +For running this script, the intl extension must be loaded and all vendors +must have been installed through composer: + +composer install + +MESSAGE + ); +} + +echo LINE; +echo centered('ICU Resource Bundle Compilation')."\n"; +echo LINE; + +if (!Intl::isExtensionLoaded()) { + bailout('The intl extension for PHP is not installed.'); +} + +if ($argc >= 2) { + $repoDir = $argv[1]; + $git = new GitRepository($repoDir); + + echo "Using the existing git repository at {$repoDir}.\n"; +} else { + echo "Starting git clone. This may take a while...\n"; + + $repoDir = sys_get_temp_dir().'/icu-data'; + $git = GitRepository::download('https://github.com/unicode-org/icu.git', $repoDir); + + echo "Git clone to {$repoDir} complete.\n"; +} + +$gitTag = $git->getLastTag(function ($tag) { + return preg_match('#^release-[0-9]{1,}-[0-9]{1}$#', $tag); +}); +$shortIcuVersion = strip_minor_versions(preg_replace('#release-([0-9]{1,})-([0-9]{1,})#', '$1.$2', $gitTag)); + +echo "Checking out `{$gitTag}` for version `{$shortIcuVersion}`...\n"; +$git->checkout('tags/'.$gitTag); + +$filesystem = new Filesystem(); +$sourceDir = $repoDir.'/icu4c/source'; + +if ($argc >= 3) { + $buildDir = $argv[2]; +} else { + // Always build genrb so that we can determine the ICU version of the + // download by running genrb --version + echo "Building genrb.\n"; + + cd($sourceDir); + + echo "Running configure...\n"; + + $buildDir = sys_get_temp_dir().'/icu-data/'.$shortIcuVersion.'/build'; + + $filesystem->remove($buildDir); + $filesystem->mkdir($buildDir); + + run('./configure --prefix='.$buildDir.' 2>&1'); + + echo "Running make...\n"; + + // If the directory "lib" does not exist in the download, create it or we + // will run into problems when building libicuuc.so. + $filesystem->mkdir($sourceDir.'/lib'); + + // If the directory "bin" does not exist in the download, create it or we + // will run into problems when building genrb. + $filesystem->mkdir($sourceDir.'/bin'); + + echo '[1/6] libicudata.so...'; + + cd($sourceDir.'/stubdata'); + run('make 2>&1 && make install 2>&1'); + + echo " ok.\n"; + + echo '[2/6] libicuuc.so...'; + + cd($sourceDir.'/common'); + run('make 2>&1 && make install 2>&1'); + + echo " ok.\n"; + + echo '[3/6] libicui18n.so...'; + + cd($sourceDir.'/i18n'); + run('make 2>&1 && make install 2>&1'); + + echo " ok.\n"; + + echo '[4/6] libicutu.so...'; + + cd($sourceDir.'/tools/toolutil'); + run('make 2>&1 && make install 2>&1'); + + echo " ok.\n"; + + echo '[5/6] libicuio.so...'; + + cd($sourceDir.'/io'); + run('make 2>&1 && make install 2>&1'); + + echo " ok.\n"; + + echo '[6/6] genrb...'; + + cd($sourceDir.'/tools/genrb'); + run('make 2>&1 && make install 2>&1'); + + echo " ok.\n"; +} + +$genrb = $buildDir.'/bin/genrb'; +$genrbEnv = 'LD_LIBRARY_PATH='.$buildDir.'/lib '; + +echo "Using $genrb.\n"; + +$icuVersionInDownload = get_icu_version_from_genrb($genrbEnv.' '.$genrb); + +echo "Preparing resource bundle compilation (version $icuVersionInDownload)...\n"; + +$compiler = new GenrbCompiler($genrb, $genrbEnv); +$config = new GeneratorConfig($sourceDir.'/data', $icuVersionInDownload); +$jsonDir = dirname(__DIR__).'/data'; + +$config->addBundleWriter($jsonDir, new JsonBundleWriter()); + +echo "Starting resource bundle compilation. This may take a while...\n"; + +// We don't want to use fallback to English during generation +Locale::setDefaultFallback('root'); + +echo "Generating language data...\n"; + +$generator = new LanguageDataGenerator($compiler, Intl::LANGUAGE_DIR); +$generator->generateData($config); + +echo "Generating script data...\n"; + +$generator = new ScriptDataGenerator($compiler, Intl::SCRIPT_DIR); +$generator->generateData($config); + +echo "Generating region data...\n"; + +$generator = new RegionDataGenerator($compiler, Intl::REGION_DIR); +$generator->generateData($config); + +echo "Generating currency data...\n"; + +$generator = new CurrencyDataGenerator($compiler, Intl::CURRENCY_DIR); +$generator->generateData($config); + +echo "Generating locale data...\n"; + +$generator = new LocaleDataGenerator($compiler, Intl::LOCALE_DIR); +$generator->generateData($config); + +echo "Generating timezone data...\n"; + +$generator = new TimezoneDataGenerator($compiler, Intl::TIMEZONE_DIR); +$generator->generateData($config); + +echo "Resource bundle compilation complete.\n"; + +$gitInfo = <<getUrl()} +Revision: {$git->getLastCommitHash()} +Author: {$git->getLastAuthor()} +Date: {$git->getLastAuthoredDate()->format('c')} + +GIT_INFO; + +$gitInfoFile = $jsonDir.'/git-info.txt'; + +file_put_contents($gitInfoFile, $gitInfo); + +echo "Wrote $gitInfoFile.\n"; + +$versionFile = $jsonDir.'/version.txt'; + +file_put_contents($versionFile, "$icuVersionInDownload\n"); + +echo "Wrote $versionFile.\n"; +echo "Done.\n"; diff --git a/vendor/symfony/intl/Resources/stubs/Collator.php b/vendor/symfony/intl/Resources/stubs/Collator.php new file mode 100644 index 0000000..1977fdf --- /dev/null +++ b/vendor/symfony/intl/Resources/stubs/Collator.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Intl\Collator\Collator as IntlCollator; + +/** + * Stub implementation for the Collator class of the intl extension. + * + * @author Bernhard Schussek + */ +class Collator extends IntlCollator +{ +} diff --git a/vendor/symfony/intl/Resources/stubs/IntlDateFormatter.php b/vendor/symfony/intl/Resources/stubs/IntlDateFormatter.php new file mode 100644 index 0000000..e5209b6 --- /dev/null +++ b/vendor/symfony/intl/Resources/stubs/IntlDateFormatter.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Intl\DateFormatter\IntlDateFormatter as BaseIntlDateFormatter; + +/** + * Stub implementation for the IntlDateFormatter class of the intl extension. + * + * @author Bernhard Schussek + * + * @see BaseIntlDateFormatter + */ +class IntlDateFormatter extends BaseIntlDateFormatter +{ +} diff --git a/vendor/symfony/intl/Resources/stubs/Locale.php b/vendor/symfony/intl/Resources/stubs/Locale.php new file mode 100644 index 0000000..8a3b89b --- /dev/null +++ b/vendor/symfony/intl/Resources/stubs/Locale.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Intl\Locale\Locale as IntlLocale; + +/** + * Stub implementation for the Locale class of the intl extension. + * + * @author Bernhard Schussek + * + * @see IntlLocale + */ +class Locale extends IntlLocale +{ +} diff --git a/vendor/symfony/intl/Resources/stubs/NumberFormatter.php b/vendor/symfony/intl/Resources/stubs/NumberFormatter.php new file mode 100644 index 0000000..c8e689b --- /dev/null +++ b/vendor/symfony/intl/Resources/stubs/NumberFormatter.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Intl\NumberFormatter\NumberFormatter as IntlNumberFormatter; + +/** + * Stub implementation for the NumberFormatter class of the intl extension. + * + * @author Bernhard Schussek + * + * @see IntlNumberFormatter + */ +class NumberFormatter extends IntlNumberFormatter +{ +} diff --git a/vendor/symfony/intl/Scripts.php b/vendor/symfony/intl/Scripts.php new file mode 100644 index 0000000..bb29f00 --- /dev/null +++ b/vendor/symfony/intl/Scripts.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +use Symfony\Component\Intl\Exception\MissingResourceException; + +/** + * Gives access to script-related ICU data. + * + * @author Bernhard Schussek + * @author Roland Franssen + */ +final class Scripts extends ResourceBundle +{ + /** + * @return string[] + */ + public static function getScriptCodes(): array + { + return self::readEntry(['Scripts'], 'meta'); + } + + public static function exists(string $script): bool + { + try { + self::readEntry(['Names', $script]); + + return true; + } catch (MissingResourceException $e) { + return false; + } + } + + /** + * @throws MissingResourceException if the script code does not exist + */ + public static function getName(string $script, string $displayLocale = null): string + { + return self::readEntry(['Names', $script], $displayLocale); + } + + /** + * @return string[] + */ + public static function getNames($displayLocale = null): array + { + return self::asort(self::readEntry(['Names'], $displayLocale), $displayLocale); + } + + protected static function getPath(): string + { + return Intl::getDataDirectory().'/'.Intl::SCRIPT_DIR; + } +} diff --git a/vendor/symfony/intl/Timezones.php b/vendor/symfony/intl/Timezones.php new file mode 100644 index 0000000..dd72d35 --- /dev/null +++ b/vendor/symfony/intl/Timezones.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +use Symfony\Component\Intl\Exception\MissingResourceException; +use Symfony\Component\Intl\Exception\RuntimeException; + +/** + * Gives access to timezone-related ICU data. + * + * @author Roland Franssen + */ +final class Timezones extends ResourceBundle +{ + /** + * @return string[] + */ + public static function getIds(): array + { + return self::readEntry(['Zones'], 'meta'); + } + + public static function exists(string $timezone): bool + { + try { + self::readEntry(['Names', $timezone]); + + return true; + } catch (MissingResourceException $e) { + try { + new \DateTimeZone($timezone); + + return true; + } catch (\Exception $e) { + return false; + } + } + } + + /** + * @throws MissingResourceException if the timezone identifier does not exist or is an alias + */ + public static function getName(string $timezone, string $displayLocale = null): string + { + return self::readEntry(['Names', $timezone], $displayLocale); + } + + /** + * @return string[] + */ + public static function getNames(string $displayLocale = null): array + { + return self::asort(self::readEntry(['Names'], $displayLocale), $displayLocale); + } + + /** + * @throws \Exception if the timezone identifier does not exist + * @throws RuntimeException if there's no timezone DST transition information available + */ + public static function getRawOffset(string $timezone, int $timestamp = null): int + { + if (null === $timestamp) { + $timestamp = time(); + } + + $transitions = (new \DateTimeZone($timezone))->getTransitions($timestamp, $timestamp); + + if (!isset($transitions[0]['offset'])) { + throw new RuntimeException('No timezone transitions available.'); + } + + return $transitions[0]['offset']; + } + + public static function getGmtOffset(string $timezone, int $timestamp = null, string $displayLocale = null): string + { + $offset = self::getRawOffset($timezone, $timestamp); + $abs = abs($offset); + + return sprintf(self::readEntry(['Meta', 'GmtFormat'], $displayLocale), sprintf(self::readEntry(['Meta', 'HourFormat'.(0 <= $offset ? 'Pos' : 'Neg')], $displayLocale), $abs / 3600, $abs / 60 % 60)); + } + + /** + * @throws MissingResourceException if the timezone identifier has no associated country code + */ + public static function getCountryCode(string $timezone): string + { + return self::readEntry(['ZoneToCountry', $timezone], 'meta'); + } + + /** + * @throws MissingResourceException if the country code does not exist + */ + public static function forCountryCode(string $country): array + { + try { + return self::readEntry(['CountryToZone', $country], 'meta'); + } catch (MissingResourceException $e) { + if (Countries::exists($country)) { + return []; + } + + if (Countries::exists(strtoupper($country))) { + throw new MissingResourceException(sprintf('Country codes must be in uppercase, but "%s" was passed. Try with "%s" country code instead.', $country, strtoupper($country))); + } + + throw $e; + } + } + + protected static function getPath(): string + { + return Intl::getDataDirectory().'/'.Intl::TIMEZONE_DIR; + } +} diff --git a/vendor/symfony/intl/Util/GitRepository.php b/vendor/symfony/intl/Util/GitRepository.php new file mode 100644 index 0000000..3f24c6a --- /dev/null +++ b/vendor/symfony/intl/Util/GitRepository.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Util; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Intl\Exception\RuntimeException; + +/** + * @internal + */ +final class GitRepository +{ + private $path; + + public function __construct(string $path) + { + $this->path = $path; + + $this->getUrl(); + } + + public static function download(string $remote, string $targetDir): self + { + self::exec('which git', 'The command "git" is not installed.'); + + $filesystem = new Filesystem(); + + if (!$filesystem->exists($targetDir.'/.git')) { + $filesystem->remove($targetDir); + $filesystem->mkdir($targetDir); + + self::exec(sprintf('git clone %s %s', escapeshellarg($remote), escapeshellarg($targetDir))); + } + + return new self(realpath($targetDir)); + } + + public function getPath(): string + { + return $this->path; + } + + public function getUrl(): string + { + return $this->getLastLine($this->execInPath('git config --get remote.origin.url')); + } + + public function getLastCommitHash(): string + { + return $this->getLastLine($this->execInPath('git log -1 --format="%H"')); + } + + public function getLastAuthor(): string + { + return $this->getLastLine($this->execInPath('git log -1 --format="%an"')); + } + + public function getLastAuthoredDate(): \DateTime + { + return new \DateTime($this->getLastLine($this->execInPath('git log -1 --format="%ai"'))); + } + + public function getLastTag(callable $filter = null): string + { + $tags = $this->execInPath('git tag -l --sort=v:refname'); + + if (null !== $filter) { + $tags = array_filter($tags, $filter); + } + + return $this->getLastLine($tags); + } + + public function checkout(string $branch) + { + $this->execInPath(sprintf('git checkout %s', escapeshellarg($branch))); + } + + private function execInPath(string $command): array + { + return self::exec(sprintf('cd %s && %s', escapeshellarg($this->path), $command)); + } + + private static function exec(string $command, string $customErrorMessage = null): array + { + exec(sprintf('%s 2>&1', $command), $output, $result); + + if (0 !== $result) { + throw new RuntimeException(null !== $customErrorMessage ? $customErrorMessage : sprintf('The "%s" command failed.', $command)); + } + + return $output; + } + + private function getLastLine(array $output): string + { + return array_pop($output); + } +} diff --git a/vendor/symfony/intl/Util/IcuVersion.php b/vendor/symfony/intl/Util/IcuVersion.php new file mode 100644 index 0000000..4801e69 --- /dev/null +++ b/vendor/symfony/intl/Util/IcuVersion.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Util; + +/** + * Facilitates the comparison of ICU version strings. + * + * @author Bernhard Schussek + */ +class IcuVersion +{ + /** + * Compares two ICU versions with an operator. + * + * This method is identical to {@link version_compare()}, except that you + * can pass the number of regarded version components in the last argument + * $precision. + * + * Also, a single digit release version and a single digit major version + * are contracted to a two digit release version. If no major version + * is given, it is substituted by zero. + * + * Examples: + * + * IcuVersion::compare('1.2.3', '1.2.4', '==') + * // => false + * + * IcuVersion::compare('1.2.3', '1.2.4', '==', 2) + * // => true + * + * IcuVersion::compare('1.2.3', '12.3', '==') + * // => true + * + * IcuVersion::compare('1', '10', '==') + * // => true + * + * @param int|null $precision The number of components to compare. Pass + * NULL to compare the versions unchanged. + * + * @return bool Whether the comparison succeeded + * + * @see normalize() + */ + public static function compare(string $version1, string $version2, string $operator, ?int $precision = null) + { + $version1 = self::normalize($version1, $precision); + $version2 = self::normalize($version2, $precision); + + return version_compare($version1, $version2, $operator); + } + + /** + * Normalizes a version string to the number of components given in the + * parameter $precision. + * + * A single digit release version and a single digit major version are + * contracted to a two digit release version. If no major version is given, + * it is substituted by zero. + * + * Examples: + * + * IcuVersion::normalize('1.2.3.4'); + * // => '12.3.4' + * + * IcuVersion::normalize('1.2.3.4', 1); + * // => '12' + * + * IcuVersion::normalize('1.2.3.4', 2); + * // => '12.3' + * + * @param int|null $precision The number of components to include. Pass + * NULL to return the version unchanged. + * + * @return string|null the normalized ICU version or NULL if it couldn't be + * normalized + */ + public static function normalize(string $version, ?int $precision) + { + $version = preg_replace('/^(\d)\.(\d)/', '$1$2', $version); + + if (1 === \strlen($version)) { + $version .= '0'; + } + + return Version::normalize($version, $precision); + } + + /** + * Must not be instantiated. + */ + private function __construct() + { + } +} diff --git a/vendor/symfony/intl/Util/IntlTestHelper.php b/vendor/symfony/intl/Util/IntlTestHelper.php new file mode 100644 index 0000000..ca9a0b5 --- /dev/null +++ b/vendor/symfony/intl/Util/IntlTestHelper.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Util; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Intl\Intl; + +/** + * Helper class for preparing test cases that rely on the Intl component. + * + * Any test that tests functionality relying on either the intl classes or + * the resource bundle data should call either of the methods + * {@link requireIntl()} or {@link requireFullIntl()}. Calling + * {@link requireFullIntl()} is only necessary if you use functionality in the + * test that is not provided by the stub intl implementation. + * + * @author Bernhard Schussek + */ +class IntlTestHelper +{ + /** + * Should be called before tests that work fine with the stub implementation. + */ + public static function requireIntl(TestCase $testCase, $minimumIcuVersion = null) + { + if (null === $minimumIcuVersion) { + $minimumIcuVersion = Intl::getIcuStubVersion(); + } + + // We only run tests if the version is *one specific version*. + // This condition is satisfied if + // + // * the intl extension is loaded with version Intl::getIcuStubVersion() + // * the intl extension is not loaded + + if ($minimumIcuVersion && IcuVersion::compare(Intl::getIcuVersion(), $minimumIcuVersion, '<', 1)) { + $testCase->markTestSkipped('ICU version '.$minimumIcuVersion.' is required.'); + } + + // Normalize the default locale in case this is not done explicitly + // in the test + \Locale::setDefault('en'); + + // Consequently, tests will + // + // * run only for one ICU version (see Intl::getIcuStubVersion()) + // there is no need to add control structures to your tests that + // change the test depending on the ICU version. + // + // Tests should only rely on functionality that is implemented in the + // stub classes. + } + + /** + * Should be called before tests that require a feature-complete intl + * implementation. + */ + public static function requireFullIntl(TestCase $testCase, $minimumIcuVersion = null) + { + // We only run tests if the intl extension is loaded... + if (!Intl::isExtensionLoaded()) { + $testCase->markTestSkipped('Extension intl is required.'); + } + + self::requireIntl($testCase, $minimumIcuVersion); + + // Consequently, tests will + // + // * run only for one ICU version (see Intl::getIcuStubVersion()) + // there is no need to add control structures to your tests that + // change the test depending on the ICU version. + // * always use the C intl classes + } + + /** + * Skips the test unless the current system has a 32bit architecture. + */ + public static function require32Bit(TestCase $testCase) + { + if (4 !== PHP_INT_SIZE) { + $testCase->markTestSkipped('PHP 32 bit is required.'); + } + } + + /** + * Skips the test unless the current system has a 64bit architecture. + */ + public static function require64Bit(TestCase $testCase) + { + if (8 !== PHP_INT_SIZE) { + $testCase->markTestSkipped('PHP 64 bit is required.'); + } + } + + /** + * Must not be instantiated. + */ + private function __construct() + { + } +} diff --git a/vendor/symfony/intl/Util/Version.php b/vendor/symfony/intl/Util/Version.php new file mode 100644 index 0000000..8259cb8 --- /dev/null +++ b/vendor/symfony/intl/Util/Version.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Util; + +/** + * Facilitates the comparison of version strings. + * + * @author Bernhard Schussek + */ +class Version +{ + /** + * Compares two versions with an operator. + * + * This method is identical to {@link version_compare()}, except that you + * can pass the number of regarded version components in the last argument + * $precision. + * + * Examples: + * + * Version::compare('1.2.3', '1.2.4', '==') + * // => false + * + * Version::compare('1.2.3', '1.2.4', '==', 2) + * // => true + * + * @param int|null $precision The number of components to compare. Pass + * NULL to compare the versions unchanged. + * + * @return bool Whether the comparison succeeded + * + * @see normalize() + */ + public static function compare(string $version1, string $version2, string $operator, ?int $precision = null) + { + $version1 = self::normalize($version1, $precision); + $version2 = self::normalize($version2, $precision); + + return version_compare($version1, $version2, $operator); + } + + /** + * Normalizes a version string to the number of components given in the + * parameter $precision. + * + * Examples: + * + * Version::normalize('1.2.3', 1); + * // => '1' + * + * Version::normalize('1.2.3', 2); + * // => '1.2' + * + * @param int|null $precision The number of components to include. Pass + * NULL to return the version unchanged. + * + * @return string|null the normalized version or NULL if it couldn't be + * normalized + */ + public static function normalize(string $version, ?int $precision) + { + if (null === $precision) { + return $version; + } + + $pattern = '[^\.]+'; + + for ($i = 2; $i <= $precision; ++$i) { + $pattern = sprintf('[^\.]+(\.%s)?', $pattern); + } + + if (!preg_match('/^'.$pattern.'/', $version, $matches)) { + return null; + } + + return $matches[0]; + } + + /** + * Must not be instantiated. + */ + private function __construct() + { + } +} diff --git a/vendor/symfony/polyfill-intl-icu/bootstrap.php b/vendor/symfony/polyfill-intl-icu/bootstrap.php new file mode 100644 index 0000000..e49a0d7 --- /dev/null +++ b/vendor/symfony/polyfill-intl-icu/bootstrap.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Intl\Globals\IntlGlobals; + +if (!function_exists('intl_is_failure')) { + function intl_is_failure($errorCode) { return IntlGlobals::isFailure($errorCode); } +} +if (!function_exists('intl_get_error_code')) { + function intl_get_error_code() { return IntlGlobals::getErrorCode(); } +} +if (!function_exists('intl_get_error_message')) { + function intl_get_error_message() { return IntlGlobals::getErrorMessage(); } +} +if (!function_exists('intl_error_name')) { + function intl_error_name($errorCode) { return IntlGlobals::getErrorName($errorCode); } +} diff --git a/vendor/symfony/polyfill-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000..c03491b --- /dev/null +++ b/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case PREG_INTERNAL_ERROR: + return 'Internal error'; + case PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === \strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + return '' === $needle || ('' !== $haystack && 0 === \substr_compare($haystack, $needle, -\strlen($needle))); + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000..ad0029a --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,9 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $dividend, float $divisor): float { return p\Php80::fdiv($dividend, $divisor); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(string $haystack, string $needle): bool { return p\Php80::str_contains($haystack, $needle); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(string $haystack, string $needle): bool { return p\Php80::str_starts_with($haystack, $needle); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(string $haystack, string $needle): bool { return p\Php80::str_ends_with($haystack, $needle); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($res): int { return p\Php80::get_resource_id($res); } +}