diff --git a/classes/Storage/StorageFile.php b/classes/Storage/StorageFile.php index 4a3be5a3..857708c0 100755 --- a/classes/Storage/StorageFile.php +++ b/classes/Storage/StorageFile.php @@ -16,7 +16,8 @@ namespace ILAB\MediaCloud\Storage; -use Carbon\Carbon; + +use ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon; class StorageFile { /** @var string The type of file (DIR or FILE) */ diff --git a/classes/Tasks/Task.php b/classes/Tasks/Task.php index 7b97f934..2173a7b2 100755 --- a/classes/Tasks/Task.php +++ b/classes/Tasks/Task.php @@ -13,12 +13,10 @@ namespace ILAB\MediaCloud\Tasks; -use Carbon\Carbon; -use GPBMetadata\Google\Api\Log; use ILAB\MediaCloud\Model\Model; +use ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon; use function ILAB\MediaCloud\Utilities\gen_uuid; use ILAB\MediaCloud\Utilities\Logging\Logger; -use ILAB\MediaCloud\Utilities\Performance; use function ILAB\MediaCloud\Utilities\phpMemoryLimit; use ILAB\MediaCloud\Utilities\Tracker; diff --git a/classes/Tasks/TaskSchedule.php b/classes/Tasks/TaskSchedule.php index 31317edb..9d7ba4c4 100755 --- a/classes/Tasks/TaskSchedule.php +++ b/classes/Tasks/TaskSchedule.php @@ -13,9 +13,9 @@ namespace ILAB\MediaCloud\Tasks; -use Carbon\Carbon; use Cron\CronExpression; use ILAB\MediaCloud\Model\Model; +use ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon; use function ILAB\MediaCloud\Utilities\gen_uuid; use Lorisleiva\CronTranslator\CronParsingException; use Lorisleiva\CronTranslator\CronTranslator; diff --git a/classes/Tools/Debugging/System/SystemCompatibilityTool.php b/classes/Tools/Debugging/System/SystemCompatibilityTool.php index b4d672fb..49a6d22a 100755 --- a/classes/Tools/Debugging/System/SystemCompatibilityTool.php +++ b/classes/Tools/Debugging/System/SystemCompatibilityTool.php @@ -13,7 +13,7 @@ namespace ILAB\MediaCloud\Tools\Debugging\System; -use Carbon\CarbonInterval; +use ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonInterval; use FasterImage\FasterImage; use ILAB\MediaCloud\Storage\StorageGlobals; use ILAB\MediaCloud\Tasks\TaskRunner; diff --git a/classes/Tools/SettingsTrait.php b/classes/Tools/SettingsTrait.php index f4e49ff7..6ec8b303 100755 --- a/classes/Tools/SettingsTrait.php +++ b/classes/Tools/SettingsTrait.php @@ -363,6 +363,49 @@ public function renderNumberFieldSetting($args) { ]); } + /** + * Registers an option with a number input + * + * @param $option_name + * @param $title + * @param $settings_slug + * @param null $description + * @param null $conditions + */ + protected function registerImageFieldSetting($option_name, $title, $settings_slug, $description=null, $conditions=null) { + add_settings_field($option_name, + $title, + [$this, 'renderImageFieldSetting'], + $this->options_page, + $settings_slug, + ['option'=>$option_name,'description'=>$description, 'conditions' => $conditions]); + + } + + /** + * Renders a number input + * @param $args + */ + public function renderImageFieldSetting($args) { + $value = Environment::Option($args['option'], null, null); + + $imageUrl = null; + if (!empty($value)) { + $src = wp_get_attachment_image_src($value, 'medium'); + if (!empty($src)) { + $imageUrl = $src[0]; + } + } + + echo View::render_view('base/fields/image.php',[ + 'value' => $value, + 'imageUrl' => $imageUrl, + 'name' => $args['option'], + 'conditions' => $args['conditions'], + 'description' => (isset($args['description'])) ? $args['description'] : false + ]); + } + /** * Registers an option with a dropdown/select input * @param $option_name diff --git a/classes/Tools/Storage/StorageTool.php b/classes/Tools/Storage/StorageTool.php index 57d519aa..fdcb7b7e 100755 --- a/classes/Tools/Storage/StorageTool.php +++ b/classes/Tools/Storage/StorageTool.php @@ -422,6 +422,12 @@ function ( $file, $type, $fullsize ) { PHP_INT_MAX - 1, 1 ); + add_filter( + 'the_editor_content', + [ $this, 'filterContent' ], + PHP_INT_MAX - 1, + 2 + ); add_filter( 'render_block', [ $this, 'filterBlocks' ], @@ -2121,13 +2127,20 @@ public function filterGutenbergContent( $content ) * @return mixed * @throws StorageException */ - public function filterContent( $content ) + public function filterContent( $content, $context = 'post' ) { if ( !apply_filters( 'media-cloud/storage/can-filter-content', true ) ) { return $content; } + $originalContent = $content; + + if ( $context !== 'post' ) { + $content = str_replace( '<', '<', $content ); + $content = str_replace( '>', '>', $content ); + } + if ( !preg_match_all( '/]+>/', $content, $matches ) ) { - return $content; + return $originalContent; } $uploadDir = wp_get_upload_dir(); $replacements = []; @@ -2261,6 +2274,12 @@ public function filterContent( $content ) foreach ( $resizedReplacements as $id => $data ) { $content = $this->replaceImageInContent( $data['id'], $data, $content ); } + + if ( $context !== 'post' ) { + $content = str_replace( '<', '<', $content ); + $content = str_replace( '>', '>', $content ); + } + return $content; } diff --git a/classes/Tools/Tasks/CLI/TasksCommands.php b/classes/Tools/Tasks/CLI/TasksCommands.php index 24ca7ec2..d3313f01 100755 --- a/classes/Tools/Tasks/CLI/TasksCommands.php +++ b/classes/Tools/Tasks/CLI/TasksCommands.php @@ -16,10 +16,10 @@ namespace ILAB\MediaCloud\Tools\Tasks\CLI; -use Carbon\Carbon; use ILAB\MediaCloud\CLI\Command; use ILAB\MediaCloud\Tasks\TaskSchedule; use ILAB\MediaCloud\Tasks\TestTask; +use ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon; use function ILAB\MediaCloud\Utilities\arrayPath; use function ILAB\MediaCloud\Utilities\gen_uuid; diff --git a/classes/Tools/Tool.php b/classes/Tools/Tool.php index 9639fe12..cef7a9a3 100755 --- a/classes/Tools/Tool.php +++ b/classes/Tools/Tool.php @@ -520,6 +520,9 @@ public function registerSettings() { case 'advanced-privacy': $this->registerAdvancedPrivacy($option, $optionInfo['title'], $group, $conditions); break; + case 'image': + $this->registerImageFieldSetting($option, $optionInfo['title'], $group, $description, $conditions); + break; default: do_action('media-cloud/tools/register-setting-type', $option, $optionInfo, $group, $groupInfo, $conditions); } diff --git a/classes/Tools/ToolsManager.php b/classes/Tools/ToolsManager.php index c8180376..489f19db 100755 --- a/classes/Tools/ToolsManager.php +++ b/classes/Tools/ToolsManager.php @@ -158,21 +158,6 @@ public function __construct() 'ilab-media-tools-extime-notice' ); } - $runTime = Environment::Option( 'ilab_media_tools_run_time', null, 0 ); - - if ( $runTime == 0 ) { - Environment::UpdateOption( 'ilab_media_tools_run_time', microtime( true ) ); - } else { - if ( microtime( true ) - floatval( $runTime ) > 1209600 ) { - NoticeManager::instance()->displayAdminNotice( - 'info', - "Thanks for using Media Cloud! If you like it, please leave a review. Thank you!", - true, - 'ilab-media-tools-nag-notice' - ); - } - } - if ( !extension_loaded( 'mbstring' ) ) { NoticeManager::instance()->displayAdminNotice( 'warning', diff --git a/classes/Utilities/Misc/Carbon/Carbon.php b/classes/Utilities/Misc/Carbon/Carbon.php new file mode 100755 index 00000000..caa426e8 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Carbon.php @@ -0,0 +1,4750 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Carbon; + +use ILAB\MediaCloud\Utilities\Misc\Carbon\Exceptions\InvalidDateException; +use Closure; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use JsonSerializable; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface; +/** + * A simple API extension for DateTime + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $timestamp seconds since the Unix Epoch + * @property \DateTimeZone $timezone the current timezone + * @property \DateTimeZone $tz alias of timezone + * @property-read int $micro + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $dayOfYear 0 through 365 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read int $age does a diffInYears() with default parameters + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $offset the timezone offset in seconds from UTC + * @property-read int $offsetHours the timezone offset in hours from UTC + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName + * @property-read string $tzName + * @property-read string $englishDayOfWeek the day of week in English + * @property-read string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property-read string $englishMonth the day of week in English + * @property-read string $shortEnglishMonth the abbreviated day of week in English + * @property-read string $localeDayOfWeek the day of week in current locale LC_TIME + * @property-read string $shortLocaleDayOfWeek the abbreviated day of week in current locale LC_TIME + * @property-read string $localeMonth the month in current locale LC_TIME + * @property-read string $shortLocaleMonth the abbreviated month in current locale LC_TIME + */ +class Carbon extends \DateTime implements \JsonSerializable +{ + const NO_ZERO_DIFF = 01; + const JUST_NOW = 02; + const ONE_DAY_WORDS = 04; + const TWO_DAY_WORDS = 010; + // Substitutes for Carbon 2 modes + const DIFF_RELATIVE_TO_NOW = 'relative-to-now'; + const DIFF_RELATIVE_TO_OTHER = 'relative-to-other'; + /** + * The day constants. + */ + const SUNDAY = 0; + const MONDAY = 1; + const TUESDAY = 2; + const WEDNESDAY = 3; + const THURSDAY = 4; + const FRIDAY = 5; + const SATURDAY = 6; + /** + * Names of days of the week. + * + * @var array + */ + protected static $days = array(self::SUNDAY => 'Sunday', self::MONDAY => 'Monday', self::TUESDAY => 'Tuesday', self::WEDNESDAY => 'Wednesday', self::THURSDAY => 'Thursday', self::FRIDAY => 'Friday', self::SATURDAY => 'Saturday'); + /** + * Number of X in Y. + */ + const YEARS_PER_MILLENNIUM = 1000; + const YEARS_PER_CENTURY = 100; + const YEARS_PER_DECADE = 10; + const MONTHS_PER_YEAR = 12; + const MONTHS_PER_QUARTER = 3; + const WEEKS_PER_YEAR = 52; + const WEEKS_PER_MONTH = 4; + const DAYS_PER_WEEK = 7; + const HOURS_PER_DAY = 24; + const MINUTES_PER_HOUR = 60; + const SECONDS_PER_MINUTE = 60; + const MICROSECONDS_PER_MILLISECOND = 1000; + const MICROSECONDS_PER_SECOND = 1000000; + /** + * RFC7231 DateTime format. + * + * @var string + */ + const RFC7231_FORMAT = 'D, d M Y H:i:s \\G\\M\\T'; + /** + * Default format to use for __toString method when type juggling occurs. + * + * @var string + */ + const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s'; + /** + * Format for converting mocked time, includes microseconds. + * + * @var string + */ + const MOCK_DATETIME_FORMAT = 'Y-m-d H:i:s.u'; + /** + * Customizable PHP_INT_SIZE override. + * + * @var int + */ + public static $PHPIntSize = \PHP_INT_SIZE; + /** + * Format to use for __toString method when type juggling occurs. + * + * @var string + */ + protected static $toStringFormat = self::DEFAULT_TO_STRING_FORMAT; + /** + * First day of week. + * + * @var int + */ + protected static $weekStartsAt = self::MONDAY; + /** + * Last day of week. + * + * @var int + */ + protected static $weekEndsAt = self::SUNDAY; + /** + * Days of weekend. + * + * @var array + */ + protected static $weekendDays = array(self::SATURDAY, self::SUNDAY); + /** + * Midday/noon hour. + * + * @var int + */ + protected static $midDayAt = 12; + /** + * Format regex patterns. + * + * @var array + */ + protected static $regexFormats = array( + 'd' => '(3[01]|[12][0-9]|0[1-9])', + 'D' => '([a-zA-Z]{3})', + 'j' => '([123][0-9]|[1-9])', + 'l' => '([a-zA-Z]{2,})', + 'N' => '([1-7])', + 'S' => '([a-zA-Z]{2})', + 'w' => '([0-6])', + 'z' => '(36[0-5]|3[0-5][0-9]|[12][0-9]{2}|[1-9]?[0-9])', + 'W' => '(5[012]|[1-4][0-9]|[1-9])', + 'F' => '([a-zA-Z]{2,})', + 'm' => '(1[012]|0[1-9])', + 'M' => '([a-zA-Z]{3})', + 'n' => '(1[012]|[1-9])', + 't' => '(2[89]|3[01])', + 'L' => '(0|1)', + 'o' => '([1-9][0-9]{0,4})', + 'Y' => '([1-9]?[0-9]{4})', + 'y' => '([0-9]{2})', + 'a' => '(am|pm)', + 'A' => '(AM|PM)', + 'B' => '([0-9]{3})', + 'g' => '(1[012]|[1-9])', + 'G' => '(2[0-3]|1?[0-9])', + 'h' => '(1[012]|0[1-9])', + 'H' => '(2[0-3]|[01][0-9])', + 'i' => '([0-5][0-9])', + 's' => '([0-5][0-9])', + 'u' => '([0-9]{1,6})', + 'v' => '([0-9]{1,3})', + 'e' => '([a-zA-Z]{1,5})|([a-zA-Z]*\\/[a-zA-Z]*)', + 'I' => '(0|1)', + 'O' => '([\\+\\-](1[012]|0[0-9])[0134][05])', + 'P' => '([\\+\\-](1[012]|0[0-9]):[0134][05])', + 'T' => '([a-zA-Z]{1,5})', + 'Z' => '(-?[1-5]?[0-9]{1,4})', + 'U' => '([0-9]*)', + // The formats below are combinations of the above formats. + 'c' => '(([1-9]?[0-9]{4})\\-(1[012]|0[1-9])\\-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[\\+\\-](1[012]|0[0-9]):([0134][05]))', + // Y-m-dTH:i:sP + 'r' => '(([a-zA-Z]{3}), ([123][0-9]|[1-9]) ([a-zA-Z]{3}) ([1-9]?[0-9]{4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [\\+\\-](1[012]|0[0-9])([0134][05]))', + ); + /** + * A test Carbon instance to be returned when now instances are created. + * + * @var \Carbon\Carbon + */ + protected static $testNow; + /** + * A translator to ... er ... translate stuff. + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + /** + * The errors that can occur. + * + * @var array + */ + protected static $lastErrors; + /** + * The custom Carbon JSON serializer. + * + * @var callable|null + */ + protected static $serializer; + /** + * The registered string macros. + * + * @var array + */ + protected static $localMacros = array(); + /** + * Will UTF8 encoding be used to print localized date/time ? + * + * @var bool + */ + protected static $utf8 = \false; + /** + * Add microseconds to now on PHP < 7.1 and 7.1.3. true by default. + * + * @var bool + */ + protected static $microsecondsFallback = \true; + /** + * Indicates if months should be calculated with overflow. + * + * @var bool + */ + protected static $monthsOverflow = \true; + /** + * Indicates if years should be calculated with overflow. + * + * @var bool + */ + protected static $yearsOverflow = \true; + /** + * Indicates if years are compared with month by default so isSameMonth and isSameQuarter have $ofSameYear set + * to true by default. + * + * @var bool + */ + protected static $compareYearWithMonth = \false; + /** + * Options for diffForHumans(). + * + * @var int + */ + protected static $humanDiffOptions = self::NO_ZERO_DIFF; + /** + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions) + { + static::$humanDiffOptions = $humanDiffOptions; + } + /** + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption; + } + /** + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption; + } + /** + * @return int + */ + public static function getHumanDiffOptions() + { + return static::$humanDiffOptions; + } + /** + * Add microseconds to now on PHP < 7.1 and 7.1.3 if set to true, + * let microseconds to 0 on those PHP versions if false. + * + * @param bool $microsecondsFallback + */ + public static function useMicrosecondsFallback($microsecondsFallback = \true) + { + static::$microsecondsFallback = $microsecondsFallback; + } + /** + * Return true if microseconds fallback on PHP < 7.1 and 7.1.3 is + * enabled. false if disabled. + * + * @return bool + */ + public static function isMicrosecondsFallbackEnabled() + { + return static::$microsecondsFallback; + } + /** + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = \true) + { + static::$monthsOverflow = $monthsOverflow; + } + /** + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow() + { + static::$monthsOverflow = \true; + } + /** + * Get the month overflow behavior. + * + * @return bool + */ + public static function shouldOverflowMonths() + { + return static::$monthsOverflow; + } + /** + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = \true) + { + static::$yearsOverflow = $yearsOverflow; + } + /** + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow() + { + static::$yearsOverflow = \true; + } + /** + * Get the month overflow behavior. + * + * @return bool + */ + public static function shouldOverflowYears() + { + return static::$yearsOverflow; + } + /** + * Get the month comparison default behavior. + * + * @return bool + */ + public static function compareYearWithMonth($compareYearWithMonth = \true) + { + static::$compareYearWithMonth = $compareYearWithMonth; + } + /** + * Get the month comparison default behavior. + * + * @return bool + */ + public static function shouldCompareYearWithMonth() + { + return static::$compareYearWithMonth; + } + /** + * Creates a DateTimeZone from a string, DateTimeZone or integer offset. + * + * @param \DateTimeZone|string|int|null $object + * + * @throws \InvalidArgumentException + * + * @return \DateTimeZone + */ + protected static function safeCreateDateTimeZone($object) + { + if ($object === null) { + // Don't return null... avoid Bug #52063 in PHP <5.3.6 + return new \DateTimeZone(\date_default_timezone_get()); + } + if ($object instanceof \DateTimeZone) { + return $object; + } + if (\is_numeric($object)) { + $tzName = \timezone_name_from_abbr(null, $object * 3600, \true); + if ($tzName === \false) { + throw new \InvalidArgumentException('Unknown or bad timezone (' . $object . ')'); + } + $object = $tzName; + } + $tz = @\timezone_open($object = (string) $object); + if ($tz !== \false) { + return $tz; + } + // Work-around for a bug fixed in PHP 5.5.10 https://bugs.php.net/bug.php?id=45528 + // See: https://stackoverflow.com/q/14068594/2646927 + // @codeCoverageIgnoreStart + if (\strpos($object, ':') !== \false) { + try { + return static::createFromFormat('O', $object)->getTimezone(); + } catch (\InvalidArgumentException $e) { + // + } + } + // @codeCoverageIgnoreEnd + throw new \InvalidArgumentException('Unknown or bad timezone (' . $object . ')'); + } + /////////////////////////////////////////////////////////////////// + //////////////////////////// CONSTRUCTORS ///////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Create a new Carbon instance. + * + * Please see the testing aids section (specifically static::setTestNow()) + * for more on the possibility of this constructor returning a test instance. + * + * @param string|null $time + * @param \DateTimeZone|string|null $tz + */ + public function __construct($time = null, $tz = null) + { + // If the class has a test now set and we are trying to create a now() + // instance then override as required + $isNow = empty($time) || $time === 'now'; + if (static::hasTestNow() && ($isNow || static::hasRelativeKeywords($time))) { + $testInstance = clone static::getTestNow(); + //shift the time according to the given time zone + if ($tz !== null && $tz !== static::getTestNow()->getTimezone()) { + $testInstance->setTimezone($tz); + } else { + $tz = $testInstance->getTimezone(); + } + if (static::hasRelativeKeywords($time)) { + $testInstance->modify($time); + } + $time = $testInstance->format(static::MOCK_DATETIME_FORMAT); + } + $timezone = static::safeCreateDateTimeZone($tz); + // @codeCoverageIgnoreStart + if ($isNow && !isset($testInstance) && static::isMicrosecondsFallbackEnabled() && (\version_compare(\PHP_VERSION, '7.1.0-dev', '<') || \version_compare(\PHP_VERSION, '7.1.3-dev', '>=') && \version_compare(\PHP_VERSION, '7.1.4-dev', '<'))) { + // Get microseconds from microtime() if "now" asked and PHP < 7.1 and PHP 7.1.3 if fallback enabled. + list($microTime, $timeStamp) = \explode(' ', \microtime()); + $dateTime = new \DateTime('now', $timezone); + $dateTime->setTimestamp($timeStamp); + // Use the timestamp returned by microtime as now can happen in the next second + $time = $dateTime->format(static::DEFAULT_TO_STRING_FORMAT) . \substr($microTime, 1, 7); + } + // @codeCoverageIgnoreEnd + // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127 + if (\strpos((string) 0.1, '.') === \false) { + $locale = \setlocale(\LC_NUMERIC, '0'); + \setlocale(\LC_NUMERIC, 'C'); + } + parent::__construct($time, $timezone); + if (isset($locale)) { + \setlocale(\LC_NUMERIC, $locale); + } + static::setLastErrors(parent::getLastErrors()); + } + /** + * Create a Carbon instance from a DateTime one. + * + * @param \DateTime|\DateTimeInterface $date + * + * @return static + */ + public static function instance($date) + { + if ($date instanceof static) { + return clone $date; + } + static::expectDateTime($date); + return new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + } + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|null $time + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function parse($time = null, $tz = null) + { + return new static($time, $tz); + } + /** + * Get a Carbon instance for the current date and time. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function now($tz = null) + { + return new static(null, $tz); + } + /** + * Create a Carbon instance for today. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function today($tz = null) + { + return static::parse('today', $tz); + } + /** + * Create a Carbon instance for tomorrow. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null) + { + return static::parse('tomorrow', $tz); + } + /** + * Create a Carbon instance for yesterday. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null) + { + return static::parse('yesterday', $tz); + } + /** + * Create a Carbon instance for the greatest supported date. + * + * @return static + */ + public static function maxValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(\PHP_INT_MAX); + // @codeCoverageIgnore + } + // 64 bit + return static::create(9999, 12, 31, 23, 59, 59); + } + /** + * Create a Carbon instance for the lowest supported date. + * + * @return static + */ + public static function minValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(~\PHP_INT_MAX); + // @codeCoverageIgnore + } + // 64 bit + return static::create(1, 1, 1, 0, 0, 0); + } + /** + * Create a new Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function create($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $now = static::hasTestNow() ? static::getTestNow() : static::now($tz); + $defaults = \array_combine(array('year', 'month', 'day', 'hour', 'minute', 'second'), \explode('-', $now->format('Y-n-j-G-i-s'))); + $year = $year === null ? $defaults['year'] : $year; + $month = $month === null ? $defaults['month'] : $month; + $day = $day === null ? $defaults['day'] : $day; + if ($hour === null) { + $hour = $defaults['hour']; + $minute = $minute === null ? $defaults['minute'] : $minute; + $second = $second === null ? $defaults['second'] : $second; + } else { + $minute = $minute === null ? 0 : $minute; + $second = $second === null ? 0 : $second; + } + $fixYear = null; + if ($year < 0) { + $fixYear = $year; + $year = 0; + } elseif ($year > 9999) { + $fixYear = $year - 9999; + $year = 9999; + } + $instance = static::createFromFormat('!Y-n-j G:i:s', \sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz); + if ($fixYear !== null) { + $instance->addYears($fixYear); + } + return $instance; + } + /** + * Create a new safe Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * If one of the set values is not valid, an \InvalidArgumentException + * will be thrown. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param \DateTimeZone|string|null $tz + * + * @throws \Carbon\Exceptions\InvalidDateException|\InvalidArgumentException + * + * @return static + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $fields = array('year' => array(0, 9999), 'month' => array(0, 12), 'day' => array(0, 31), 'hour' => array(0, 24), 'minute' => array(0, 59), 'second' => array(0, 59)); + foreach ($fields as $field => $range) { + if (${$field} !== null && (!\is_int(${$field}) || ${$field} < $range[0] || ${$field} > $range[1])) { + throw new \ILAB\MediaCloud\Utilities\Misc\Carbon\Exceptions\InvalidDateException($field, ${$field}); + } + } + $instance = static::create($year, $month, $day, $hour, $minute, $second, $tz); + foreach (\array_reverse($fields) as $field => $range) { + if (${$field} !== null && (!\is_int(${$field}) || ${$field} !== $instance->{$field})) { + throw new \ILAB\MediaCloud\Utilities\Misc\Carbon\Exceptions\InvalidDateException($field, ${$field}); + } + } + return $instance; + } + /** + * Create a Carbon instance from just a date. The time portion is set to now. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, null, null, null, $tz); + } + /** + * Create a Carbon instance from just a date. The time portion is set to midnight. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, 0, 0, 0, $tz); + } + /** + * Create a Carbon instance from just a time. The date portion is set to today. + * + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function createFromTime($hour = null, $minute = null, $second = null, $tz = null) + { + return static::create(null, null, null, $hour, $minute, $second, $tz); + } + /** + * Create a Carbon instance from a time string. The date portion is set to today. + * + * @param string $time + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null) + { + return static::today($tz)->setTimeFromTimeString($time); + } + private static function createFromFormatAndTimezone($format, $time, $tz) + { + return $tz !== null ? parent::createFromFormat($format, $time, static::safeCreateDateTimeZone($tz)) : parent::createFromFormat($format, $time); + } + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param \DateTimeZone|string|null $tz + * + * @throws InvalidArgumentException + * + * @return static + */ + public static function createFromFormat($format, $time, $tz = null) + { + // First attempt to create an instance, so that error messages are based on the unmodified format. + $date = self::createFromFormatAndTimezone($format, $time, $tz); + $lastErrors = parent::getLastErrors(); + if (($mock = static::getTestNow()) && ($date instanceof \DateTime || $date instanceof \DateTimeInterface)) { + // Set timezone from mock if custom timezone was neither given directly nor as a part of format. + // First let's skip the part that will be ignored by the parser. + $nonEscaped = '(?getTimezone(); + } + // Prepend mock datetime only if the format does not contain non escaped unix epoch reset flag. + if (!\preg_match("/{$nonEscaped}[!|]/", $format)) { + $format = static::MOCK_DATETIME_FORMAT . ' ' . $format; + $time = $mock->format(static::MOCK_DATETIME_FORMAT) . ' ' . $time; + } + // Regenerate date from the modified format to base result on the mocked instance instead of now. + $date = self::createFromFormatAndTimezone($format, $time, $tz); + } + if ($date instanceof \DateTime || $date instanceof \DateTimeInterface) { + $instance = static::instance($date); + $instance::setLastErrors($lastErrors); + return $instance; + } + throw new \InvalidArgumentException(\implode(\PHP_EOL, $lastErrors['errors'])); + } + /** + * Set last errors. + * + * @param array $lastErrors + * + * @return void + */ + private static function setLastErrors(array $lastErrors) + { + static::$lastErrors = $lastErrors; + } + /** + * {@inheritdoc} + */ + public static function getLastErrors() + { + return static::$lastErrors; + } + /** + * Create a Carbon instance from a timestamp. + * + * @param int $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null) + { + return static::today($tz)->setTimestamp($timestamp); + } + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * @param int $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null) + { + return static::createFromFormat('U.u', \sprintf('%F', $timestamp / 1000))->setTimezone($tz); + } + /** + * Create a Carbon instance from an UTC timestamp. + * + * @param int $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp) + { + return new static('@' . $timestamp); + } + /** + * Make a Carbon instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @return static|null + */ + public static function make($var) + { + if ($var instanceof \DateTime || $var instanceof \DateTimeInterface) { + return static::instance($var); + } + if (\is_string($var)) { + $var = \trim($var); + $first = \substr($var, 0, 1); + if (\is_string($var) && $first !== 'P' && $first !== 'R' && \preg_match('/[a-z0-9]/i', $var)) { + return static::parse($var); + } + } + } + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + /** + * Returns a present instance in the same timezone. + * + * @return static + */ + public function nowWithSameTz() + { + return static::now($this->getTimezone()); + } + /** + * Throws an exception if the given object is not a DateTime and does not implement DateTimeInterface + * and not in $other. + * + * @param mixed $date + * @param string|array $other + * + * @throws \InvalidArgumentException + */ + protected static function expectDateTime($date, $other = array()) + { + $message = 'Expected '; + foreach ((array) $other as $expect) { + $message .= "{$expect}, "; + } + if (!$date instanceof \DateTime && !$date instanceof \DateTimeInterface) { + throw new \InvalidArgumentException($message . 'DateTime or DateTimeInterface, ' . (\is_object($date) ? \get_class($date) : \gettype($date)) . ' given'); + } + } + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + protected function resolveCarbon($date = null) + { + if (!$date) { + return $this->nowWithSameTz(); + } + if (\is_string($date)) { + return static::parse($date, $this->getTimezone()); + } + static::expectDateTime($date, array('null', 'string')); + return $date instanceof self ? $date : static::instance($date); + } + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws \InvalidArgumentException + * + * @return string|int|bool|\DateTimeZone + */ + public function __get($name) + { + static $formats = array('year' => 'Y', 'yearIso' => 'o', 'month' => 'n', 'day' => 'j', 'hour' => 'G', 'minute' => 'i', 'second' => 's', 'micro' => 'u', 'dayOfWeek' => 'w', 'dayOfWeekIso' => 'N', 'dayOfYear' => 'z', 'weekOfYear' => 'W', 'daysInMonth' => 't', 'timestamp' => 'U', 'englishDayOfWeek' => 'l', 'shortEnglishDayOfWeek' => 'D', 'englishMonth' => 'F', 'shortEnglishMonth' => 'M', 'localeDayOfWeek' => '%A', 'shortLocaleDayOfWeek' => '%a', 'localeMonth' => '%B', 'shortLocaleMonth' => '%b'); + switch (\true) { + case isset($formats[$name]): + $format = $formats[$name]; + $method = \substr($format, 0, 1) === '%' ? 'formatLocalized' : 'format'; + $value = $this->{$method}($format); + return \is_numeric($value) ? (int) $value : $value; + case $name === 'weekOfMonth': + return (int) \ceil($this->day / static::DAYS_PER_WEEK); + case $name === 'weekNumberInMonth': + return (int) \ceil(($this->day + $this->copy()->startOfMonth()->dayOfWeek - 1) / static::DAYS_PER_WEEK); + case $name === 'age': + return $this->diffInYears(); + case $name === 'quarter': + return (int) \ceil($this->month / static::MONTHS_PER_QUARTER); + case $name === 'offset': + return $this->getOffset(); + case $name === 'offsetHours': + return $this->getOffset() / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR; + case $name === 'dst': + return $this->format('I') === '1'; + case $name === 'local': + return $this->getOffset() === $this->copy()->setTimezone(\date_default_timezone_get())->getOffset(); + case $name === 'utc': + return $this->getOffset() === 0; + case $name === 'timezone' || $name === 'tz': + return $this->getTimezone(); + case $name === 'timezoneName' || $name === 'tzName': + return $this->getTimezone()->getName(); + default: + throw new \InvalidArgumentException(\sprintf("Unknown getter '%s'", $name)); + } + } + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name) + { + try { + $this->__get($name); + } catch (\InvalidArgumentException $e) { + return \false; + } + return \true; + } + /** + * Set a part of the Carbon object + * + * @param string $name + * @param string|int|\DateTimeZone $value + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function __set($name, $value) + { + switch ($name) { + case 'year': + case 'month': + case 'day': + case 'hour': + case 'minute': + case 'second': + list($year, $month, $day, $hour, $minute, $second) = \explode('-', $this->format('Y-n-j-G-i-s')); + ${$name} = $value; + $this->setDateTime($year, $month, $day, $hour, $minute, $second); + break; + case 'timestamp': + parent::setTimestamp($value); + break; + case 'timezone': + case 'tz': + $this->setTimezone($value); + break; + default: + throw new \InvalidArgumentException(\sprintf("Unknown setter '%s'", $name)); + } + } + /** + * Set the instance's year + * + * @param int $value + * + * @return static + */ + public function year($value) + { + $this->year = $value; + return $this; + } + /** + * Set the instance's month + * + * @param int $value + * + * @return static + */ + public function month($value) + { + $this->month = $value; + return $this; + } + /** + * Set the instance's day + * + * @param int $value + * + * @return static + */ + public function day($value) + { + $this->day = $value; + return $this; + } + /** + * Set the instance's hour + * + * @param int $value + * + * @return static + */ + public function hour($value) + { + $this->hour = $value; + return $this; + } + /** + * Set the instance's minute + * + * @param int $value + * + * @return static + */ + public function minute($value) + { + $this->minute = $value; + return $this; + } + /** + * Set the instance's second + * + * @param int $value + * + * @return static + */ + public function second($value) + { + $this->second = $value; + return $this; + } + /** + * Sets the current date of the DateTime object to a different date. + * Calls modify as a workaround for a php bug + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + * + * @see https://github.com/briannesbitt/Carbon/issues/539 + * @see https://bugs.php.net/bug.php?id=63863 + */ + public function setDate($year, $month, $day) + { + $this->modify('+0 day'); + return parent::setDate($year, $month, $day); + } + /** + * Set the date and time all together + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @param int $second + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0) + { + return $this->setDate($year, $month, $day)->setTime($hour, $minute, $second); + } + /** + * Set the time by time string + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time) + { + if (\strpos($time, ':') === \false) { + $time .= ':0'; + } + return $this->modify($time); + } + /** + * Set the instance's timestamp + * + * @param int $value + * + * @return static + */ + public function timestamp($value) + { + return $this->setTimestamp($value); + } + /** + * Alias for setTimezone() + * + * @param \DateTimeZone|string $value + * + * @return static + */ + public function timezone($value) + { + return $this->setTimezone($value); + } + /** + * Alias for setTimezone() + * + * @param \DateTimeZone|string $value + * + * @return static + */ + public function tz($value) + { + return $this->setTimezone($value); + } + /** + * Set the instance's timezone from a string or object + * + * @param \DateTimeZone|string $value + * + * @return static + */ + public function setTimezone($value) + { + parent::setTimezone(static::safeCreateDateTimeZone($value)); + // https://bugs.php.net/bug.php?id=72338 + // just workaround on this bug + $this->getTimestamp(); + return $this; + } + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param \Carbon\Carbon|\DateTimeInterface $date + * + * @return static + */ + public function setDateFrom($date) + { + $date = static::instance($date); + $this->setDate($date->year, $date->month, $date->day); + return $this; + } + /** + * Set the hour, day, and time for this instance to that of the passed instance. + * + * @param \Carbon\Carbon|\DateTimeInterface $date + * + * @return static + */ + public function setTimeFrom($date) + { + $date = static::instance($date); + $this->setTime($date->hour, $date->minute, $date->second); + return $this; + } + /** + * Get the days of the week + * + * @return array + */ + public static function getDays() + { + return static::$days; + } + /////////////////////////////////////////////////////////////////// + /////////////////////// WEEK SPECIAL DAYS ///////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt() + { + return static::$weekStartsAt; + } + /** + * Set the first day of week + * + * @param int $day week start day + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function setWeekStartsAt($day) + { + if ($day > static::SATURDAY || $day < static::SUNDAY) { + throw new \InvalidArgumentException('Day of a week should be greater than or equal to 0 and less than or equal to 6.'); + } + static::$weekStartsAt = $day; + } + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt() + { + return static::$weekEndsAt; + } + /** + * Set the last day of week + * + * @param int $day + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function setWeekEndsAt($day) + { + if ($day > static::SATURDAY || $day < static::SUNDAY) { + throw new \InvalidArgumentException('Day of a week should be greater than or equal to 0 and less than or equal to 6.'); + } + static::$weekEndsAt = $day; + } + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays() + { + return static::$weekendDays; + } + /** + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days) + { + static::$weekendDays = $days; + } + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt() + { + return static::$midDayAt; + } + /** + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour) + { + static::$midDayAt = $hour; + } + /////////////////////////////////////////////////////////////////// + ///////////////////////// TESTING AIDS //////////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * @param \Carbon\Carbon|null $testNow real or mock Carbon instance + * @param \Carbon\Carbon|string|null $testNow + */ + public static function setTestNow($testNow = null) + { + static::$testNow = \is_string($testNow) ? static::parse($testNow) : $testNow; + } + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return static the current instance used for testing + */ + public static function getTestNow() + { + return static::$testNow; + } + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return bool true if there is a test instance, otherwise false + */ + public static function hasTestNow() + { + return static::getTestNow() !== null; + } + /** + * Determine if a time string will produce a relative date. + * + * @param string $time + * + * @return bool true if time match a relative date, false if absolute or invalid time string + */ + public static function hasRelativeKeywords($time) + { + if (\strtotime($time) === \false) { + return \false; + } + $date1 = new \DateTime('2000-01-01T00:00:00Z'); + $date1->modify($time); + $date2 = new \DateTime('2001-12-25T00:00:00Z'); + $date2->modify($time); + return $date1 != $date2; + } + /////////////////////////////////////////////////////////////////// + /////////////////////// LOCALIZATION ////////////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Initialize the translator instance if necessary. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + protected static function translator() + { + if (static::$translator === null) { + static::$translator = \ILAB\MediaCloud\Utilities\Misc\Carbon\Translator::get(); + } + return static::$translator; + } + /** + * Get the translator instance in use + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator() + { + return static::translator(); + } + /** + * Set the translator instance to use + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface $translator) + { + static::$translator = $translator; + } + /** + * Get the current translator locale + * + * @return string + */ + public static function getLocale() + { + return static::translator()->getLocale(); + } + /** + * Set the current translator locale and indicate if the source locale file exists + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale) + { + return static::translator()->setLocale($locale) !== \false; + } + /** + * Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * + * @param string $locale locale ex. en + * + * @return mixed + */ + public static function executeWithLocale($locale, $func) + { + $currentLocale = static::getLocale(); + $result = \call_user_func($func, static::setLocale($locale) ? static::getLocale() : \false, static::translator()); + static::setLocale($currentLocale); + return $result; + } + /** + * Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasShortUnits($locale) + { + return static::executeWithLocale($locale, function ($newLocale, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface $translator) { + return $newLocale && (($y = $translator->trans('y')) !== 'y' && $y !== $translator->trans('year')) || ($y = $translator->trans('d')) !== 'd' && $y !== $translator->trans('day') || ($y = $translator->trans('h')) !== 'h' && $y !== $translator->trans('hour'); + }); + } + /** + * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface $translator) { + return $newLocale && $translator->trans('ago') !== 'ago' && $translator->trans('from_now') !== 'from_now' && $translator->trans('before') !== 'before' && $translator->trans('after') !== 'after'; + }); + } + /** + * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffOneDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface $translator) { + return $newLocale && $translator->trans('diff_now') !== 'diff_now' && $translator->trans('diff_yesterday') !== 'diff_yesterday' && $translator->trans('diff_tomorrow') !== 'diff_tomorrow'; + }); + } + /** + * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffTwoDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface $translator) { + return $newLocale && $translator->trans('diff_before_yesterday') !== 'diff_before_yesterday' && $translator->trans('diff_after_tomorrow') !== 'diff_after_tomorrow'; + }); + } + /** + * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasPeriodSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface $translator) { + return $newLocale && $translator->trans('period_recurrences') !== 'period_recurrences' && $translator->trans('period_interval') !== 'period_interval' && $translator->trans('period_start_date') !== 'period_start_date' && $translator->trans('period_end_date') !== 'period_end_date'; + }); + } + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @return array + */ + public static function getAvailableLocales() + { + $translator = static::translator(); + $locales = array(); + if ($translator instanceof \ILAB\MediaCloud\Utilities\Misc\Carbon\Translator) { + foreach (\glob(__DIR__ . '/Lang/*.php') as $file) { + $locales[] = \substr($file, \strrpos($file, '/') + 1, -4); + } + $locales = \array_unique(\array_merge($locales, \array_keys($translator->getMessages()))); + } + return $locales; + } + /////////////////////////////////////////////////////////////////// + /////////////////////// STRING FORMATTING ///////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Set if UTF8 will be used for localized date/time + * + * @param bool $utf8 + */ + public static function setUtf8($utf8) + { + static::$utf8 = $utf8; + } + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() http://php.net/setlocale. + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format) + { + // Check for Windows to find and replace the %e modifier correctly. + if (\strtoupper(\substr(\PHP_OS, 0, 3)) === 'WIN') { + $format = \preg_replace('#(?toDateTimeString())); + return static::$utf8 ? \utf8_encode($formatted) : $formatted; + } + /** + * Reset the format used to the default when type juggling a Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat() + { + static::setToStringFormat(static::DEFAULT_TO_STRING_FORMAT); + } + /** + * Set the default format used when type juggling a Carbon instance to a string + * + * @param string|Closure $format + * + * @return void + */ + public static function setToStringFormat($format) + { + static::$toStringFormat = $format; + } + /** + * Format the instance as a string using the set format + * + * @return string + */ + public function __toString() + { + $format = static::$toStringFormat; + return $this->format($format instanceof \Closure ? $format($this) : $format); + } + /** + * Format the instance as date + * + * @return string + */ + public function toDateString() + { + return $this->format('Y-m-d'); + } + /** + * Format the instance as a readable date + * + * @return string + */ + public function toFormattedDateString() + { + return $this->format('M j, Y'); + } + /** + * Format the instance as time + * + * @return string + */ + public function toTimeString() + { + return $this->format('H:i:s'); + } + /** + * Format the instance as date and time + * + * @return string + */ + public function toDateTimeString() + { + return $this->format('Y-m-d H:i:s'); + } + /** + * Format the instance as date and time T-separated with no timezone + * + * @example + * ``` + * echo Carbon::now()->toDateTimeLocalString(); + * ``` + * + * @return string + */ + public function toDateTimeLocalString() + { + return $this->format('Y-m-d\\TH:i:s'); + } + /** + * Format the instance with day, date and time + * + * @return string + */ + public function toDayDateTimeString() + { + return $this->format('D, M j, Y g:i A'); + } + /** + * Format the instance as ATOM + * + * @return string + */ + public function toAtomString() + { + return $this->format(static::ATOM); + } + /** + * Format the instance as COOKIE + * + * @return string + */ + public function toCookieString() + { + return $this->format(static::COOKIE); + } + /** + * Format the instance as ISO8601 + * + * @return string + */ + public function toIso8601String() + { + return $this->toAtomString(); + } + /** + * Format the instance as RFC822 + * + * @return string + */ + public function toRfc822String() + { + return $this->format(static::RFC822); + } + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @return string + */ + public function toIso8601ZuluString() + { + return $this->copy()->setTimezone('UTC')->format('Y-m-d\\TH:i:s\\Z'); + } + /** + * Format the instance as RFC850 + * + * @return string + */ + public function toRfc850String() + { + return $this->format(static::RFC850); + } + /** + * Format the instance as RFC1036 + * + * @return string + */ + public function toRfc1036String() + { + return $this->format(static::RFC1036); + } + /** + * Format the instance as RFC1123 + * + * @return string + */ + public function toRfc1123String() + { + return $this->format(static::RFC1123); + } + /** + * Format the instance as RFC2822 + * + * @return string + */ + public function toRfc2822String() + { + return $this->format(static::RFC2822); + } + /** + * Format the instance as RFC3339 + * + * @return string + */ + public function toRfc3339String() + { + return $this->format(static::RFC3339); + } + /** + * Format the instance as RSS + * + * @return string + */ + public function toRssString() + { + return $this->format(static::RSS); + } + /** + * Format the instance as W3C + * + * @return string + */ + public function toW3cString() + { + return $this->format(static::W3C); + } + /** + * Format the instance as RFC7231 + * + * @return string + */ + public function toRfc7231String() + { + return $this->copy()->setTimezone('GMT')->format(static::RFC7231_FORMAT); + } + /** + * Get default array representation + * + * @return array + */ + public function toArray() + { + return array('year' => $this->year, 'month' => $this->month, 'day' => $this->day, 'dayOfWeek' => $this->dayOfWeek, 'dayOfYear' => $this->dayOfYear, 'hour' => $this->hour, 'minute' => $this->minute, 'second' => $this->second, 'micro' => $this->micro, 'timestamp' => $this->timestamp, 'formatted' => $this->format(self::DEFAULT_TO_STRING_FORMAT), 'timezone' => $this->timezone); + } + /** + * Get default object representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toObject()); + * ``` + * + * @return object + */ + public function toObject() + { + return (object) $this->toArray(); + } + /** + * Returns english human readable complete date string. + * + * @example + * ``` + * echo Carbon::now()->toString(); + * ``` + * + * @return string + */ + public function toString() + { + return $this->format('D M j Y H:i:s \\G\\M\\TO'); + } + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z, if $keepOffset truthy, offset will be kept: + * 1977-04-22T01:00:00-05:00). + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toISOString() . "\n"; + * echo Carbon::now('America/Toronto')->toISOString(true) . "\n"; + * ``` + * + * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC. + * + * @return null|string + */ + public function toISOString($keepOffset = \false) + { + if ($this->year === 0) { + return null; + } + $year = $this->year < 0 || $this->year > 9999 ? ($this->year < 0 ? '-' : '+') . \str_pad(\abs($this->year), 6, '0', \STR_PAD_LEFT) : \str_pad($this->year, 4, '0', \STR_PAD_LEFT); + $tz = $keepOffset ? $this->format('P') : 'Z'; + $date = $keepOffset ? $this : $this->copy()->setTimezone('UTC'); + return $year . $date->format('-m-d\\TH:i:s.u') . $tz; + } + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone. + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toJSON(); + * ``` + * + * @return null|string + */ + public function toJSON() + { + return $this->toISOString(); + } + /** + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTime()); + * ``` + * + * @return DateTime + */ + public function toDateTime() + { + return new \DateTime($this->format('Y-m-d H:i:s.u'), $this->getTimezone()); + } + /** + * @alias toDateTime + * + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDate()); + * ``` + * + * @return DateTime + */ + public function toDate() + { + return $this->toDateTime(); + } + /////////////////////////////////////////////////////////////////// + ////////////////////////// COMPARISONS //////////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Determines if the instance is equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function eq($date) + { + return $this == $date; + } + /** + * Determines if the instance is equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see eq() + * + * @return bool + */ + public function equalTo($date) + { + return $this->eq($date); + } + /** + * Determines if the instance is not equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function ne($date) + { + return !$this->eq($date); + } + /** + * Determines if the instance is not equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see ne() + * + * @return bool + */ + public function notEqualTo($date) + { + return $this->ne($date); + } + /** + * Determines if the instance is greater (after) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function gt($date) + { + return $this > $date; + } + /** + * Determines if the instance is greater (after) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see gt() + * + * @return bool + */ + public function greaterThan($date) + { + return $this->gt($date); + } + /** + * Determines if the instance is greater (after) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see gt() + * + * @return bool + */ + public function isAfter($date) + { + return $this->gt($date); + } + /** + * Determines if the instance is greater (after) than or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function gte($date) + { + return $this >= $date; + } + /** + * Determines if the instance is greater (after) than or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see gte() + * + * @return bool + */ + public function greaterThanOrEqualTo($date) + { + return $this->gte($date); + } + /** + * Determines if the instance is less (before) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lt($date) + { + return $this < $date; + } + /** + * Determines if the instance is less (before) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lt() + * + * @return bool + */ + public function lessThan($date) + { + return $this->lt($date); + } + /** + * Determines if the instance is less (before) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lt() + * + * @return bool + */ + public function isBefore($date) + { + return $this->lt($date); + } + /** + * Determines if the instance is less (before) or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lte($date) + { + return $this <= $date; + } + /** + * Determines if the instance is less (before) or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lte() + * + * @return bool + */ + public function lessThanOrEqualTo($date) + { + return $this->lte($date); + } + /** + * Determines if the instance is between two others + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($date1, $date2, $equal = \true) + { + if ($date1->gt($date2)) { + $temp = $date1; + $date1 = $date2; + $date2 = $temp; + } + if ($equal) { + return $this->gte($date1) && $this->lte($date2); + } + return $this->gt($date1) && $this->lt($date2); + } + protected function floatDiffInSeconds($date) + { + $date = $this->resolveCarbon($date); + return \abs($this->diffInRealSeconds($date, \false) + ($date->micro - $this->micro) / 1000000); + } + /** + * Determines if the instance is between two others + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if a > and < comparison should be used or <= or >= + * + * @return bool + */ + public function isBetween($date1, $date2, $equal = \true) + { + return $this->between($date1, $date2, $equal); + } + /** + * Get the closest date from the instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2) + { + return $this->floatDiffInSeconds($date1) < $this->floatDiffInSeconds($date2) ? $date1 : $date2; + } + /** + * Get the farthest date from the instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2) + { + return $this->floatDiffInSeconds($date1) > $this->floatDiffInSeconds($date2) ? $date1 : $date2; + } + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + public function min($date = null) + { + $date = $this->resolveCarbon($date); + return $this->lt($date) ? $this : $date; + } + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see min() + * + * @return static + */ + public function minimum($date = null) + { + return $this->min($date); + } + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + public function max($date = null) + { + $date = $this->resolveCarbon($date); + return $this->gt($date) ? $this : $date; + } + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see max() + * + * @return static + */ + public function maximum($date = null) + { + return $this->max($date); + } + /** + * Determines if the instance is a weekday. + * + * @return bool + */ + public function isWeekday() + { + return !$this->isWeekend(); + } + /** + * Determines if the instance is a weekend day. + * + * @return bool + */ + public function isWeekend() + { + return \in_array($this->dayOfWeek, static::$weekendDays); + } + /** + * Determines if the instance is yesterday. + * + * @return bool + */ + public function isYesterday() + { + return $this->toDateString() === static::yesterday($this->getTimezone())->toDateString(); + } + /** + * Determines if the instance is today. + * + * @return bool + */ + public function isToday() + { + return $this->toDateString() === $this->nowWithSameTz()->toDateString(); + } + /** + * Determines if the instance is tomorrow. + * + * @return bool + */ + public function isTomorrow() + { + return $this->toDateString() === static::tomorrow($this->getTimezone())->toDateString(); + } + /** + * Determines if the instance is within the next week. + * + * @return bool + */ + public function isNextWeek() + { + return $this->weekOfYear === $this->nowWithSameTz()->addWeek()->weekOfYear; + } + /** + * Determines if the instance is within the last week. + * + * @return bool + */ + public function isLastWeek() + { + return $this->weekOfYear === $this->nowWithSameTz()->subWeek()->weekOfYear; + } + /** + * Determines if the instance is within the next quarter. + * + * @return bool + */ + public function isNextQuarter() + { + return $this->quarter === $this->nowWithSameTz()->addQuarter()->quarter; + } + /** + * Determines if the instance is within the last quarter. + * + * @return bool + */ + public function isLastQuarter() + { + return $this->quarter === $this->nowWithSameTz()->subQuarter()->quarter; + } + /** + * Determines if the instance is within the next month. + * + * @return bool + */ + public function isNextMonth() + { + return $this->month === $this->nowWithSameTz()->addMonthNoOverflow()->month; + } + /** + * Determines if the instance is within the last month. + * + * @return bool + */ + public function isLastMonth() + { + return $this->month === $this->nowWithSameTz()->subMonthNoOverflow()->month; + } + /** + * Determines if the instance is within next year. + * + * @return bool + */ + public function isNextYear() + { + return $this->year === $this->nowWithSameTz()->addYear()->year; + } + /** + * Determines if the instance is within the previous year. + * + * @return bool + */ + public function isLastYear() + { + return $this->year === $this->nowWithSameTz()->subYear()->year; + } + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @return bool + */ + public function isFuture() + { + return $this->gt($this->nowWithSameTz()); + } + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @return bool + */ + public function isPast() + { + return $this->lt($this->nowWithSameTz()); + } + /** + * Determines if the instance is a leap year. + * + * @return bool + */ + public function isLeapYear() + { + return $this->format('L') === '1'; + } + /** + * Determines if the instance is a long year + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongYear() + { + return static::create($this->year, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; + } + /** + * Compares the formatted values of the two dates. + * + * @param string $format The date formats to compare. + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @throws \InvalidArgumentException + * + * @return bool + */ + public function isSameAs($format, $date = null) + { + $date = $date ?: static::now($this->tz); + static::expectDateTime($date, 'null'); + return $this->format($format) === $date->format($format); + } + /** + * Determines if the instance is in the current year. + * + * @return bool + */ + public function isCurrentYear() + { + return $this->isSameYear(); + } + /** + * Checks if the passed in date is in the same year as the instance year. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameYear($date = null) + { + return $this->isSameAs('Y', $date); + } + /** + * Determines if the instance is in the current month. + * + * @return bool + */ + public function isCurrentQuarter() + { + return $this->isSameQuarter(); + } + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameQuarter($date = null, $ofSameYear = null) + { + $date = $date ? static::instance($date) : static::now($this->tz); + static::expectDateTime($date, 'null'); + $ofSameYear = \is_null($ofSameYear) ? static::shouldCompareYearWithMonth() : $ofSameYear; + return $this->quarter === $date->quarter && (!$ofSameYear || $this->isSameYear($date)); + } + /** + * Determines if the instance is in the current month. + * + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isCurrentMonth($ofSameYear = null) + { + return $this->isSameMonth(null, $ofSameYear); + } + /** + * Checks if the passed in date is in the same month as the instance´s month. + * + * Note that this defaults to only comparing the month while ignoring the year. + * To test if it is the same exact month of the same year, pass in true as the second parameter. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameMonth($date = null, $ofSameYear = null) + { + $ofSameYear = \is_null($ofSameYear) ? static::shouldCompareYearWithMonth() : $ofSameYear; + return $this->isSameAs($ofSameYear ? 'Y-m' : 'm', $date); + } + /** + * Determines if the instance is in the current day. + * + * @return bool + */ + public function isCurrentDay() + { + return $this->isSameDay(); + } + /** + * Checks if the passed in date is the same exact day as the instance´s day. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameDay($date = null) + { + return $this->isSameAs('Y-m-d', $date); + } + /** + * Determines if the instance is in the current hour. + * + * @return bool + */ + public function isCurrentHour() + { + return $this->isSameHour(); + } + /** + * Checks if the passed in date is the same exact hour as the instance´s hour. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameHour($date = null) + { + return $this->isSameAs('Y-m-d H', $date); + } + /** + * Determines if the instance is in the current minute. + * + * @return bool + */ + public function isCurrentMinute() + { + return $this->isSameMinute(); + } + /** + * Checks if the passed in date is the same exact minute as the instance´s minute. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameMinute($date = null) + { + return $this->isSameAs('Y-m-d H:i', $date); + } + /** + * Determines if the instance is in the current second. + * + * @return bool + */ + public function isCurrentSecond() + { + return $this->isSameSecond(); + } + /** + * Checks if the passed in date is the same exact second as the instance´s second. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameSecond($date = null) + { + return $this->isSameAs('Y-m-d H:i:s', $date); + } + /** + * Checks if this day is a specific day of the week. + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek) + { + return $this->dayOfWeek === $dayOfWeek; + } + /** + * Checks if this day is a Sunday. + * + * @return bool + */ + public function isSunday() + { + return $this->dayOfWeek === static::SUNDAY; + } + /** + * Checks if this day is a Monday. + * + * @return bool + */ + public function isMonday() + { + return $this->dayOfWeek === static::MONDAY; + } + /** + * Checks if this day is a Tuesday. + * + * @return bool + */ + public function isTuesday() + { + return $this->dayOfWeek === static::TUESDAY; + } + /** + * Checks if this day is a Wednesday. + * + * @return bool + */ + public function isWednesday() + { + return $this->dayOfWeek === static::WEDNESDAY; + } + /** + * Checks if this day is a Thursday. + * + * @return bool + */ + public function isThursday() + { + return $this->dayOfWeek === static::THURSDAY; + } + /** + * Checks if this day is a Friday. + * + * @return bool + */ + public function isFriday() + { + return $this->dayOfWeek === static::FRIDAY; + } + /** + * Checks if this day is a Saturday. + * + * @return bool + */ + public function isSaturday() + { + return $this->dayOfWeek === static::SATURDAY; + } + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isBirthday($date = null) + { + return $this->isSameAs('md', $date); + } + /** + * Check if today is the last day of the Month + * + * @return bool + */ + public function isLastOfMonth() + { + return $this->day === $this->daysInMonth; + } + /** + * Check if the instance is start of day / midnight. + * + * @param bool $checkMicroseconds check time at microseconds precision + * /!\ Warning, this is not reliable with PHP < 7.1.4 + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = \false) + { + return $checkMicroseconds ? $this->format('H:i:s.u') === '00:00:00.000000' : $this->format('H:i:s') === '00:00:00'; + } + /** + * Check if the instance is end of day. + * + * @param bool $checkMicroseconds check time at microseconds precision + * /!\ Warning, this is not reliable with PHP < 7.1.4 + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = \false) + { + return $checkMicroseconds ? $this->format('H:i:s.u') === '23:59:59.999999' : $this->format('H:i:s') === '23:59:59'; + } + /** + * Check if the instance is start of day / midnight. + * + * @return bool + */ + public function isMidnight() + { + return $this->isStartOfDay(); + } + /** + * Check if the instance is midday. + * + * @return bool + */ + public function isMidday() + { + return $this->format('G:i:s') === static::$midDayAt . ':00:00'; + } + /** + * Checks if the (date)time string is in a given format. + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format) + { + try { + // Try to create a DateTime object. Throws an InvalidArgumentException if the provided time string + // doesn't match the format in any way. + static::createFromFormat($format, $date); + // createFromFormat() is known to handle edge cases silently. + // E.g. "1975-5-1" (Y-n-j) will still be parsed correctly when "Y-m-d" is supplied as the format. + // To ensure we're really testing against our desired format, perform an additional regex validation. + $regex = \strtr(\preg_quote($format, '/'), static::$regexFormats); + return (bool) \preg_match('/^' . $regex . '$/', $date); + } catch (\InvalidArgumentException $e) { + } + return \false; + } + /////////////////////////////////////////////////////////////////// + /////////////////// ADDITIONS AND SUBTRACTIONS //////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Add centuries to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addCenturies($value) + { + return $this->addYears(static::YEARS_PER_CENTURY * $value); + } + /** + * Add a century to the instance + * + * @param int $value + * + * @return static + */ + public function addCentury($value = 1) + { + return $this->addCenturies($value); + } + /** + * Remove centuries from the instance + * + * @param int $value + * + * @return static + */ + public function subCenturies($value) + { + return $this->addCenturies(-1 * $value); + } + /** + * Remove a century from the instance + * + * @param int $value + * + * @return static + */ + public function subCentury($value = 1) + { + return $this->subCenturies($value); + } + /** + * Add years to the instance. Positive $value travel forward while + * negative $value travel into the past. + * + * @param int $value + * + * @return static + */ + public function addYears($value) + { + if ($this->shouldOverflowYears()) { + return $this->addYearsWithOverflow($value); + } + return $this->addYearsNoOverflow($value); + } + /** + * Add a year to the instance + * + * @param int $value + * + * @return static + */ + public function addYear($value = 1) + { + return $this->addYears($value); + } + /** + * Add years to the instance with no overflow of months + * Positive $value travel forward while + * negative $value travel into the past. + * + * @param int $value + * + * @return static + */ + public function addYearsNoOverflow($value) + { + return $this->addMonthsNoOverflow($value * static::MONTHS_PER_YEAR); + } + /** + * Add year with overflow months set to false + * + * @param int $value + * + * @return static + */ + public function addYearNoOverflow($value = 1) + { + return $this->addYearsNoOverflow($value); + } + /** + * Add years to the instance. + * Positive $value travel forward while + * negative $value travel into the past. + * + * @param int $value + * + * @return static + */ + public function addYearsWithOverflow($value) + { + return $this->modify((int) $value . ' year'); + } + /** + * Add year with overflow. + * + * @param int $value + * + * @return static + */ + public function addYearWithOverflow($value = 1) + { + return $this->addYearsWithOverflow($value); + } + /** + * Remove years from the instance. + * + * @param int $value + * + * @return static + */ + public function subYears($value) + { + return $this->addYears(-1 * $value); + } + /** + * Remove a year from the instance + * + * @param int $value + * + * @return static + */ + public function subYear($value = 1) + { + return $this->subYears($value); + } + /** + * Remove years from the instance with no month overflow. + * + * @param int $value + * + * @return static + */ + public function subYearsNoOverflow($value) + { + return $this->subMonthsNoOverflow($value * static::MONTHS_PER_YEAR); + } + /** + * Remove year from the instance with no month overflow + * + * @param int $value + * + * @return static + */ + public function subYearNoOverflow($value = 1) + { + return $this->subYearsNoOverflow($value); + } + /** + * Remove years from the instance. + * + * @param int $value + * + * @return static + */ + public function subYearsWithOverflow($value) + { + return $this->subMonthsWithOverflow($value * static::MONTHS_PER_YEAR); + } + /** + * Remove year from the instance. + * + * @param int $value + * + * @return static + */ + public function subYearWithOverflow($value = 1) + { + return $this->subYearsWithOverflow($value); + } + /** + * Add quarters to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addQuarters($value) + { + return $this->addMonths(static::MONTHS_PER_QUARTER * $value); + } + /** + * Add a quarter to the instance + * + * @param int $value + * + * @return static + */ + public function addQuarter($value = 1) + { + return $this->addQuarters($value); + } + /** + * Remove quarters from the instance + * + * @param int $value + * + * @return static + */ + public function subQuarters($value) + { + return $this->addQuarters(-1 * $value); + } + /** + * Remove a quarter from the instance + * + * @param int $value + * + * @return static + */ + public function subQuarter($value = 1) + { + return $this->subQuarters($value); + } + /** + * Add months to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMonths($value) + { + if (static::shouldOverflowMonths()) { + return $this->addMonthsWithOverflow($value); + } + return $this->addMonthsNoOverflow($value); + } + /** + * Add a month to the instance + * + * @param int $value + * + * @return static + */ + public function addMonth($value = 1) + { + return $this->addMonths($value); + } + /** + * Remove months from the instance + * + * @param int $value + * + * @return static + */ + public function subMonths($value) + { + return $this->addMonths(-1 * $value); + } + /** + * Remove a month from the instance + * + * @param int $value + * + * @return static + */ + public function subMonth($value = 1) + { + return $this->subMonths($value); + } + /** + * Add months to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMonthsWithOverflow($value) + { + return $this->modify((int) $value . ' month'); + } + /** + * Add a month to the instance + * + * @param int $value + * + * @return static + */ + public function addMonthWithOverflow($value = 1) + { + return $this->addMonthsWithOverflow($value); + } + /** + * Remove months from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthsWithOverflow($value) + { + return $this->addMonthsWithOverflow(-1 * $value); + } + /** + * Remove a month from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthWithOverflow($value = 1) + { + return $this->subMonthsWithOverflow($value); + } + /** + * Add months without overflowing to the instance. Positive $value + * travels forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMonthsNoOverflow($value) + { + $day = $this->day; + $this->modify((int) $value . ' month'); + if ($day !== $this->day) { + $this->modify('last day of previous month'); + } + return $this; + } + /** + * Add a month with no overflow to the instance + * + * @param int $value + * + * @return static + */ + public function addMonthNoOverflow($value = 1) + { + return $this->addMonthsNoOverflow($value); + } + /** + * Remove months with no overflow from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthsNoOverflow($value) + { + return $this->addMonthsNoOverflow(-1 * $value); + } + /** + * Remove a month with no overflow from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthNoOverflow($value = 1) + { + return $this->subMonthsNoOverflow($value); + } + /** + * Add days to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addDays($value) + { + return $this->modify((int) $value . ' day'); + } + /** + * Add a day to the instance + * + * @param int $value + * + * @return static + */ + public function addDay($value = 1) + { + return $this->addDays($value); + } + /** + * Remove days from the instance + * + * @param int $value + * + * @return static + */ + public function subDays($value) + { + return $this->addDays(-1 * $value); + } + /** + * Remove a day from the instance + * + * @param int $value + * + * @return static + */ + public function subDay($value = 1) + { + return $this->subDays($value); + } + /** + * Add weekdays to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addWeekdays($value) + { + // Fix for weekday bug https://bugs.php.net/bug.php?id=54909 + $t = $this->toTimeString(); + $this->modify((int) $value . ' weekday'); + return $this->setTimeFromTimeString($t); + } + /** + * Add a weekday to the instance + * + * @param int $value + * + * @return static + */ + public function addWeekday($value = 1) + { + return $this->addWeekdays($value); + } + /** + * Remove weekdays from the instance + * + * @param int $value + * + * @return static + */ + public function subWeekdays($value) + { + return $this->addWeekdays(-1 * $value); + } + /** + * Remove a weekday from the instance + * + * @param int $value + * + * @return static + */ + public function subWeekday($value = 1) + { + return $this->subWeekdays($value); + } + /** + * Add weeks to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addWeeks($value) + { + return $this->modify((int) $value . ' week'); + } + /** + * Add a week to the instance + * + * @param int $value + * + * @return static + */ + public function addWeek($value = 1) + { + return $this->addWeeks($value); + } + /** + * Remove weeks to the instance + * + * @param int $value + * + * @return static + */ + public function subWeeks($value) + { + return $this->addWeeks(-1 * $value); + } + /** + * Remove a week from the instance + * + * @param int $value + * + * @return static + */ + public function subWeek($value = 1) + { + return $this->subWeeks($value); + } + /** + * Add hours to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addHours($value) + { + return $this->modify((int) $value . ' hour'); + } + /** + * Add hours to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addRealHours($value) + { + return $this->addRealMinutes($value * static::MINUTES_PER_HOUR); + } + /** + * Add an hour to the instance. + * + * @param int $value + * + * @return static + */ + public function addHour($value = 1) + { + return $this->addHours($value); + } + /** + * Add an hour to the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function addRealHour($value = 1) + { + return $this->addRealHours($value); + } + /** + * Remove hours from the instance. + * + * @param int $value + * + * @return static + */ + public function subHours($value) + { + return $this->addHours(-1 * $value); + } + /** + * Remove hours from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealHours($value) + { + return $this->addRealHours(-1 * $value); + } + /** + * Remove an hour from the instance. + * + * @param int $value + * + * @return static + */ + public function subHour($value = 1) + { + return $this->subHours($value); + } + /** + * Remove an hour from the instance. + * + * @param int $value + * + * @return static + */ + public function subRealHour($value = 1) + { + return $this->subRealHours($value); + } + /** + * Add minutes to the instance using timestamp. Positive $value + * travels forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMinutes($value) + { + return $this->modify((int) $value . ' minute'); + } + /** + * Add minutes to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addRealMinutes($value) + { + return $this->addRealSeconds($value * static::SECONDS_PER_MINUTE); + } + /** + * Add a minute to the instance. + * + * @param int $value + * + * @return static + */ + public function addMinute($value = 1) + { + return $this->addMinutes($value); + } + /** + * Add a minute to the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function addRealMinute($value = 1) + { + return $this->addRealMinutes($value); + } + /** + * Remove a minute from the instance. + * + * @param int $value + * + * @return static + */ + public function subMinute($value = 1) + { + return $this->subMinutes($value); + } + /** + * Remove a minute from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealMinute($value = 1) + { + return $this->addRealMinutes(-1 * $value); + } + /** + * Remove minutes from the instance. + * + * @param int $value + * + * @return static + */ + public function subMinutes($value) + { + return $this->addMinutes(-1 * $value); + } + /** + * Remove a minute from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealMinutes($value = 1) + { + return $this->subRealMinute($value); + } + /** + * Add seconds to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addSeconds($value) + { + return $this->modify((int) $value . ' second'); + } + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addRealSeconds($value) + { + return $this->setTimestamp($this->getTimestamp() + $value); + } + /** + * Add a second to the instance. + * + * @param int $value + * + * @return static + */ + public function addSecond($value = 1) + { + return $this->addSeconds($value); + } + /** + * Add a second to the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function addRealSecond($value = 1) + { + return $this->addRealSeconds($value); + } + /** + * Remove seconds from the instance. + * + * @param int $value + * + * @return static + */ + public function subSeconds($value) + { + return $this->addSeconds(-1 * $value); + } + /** + * Remove seconds from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealSeconds($value) + { + return $this->addRealSeconds(-1 * $value); + } + /** + * Remove a second from the instance + * + * @param int $value + * + * @return static + */ + public function subSecond($value = 1) + { + return $this->subSeconds($value); + } + /** + * Remove a second from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealSecond($value = 1) + { + return $this->subRealSeconds($value); + } + /////////////////////////////////////////////////////////////////// + /////////////////////////// DIFFERENCES /////////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * @param DateInterval $diff + * @param bool $absolute + * @param bool $trimMicroseconds + * + * @return CarbonInterval + */ + protected static function fixDiffInterval(\DateInterval $diff, $absolute, $trimMicroseconds) + { + $diff = \ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonInterval::instance($diff, $trimMicroseconds); + // @codeCoverageIgnoreStart + if (\version_compare(\PHP_VERSION, '7.1.0-dev', '<')) { + return $diff; + } + // Work-around for https://bugs.php.net/bug.php?id=77145 + if ($diff->f > 0 && $diff->y === -1 && $diff->m === 11 && $diff->d >= 27 && $diff->h === 23 && $diff->i === 59 && $diff->s === 59) { + $diff->y = 0; + $diff->m = 0; + $diff->d = 0; + $diff->h = 0; + $diff->i = 0; + $diff->s = 0; + $diff->f = (1000000 - \round($diff->f * 1000000)) / 1000000; + $diff->invert(); + } elseif ($diff->f < 0) { + if ($diff->s !== 0 || $diff->i !== 0 || $diff->h !== 0 || $diff->d !== 0 || $diff->m !== 0 || $diff->y !== 0) { + $diff->f = (\round($diff->f * 1000000) + 1000000) / 1000000; + $diff->s--; + if ($diff->s < 0) { + $diff->s += 60; + $diff->i--; + if ($diff->i < 0) { + $diff->i += 60; + $diff->h--; + if ($diff->h < 0) { + $diff->h += 24; + $diff->d--; + if ($diff->d < 0) { + $diff->d += 30; + $diff->m--; + if ($diff->m < 0) { + $diff->m += 12; + $diff->y--; + } + } + } + } + } + } else { + $diff->f *= -1; + $diff->invert(); + } + } + // @codeCoverageIgnoreEnd + if ($absolute && $diff->invert) { + $diff->invert(); + } + return $diff; + } + /** + * Get the difference as a CarbonInterval instance. + * + * Pass false as second argument to get a microseconds-precise interval. Else + * microseconds in the original interval will not be kept. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * @param bool $trimMicroseconds (true by default) + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = \true, $trimMicroseconds = \true) + { + $from = $this; + $to = $this->resolveCarbon($date); + if ($trimMicroseconds) { + $from = $from->copy()->startOfSecond(); + $to = $to->copy()->startOfSecond(); + } + return static::fixDiffInterval($from->diff($to, $absolute), $absolute, $trimMicroseconds); + } + /** + * Get the difference in years + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInYears($date = null, $absolute = \true) + { + return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%y'); + } + /** + * Get the difference in months + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMonths($date = null, $absolute = \true) + { + $date = $this->resolveCarbon($date); + return $this->diffInYears($date, $absolute) * static::MONTHS_PER_YEAR + (int) $this->diff($date, $absolute)->format('%r%m'); + } + /** + * Get the difference in weeks + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeeks($date = null, $absolute = \true) + { + return (int) ($this->diffInDays($date, $absolute) / static::DAYS_PER_WEEK); + } + /** + * Get the difference in days + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = \true) + { + return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%a'); + } + /** + * Get the difference in days using a filter closure + * + * @param Closure $callback + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDaysFiltered(\Closure $callback, $date = null, $absolute = \true) + { + return $this->diffFiltered(\ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonInterval::day(), $callback, $date, $absolute); + } + /** + * Get the difference in hours using a filter closure + * + * @param Closure $callback + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHoursFiltered(\Closure $callback, $date = null, $absolute = \true) + { + return $this->diffFiltered(\ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonInterval::hour(), $callback, $date, $absolute); + } + /** + * Get the difference by the given interval using a filter closure + * + * @param CarbonInterval $ci An interval to traverse by + * @param Closure $callback + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffFiltered(\ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonInterval $ci, \Closure $callback, $date = null, $absolute = \true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $inverse = \false; + if ($end < $start) { + $start = $end; + $end = $this; + $inverse = \true; + } + $period = new \DatePeriod($start, $ci, $end); + $values = \array_filter(\iterator_to_array($period), function ($date) use($callback) { + return \call_user_func($callback, \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::instance($date)); + }); + $diff = \count($values); + return $inverse && !$absolute ? -$diff : $diff; + } + /** + * Get the difference in weekdays + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekdays($date = null, $absolute = \true) + { + return $this->diffInDaysFiltered(function (\ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon $date) { + return $date->isWeekday(); + }, $date, $absolute); + } + /** + * Get the difference in weekend days using a filter + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekendDays($date = null, $absolute = \true) + { + return $this->diffInDaysFiltered(function (\ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon $date) { + return $date->isWeekend(); + }, $date, $absolute); + } + /** + * Get the difference in hours. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHours($date = null, $absolute = \true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + /** + * Get the difference in hours using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealHours($date = null, $absolute = \true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + /** + * Get the difference in minutes. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMinutes($date = null, $absolute = \true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + /** + * Get the difference in minutes using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMinutes($date = null, $absolute = \true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + /** + * Get the difference in seconds. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInSeconds($date = null, $absolute = \true) + { + $diff = $this->diff($this->resolveCarbon($date)); + if (!$diff->days && \version_compare(\PHP_VERSION, '5.4.0-dev', '>=')) { + $diff = static::fixDiffInterval($diff, $absolute, \false); + } + $value = $diff->days * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE + $diff->h * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE + $diff->i * static::SECONDS_PER_MINUTE + $diff->s; + return $absolute || !$diff->invert ? $value : -$value; + } + /** + * Get the difference in seconds using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = \true) + { + $date = $this->resolveCarbon($date); + $value = $date->getTimestamp() - $this->getTimestamp(); + return $absolute ? \abs($value) : $value; + } + /** + * Get the difference in milliseconds. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMilliseconds($date = null, $absolute = \true) + { + return (int) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); + } + /** + * Get the difference in milliseconds using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMilliseconds($date = null, $absolute = \true) + { + return (int) ($this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); + } + /** + * Get the difference in microseconds. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMicroseconds($date = null, $absolute = \true) + { + $diff = $this->diff($this->resolveCarbon($date)); + $micro = isset($diff->f) ? $diff->f : 0; + $value = (int) \round(((($diff->days * static::HOURS_PER_DAY + $diff->h) * static::MINUTES_PER_HOUR + $diff->i) * static::SECONDS_PER_MINUTE + ($micro + $diff->s)) * static::MICROSECONDS_PER_SECOND); + return $absolute || !$diff->invert ? $value : -$value; + } + /** + * Get the difference in microseconds using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMicroseconds($date = null, $absolute = \true) + { + /** @var Carbon $date */ + $date = $this->resolveCarbon($date); + $value = ($date->timestamp - $this->timestamp) * static::MICROSECONDS_PER_SECOND + $date->micro - $this->micro; + return $absolute ? \abs($value) : $value; + } + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight() + { + return $this->diffInSeconds($this->copy()->startOfDay()); + } + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay() + { + return $this->diffInSeconds($this->copy()->endOfDay()); + } + /** + * Get the difference in a human readable format in the current locale. + * + * When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the past to another value: + * 1 hour before + * 5 months before + * + * When comparing a value in the future to another value: + * 1 hour after + * 5 months after + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function diffForHumans($other = null, $absolute = \false, $short = \false, $parts = 1) + { + $isNow = $other === null; + $relativeToNow = $isNow; + if ($absolute === static::DIFF_RELATIVE_TO_NOW) { + $absolute = \false; + $relativeToNow = \true; + } elseif ($absolute === static::DIFF_RELATIVE_TO_OTHER) { + $absolute = \false; + $relativeToNow = \false; + } + $interval = array(); + $parts = \min(6, \max(1, (int) $parts)); + $count = 1; + $unit = $short ? 's' : 'second'; + if ($isNow) { + $other = $this->nowWithSameTz(); + } elseif (!$other instanceof \DateTime && !$other instanceof \DateTimeInterface) { + $other = static::parse($other); + } + $diffInterval = $this->diff($other); + $diffIntervalArray = array(array('value' => $diffInterval->y, 'unit' => 'year', 'unitShort' => 'y'), array('value' => $diffInterval->m, 'unit' => 'month', 'unitShort' => 'm'), array('value' => $diffInterval->d, 'unit' => 'day', 'unitShort' => 'd'), array('value' => $diffInterval->h, 'unit' => 'hour', 'unitShort' => 'h'), array('value' => $diffInterval->i, 'unit' => 'minute', 'unitShort' => 'min'), array('value' => $diffInterval->s, 'unit' => 'second', 'unitShort' => 's')); + foreach ($diffIntervalArray as $diffIntervalData) { + if ($diffIntervalData['value'] > 0) { + $unit = $short ? $diffIntervalData['unitShort'] : $diffIntervalData['unit']; + $count = $diffIntervalData['value']; + if ($diffIntervalData['unit'] === 'day' && $count >= static::DAYS_PER_WEEK) { + $unit = $short ? 'w' : 'week'; + $count = (int) ($count / static::DAYS_PER_WEEK); + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + // get the count days excluding weeks (might be zero) + $numOfDaysCount = (int) ($diffIntervalData['value'] - $count * static::DAYS_PER_WEEK); + if ($numOfDaysCount > 0 && \count($interval) < $parts) { + $unit = $short ? 'd' : 'day'; + $count = $numOfDaysCount; + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + } + } else { + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + } + } + // break the loop after we get the required number of parts in array + if (\count($interval) >= $parts) { + break; + } + } + if (\count($interval) === 0) { + if ($isNow && static::getHumanDiffOptions() & self::JUST_NOW) { + $key = 'diff_now'; + $translation = static::translator()->trans($key); + if ($translation !== $key) { + return $translation; + } + } + $count = static::getHumanDiffOptions() & self::NO_ZERO_DIFF ? 1 : 0; + $unit = $short ? 's' : 'second'; + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + } + // join the interval parts by a space + $time = \implode(' ', $interval); + unset($diffIntervalArray, $interval); + if ($absolute) { + return $time; + } + $isFuture = $diffInterval->invert === 1; + $transId = $relativeToNow ? $isFuture ? 'from_now' : 'ago' : ($isFuture ? 'after' : 'before'); + if ($parts === 1) { + if ($isNow && $unit === 'day') { + if ($count === 1 && static::getHumanDiffOptions() & self::ONE_DAY_WORDS) { + $key = $isFuture ? 'diff_tomorrow' : 'diff_yesterday'; + $translation = static::translator()->trans($key); + if ($translation !== $key) { + return $translation; + } + } + if ($count === 2 && static::getHumanDiffOptions() & self::TWO_DAY_WORDS) { + $key = $isFuture ? 'diff_after_tomorrow' : 'diff_before_yesterday'; + $translation = static::translator()->trans($key); + if ($translation !== $key) { + return $translation; + } + } + } + // Some languages have special pluralization for past and future tense. + $key = $unit . '_' . $transId; + if ($key !== static::translator()->transChoice($key, $count)) { + $time = static::translator()->transChoice($key, $count, array(':count' => $count)); + } + } + return static::translator()->trans($transId, array(':time' => $time)); + } + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale. + * + * When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the past to another value: + * 1 hour before + * 5 months before + * + * When comparing a value in the future to another value: + * 1 hour after + * 5 months after + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function from($other = null, $absolute = \false, $short = \false, $parts = 1) + { + if (!$other && !$absolute) { + $absolute = static::DIFF_RELATIVE_TO_NOW; + } + return $this->diffForHumans($other, $absolute, $short, $parts); + } + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale. + * + * When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the past to another value: + * 1 hour before + * 5 months before + * + * When comparing a value in the future to another value: + * 1 hour after + * 5 months after + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function since($other = null, $absolute = \false, $short = \false, $parts = 1) + { + return $this->diffForHumans($other, $absolute, $short, $parts); + } + /** + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * When comparing a value in the past to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the future to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the past to another value: + * 1 hour after + * 5 months after + * + * When comparing a value in the future to another value: + * 1 hour before + * 5 months before + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function to($other = null, $absolute = \false, $short = \false, $parts = 1) + { + if (!$other && !$absolute) { + $absolute = static::DIFF_RELATIVE_TO_NOW; + } + return $this->resolveCarbon($other)->diffForHumans($this, $absolute, $short, $parts); + } + /** + * @alias to + * + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function until($other = null, $absolute = \false, $short = \false, $parts = 1) + { + return $this->to($other, $absolute, $short, $parts); + } + /** + * Get the difference in a human readable format in the current locale from current + * instance to now. + * + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function fromNow($absolute = null, $short = \false, $parts = 1) + { + $other = null; + if ($absolute instanceof \DateTimeInterface) { + list($other, $absolute, $short, $parts) = \array_pad(\func_get_args(), 5, null); + } + return $this->from($other, $absolute, $short, $parts); + } + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function toNow($absolute = null, $short = \false, $parts = 1) + { + return $this->to(null, $absolute, $short, $parts); + } + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function ago($absolute = null, $short = \false, $parts = 1) + { + $other = null; + if ($absolute instanceof \DateTimeInterface) { + list($other, $absolute, $short, $parts) = \array_pad(\func_get_args(), 5, null); + } + return $this->from($other, $absolute, $short, $parts); + } + /////////////////////////////////////////////////////////////////// + //////////////////////////// MODIFIERS //////////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Resets the time to 00:00:00 start of day + * + * @return static + */ + public function startOfDay() + { + return $this->modify('00:00:00.000000'); + } + /** + * Resets the time to 23:59:59 end of day + * + * @return static + */ + public function endOfDay() + { + return $this->modify('23:59:59.999999'); + } + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @return static + */ + public function startOfMonth() + { + return $this->setDate($this->year, $this->month, 1)->startOfDay(); + } + /** + * Resets the date to end of the month and time to 23:59:59 + * + * @return static + */ + public function endOfMonth() + { + return $this->setDate($this->year, $this->month, $this->daysInMonth)->endOfDay(); + } + /** + * Resets the date to the first day of the quarter and the time to 00:00:00 + * + * @return static + */ + public function startOfQuarter() + { + $month = ($this->quarter - 1) * static::MONTHS_PER_QUARTER + 1; + return $this->setDate($this->year, $month, 1)->startOfDay(); + } + /** + * Resets the date to end of the quarter and time to 23:59:59 + * + * @return static + */ + public function endOfQuarter() + { + return $this->startOfQuarter()->addMonths(static::MONTHS_PER_QUARTER - 1)->endOfMonth(); + } + /** + * Resets the date to the first day of the year and the time to 00:00:00 + * + * @return static + */ + public function startOfYear() + { + return $this->setDate($this->year, 1, 1)->startOfDay(); + } + /** + * Resets the date to end of the year and time to 23:59:59 + * + * @return static + */ + public function endOfYear() + { + return $this->setDate($this->year, 12, 31)->endOfDay(); + } + /** + * Resets the date to the first day of the decade and the time to 00:00:00 + * + * @return static + */ + public function startOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE; + return $this->setDate($year, 1, 1)->startOfDay(); + } + /** + * Resets the date to end of the decade and time to 23:59:59 + * + * @return static + */ + public function endOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE + static::YEARS_PER_DECADE - 1; + return $this->setDate($year, 12, 31)->endOfDay(); + } + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @return static + */ + public function startOfCentury() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_CENTURY; + return $this->setDate($year, 1, 1)->startOfDay(); + } + /** + * Resets the date to end of the century and time to 23:59:59 + * + * @return static + */ + public function endOfCentury() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_CENTURY + static::YEARS_PER_CENTURY; + return $this->setDate($year, 12, 31)->endOfDay(); + } + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @return static + */ + public function startOfMillennium() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_MILLENNIUM; + return $this->setDate($year, 1, 1)->startOfDay(); + } + /** + * Resets the date to end of the century and time to 23:59:59 + * + * @return static + */ + public function endOfMillennium() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_MILLENNIUM + static::YEARS_PER_MILLENNIUM; + return $this->setDate($year, 12, 31)->endOfDay(); + } + /** + * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 + * + * @return static + */ + public function startOfWeek() + { + while ($this->dayOfWeek !== static::$weekStartsAt) { + $this->subDay(); + } + return $this->startOfDay(); + } + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59 + * + * @return static + */ + public function endOfWeek() + { + while ($this->dayOfWeek !== static::$weekEndsAt) { + $this->addDay(); + } + return $this->endOfDay(); + } + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @return static + */ + public function startOfHour() + { + return $this->setTime($this->hour, 0, 0); + } + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @return static + */ + public function endOfHour() + { + return $this->modify("{$this->hour}:59:59.999999"); + } + /** + * Modify to start of current minute, seconds become 0 + * + * @return static + */ + public function startOfMinute() + { + return $this->setTime($this->hour, $this->minute, 0); + } + /** + * Modify to end of current minute, seconds become 59 + * + * @return static + */ + public function endOfMinute() + { + return $this->modify("{$this->hour}:{$this->minute}:59.999999"); + } + /** + * Modify to start of current minute, seconds become 0 + * + * @return static + */ + public function startOfSecond() + { + return $this->modify("{$this->hour}:{$this->minute}:{$this->second}.0"); + } + /** + * Modify to end of current minute, seconds become 59 + * + * @return static + */ + public function endOfSecond() + { + return $this->modify("{$this->hour}:{$this->minute}:{$this->second}.999999"); + } + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay() + { + return $this->setTime(self::$midDayAt, 0, 0); + } + /** + * Modify to the next occurrence of a given day of the week. + * If no dayOfWeek is provided, modify to the next occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function next($dayOfWeek = null) + { + if ($dayOfWeek === null) { + $dayOfWeek = $this->dayOfWeek; + } + return $this->startOfDay()->modify('next ' . static::$days[$dayOfWeek]); + } + /** + * Go forward or backward to the next week- or weekend-day. + * + * @param bool $weekday + * @param bool $forward + * + * @return $this + */ + private function nextOrPreviousDay($weekday = \true, $forward = \true) + { + $step = $forward ? 1 : -1; + do { + $this->addDay($step); + } while ($weekday ? $this->isWeekend() : $this->isWeekday()); + return $this; + } + /** + * Go forward to the next weekday. + * + * @return $this + */ + public function nextWeekday() + { + return $this->nextOrPreviousDay(); + } + /** + * Go backward to the previous weekday. + * + * @return $this + */ + public function previousWeekday() + { + return $this->nextOrPreviousDay(\true, \false); + } + /** + * Go forward to the next weekend day. + * + * @return $this + */ + public function nextWeekendDay() + { + return $this->nextOrPreviousDay(\false); + } + /** + * Go backward to the previous weekend day. + * + * @return $this + */ + public function previousWeekendDay() + { + return $this->nextOrPreviousDay(\false, \false); + } + /** + * Modify to the previous occurrence of a given day of the week. + * If no dayOfWeek is provided, modify to the previous occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function previous($dayOfWeek = null) + { + if ($dayOfWeek === null) { + $dayOfWeek = $this->dayOfWeek; + } + return $this->startOfDay()->modify('last ' . static::$days[$dayOfWeek]); + } + /** + * Modify to the first occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * first day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function firstOfMonth($dayOfWeek = null) + { + $this->startOfDay(); + if ($dayOfWeek === null) { + return $this->day(1); + } + return $this->modify('first ' . static::$days[$dayOfWeek] . ' of ' . $this->format('F') . ' ' . $this->year); + } + /** + * Modify to the last occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * last day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function lastOfMonth($dayOfWeek = null) + { + $this->startOfDay(); + if ($dayOfWeek === null) { + return $this->day($this->daysInMonth); + } + return $this->modify('last ' . static::$days[$dayOfWeek] . ' of ' . $this->format('F') . ' ' . $this->year); + } + /** + * Modify to the given occurrence of a given day of the week + * in the current month. If the calculated occurrence is outside the scope + * of the current month, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfMonth($nth, $dayOfWeek) + { + $date = $this->copy()->firstOfMonth(); + $check = $date->format('Y-m'); + $date->modify('+' . $nth . ' ' . static::$days[$dayOfWeek]); + return $date->format('Y-m') === $check ? $this->modify($date) : \false; + } + /** + * Modify to the first occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * first day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER - 2, 1)->firstOfMonth($dayOfWeek); + } + /** + * Modify to the last occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * last day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER, 1)->lastOfMonth($dayOfWeek); + } + /** + * Modify to the given occurrence of a given day of the week + * in the current quarter. If the calculated occurrence is outside the scope + * of the current quarter, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfQuarter($nth, $dayOfWeek) + { + $date = $this->copy()->day(1)->month($this->quarter * static::MONTHS_PER_QUARTER); + $lastMonth = $date->month; + $year = $date->year; + $date->firstOfQuarter()->modify('+' . $nth . ' ' . static::$days[$dayOfWeek]); + return $lastMonth < $date->month || $year !== $date->year ? \false : $this->modify($date); + } + /** + * Modify to the first occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * first day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfYear($dayOfWeek = null) + { + return $this->month(1)->firstOfMonth($dayOfWeek); + } + /** + * Modify to the last occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * last day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfYear($dayOfWeek = null) + { + return $this->month(static::MONTHS_PER_YEAR)->lastOfMonth($dayOfWeek); + } + /** + * Modify to the given occurrence of a given day of the week + * in the current year. If the calculated occurrence is outside the scope + * of the current year, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfYear($nth, $dayOfWeek) + { + $date = $this->copy()->firstOfYear()->modify('+' . $nth . ' ' . static::$days[$dayOfWeek]); + return $this->year === $date->year ? $this->modify($date) : \false; + } + /** + * Modify the current instance to the average of a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + public function average($date = null) + { + $date = $this->resolveCarbon($date); + $increment = $this->diffInRealSeconds($date, \false) / 2; + $intIncrement = \floor($increment); + $microIncrement = (int) (($date->micro - $this->micro) / 2 + 1000000 * ($increment - $intIncrement)); + $micro = (int) ($this->micro + $microIncrement); + while ($micro >= 1000000) { + $micro -= 1000000; + $intIncrement++; + } + $this->addSeconds($intIncrement); + if (\version_compare(\PHP_VERSION, '7.1.8-dev', '>=')) { + $this->setTime($this->hour, $this->minute, $this->second, $micro); + } + return $this; + } + /////////////////////////////////////////////////////////////////// + /////////////////////////// SERIALIZATION ///////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Return a serialized string of the instance. + * + * @return string + */ + public function serialize() + { + return \serialize($this); + } + /** + * Create an instance from a serialized string. + * + * @param string $value + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function fromSerialized($value) + { + $instance = @\unserialize($value); + if (!$instance instanceof static) { + throw new \InvalidArgumentException('Invalid serialized value.'); + } + return $instance; + } + /** + * The __set_state handler. + * + * @param array $array + * + * @return static + */ + public static function __set_state($array) + { + return static::instance(parent::__set_state($array)); + } + /** + * Prepare the object for JSON serialization. + * + * @return array|string + */ + public function jsonSerialize() + { + if (static::$serializer) { + return \call_user_func(static::$serializer, $this); + } + $carbon = $this; + return \call_user_func(function () use($carbon) { + return \get_object_vars($carbon); + }); + } + /** + * JSON serialize all Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback) + { + static::$serializer = $callback; + } + /////////////////////////////////////////////////////////////////// + /////////////////////////////// MACRO ///////////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Register a custom macro. + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$localMacros[$name] = $macro; + } + /** + * Remove all macros. + */ + public static function resetMacros() + { + static::$localMacros = array(); + } + /** + * Mix another object into the class. + * + * @param object $mixin + * + * @return void + */ + public static function mixin($mixin) + { + $reflection = new \ReflectionClass($mixin); + $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED); + foreach ($methods as $method) { + $method->setAccessible(\true); + static::macro($method->name, $method->invoke($mixin)); + } + } + /** + * Checks if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$localMacros[$name]); + } + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * + * @throws \BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + if (!static::hasMacro($method)) { + throw new \BadMethodCallException("Method {$method} does not exist."); + } + if (static::$localMacros[$method] instanceof \Closure && \method_exists('Closure', 'bind')) { + return \call_user_func_array(\Closure::bind(static::$localMacros[$method], null, \get_called_class()), $parameters); + } + return \call_user_func_array(static::$localMacros[$method], $parameters); + } + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * + * @throws \BadMethodCallException|\ReflectionException + * + * @return mixed + */ + public function __call($method, $parameters) + { + if (!static::hasMacro($method)) { + throw new \BadMethodCallException("Method {$method} does not exist."); + } + $macro = static::$localMacros[$method]; + $reflexion = new \ReflectionFunction($macro); + $reflectionParameters = $reflexion->getParameters(); + $expectedCount = \count($reflectionParameters); + $actualCount = \count($parameters); + if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') { + for ($i = $actualCount; $i < $expectedCount - 1; $i++) { + $parameters[] = $reflectionParameters[$i]->getDefaultValue(); + } + $parameters[] = $this; + } + if ($macro instanceof \Closure && \method_exists($macro, 'bindTo')) { + return \call_user_func_array($macro->bindTo($this, \get_class($this)), $parameters); + } + return \call_user_func_array($macro, $parameters); + } + /** + * Show truthy properties on var_dump(). + * + * @return array + */ + public function __debugInfo() + { + return \array_filter(\get_object_vars($this), function ($var) { + return $var; + }); + } + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return object + */ + public function cast($className) + { + if (!\method_exists($className, 'instance')) { + throw new \InvalidArgumentException("{$className} has not the instance() method needed to cast the date."); + } + return $className::instance($this); + } +} diff --git a/classes/Utilities/Misc/Carbon/CarbonInterval.php b/classes/Utilities/Misc/Carbon/CarbonInterval.php new file mode 100755 index 00000000..3bb12ad3 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/CarbonInterval.php @@ -0,0 +1,975 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Carbon; + +use Closure; +use DateInterval; +use InvalidArgumentException; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface; +/** + * A simple API extension for DateInterval. + * The implementation provides helpers to handle weeks but only days are saved. + * Weeks are calculated based on the total days of the current instance. + * + * @property int $years Total years of the current interval. + * @property int $months Total months of the current interval. + * @property int $weeks Total weeks of the current interval calculated from the days. + * @property int $dayz Total days of the current interval (weeks * 7 + days). + * @property int $hours Total hours of the current interval. + * @property int $minutes Total minutes of the current interval. + * @property int $seconds Total seconds of the current interval. + * @property-read int $dayzExcludeWeeks Total days remaining in the final week of the current instance (days % 7). + * @property-read int $daysExcludeWeeks alias of dayzExcludeWeeks + * @property-read float $totalYears Number of years equivalent to the interval. + * @property-read float $totalMonths Number of months equivalent to the interval. + * @property-read float $totalWeeks Number of weeks equivalent to the interval. + * @property-read float $totalDays Number of days equivalent to the interval. + * @property-read float $totalDayz Alias for totalDays. + * @property-read float $totalHours Number of hours equivalent to the interval. + * @property-read float $totalMinutes Number of minutes equivalent to the interval. + * @property-read float $totalSeconds Number of seconds equivalent to the interval. + * + * @method static CarbonInterval years($years = 1) Create instance specifying a number of years. + * @method static CarbonInterval year($years = 1) Alias for years() + * @method static CarbonInterval months($months = 1) Create instance specifying a number of months. + * @method static CarbonInterval month($months = 1) Alias for months() + * @method static CarbonInterval weeks($weeks = 1) Create instance specifying a number of weeks. + * @method static CarbonInterval week($weeks = 1) Alias for weeks() + * @method static CarbonInterval days($days = 1) Create instance specifying a number of days. + * @method static CarbonInterval dayz($days = 1) Alias for days() + * @method static CarbonInterval day($days = 1) Alias for days() + * @method static CarbonInterval hours($hours = 1) Create instance specifying a number of hours. + * @method static CarbonInterval hour($hours = 1) Alias for hours() + * @method static CarbonInterval minutes($minutes = 1) Create instance specifying a number of minutes. + * @method static CarbonInterval minute($minutes = 1) Alias for minutes() + * @method static CarbonInterval seconds($seconds = 1) Create instance specifying a number of seconds. + * @method static CarbonInterval second($seconds = 1) Alias for seconds() + * @method CarbonInterval years($years = 1) Set the years portion of the current interval. + * @method CarbonInterval year($years = 1) Alias for years(). + * @method CarbonInterval months($months = 1) Set the months portion of the current interval. + * @method CarbonInterval month($months = 1) Alias for months(). + * @method CarbonInterval weeks($weeks = 1) Set the weeks portion of the current interval. Will overwrite dayz value. + * @method CarbonInterval week($weeks = 1) Alias for weeks(). + * @method CarbonInterval days($days = 1) Set the days portion of the current interval. + * @method CarbonInterval dayz($days = 1) Alias for days(). + * @method CarbonInterval day($days = 1) Alias for days(). + * @method CarbonInterval hours($hours = 1) Set the hours portion of the current interval. + * @method CarbonInterval hour($hours = 1) Alias for hours(). + * @method CarbonInterval minutes($minutes = 1) Set the minutes portion of the current interval. + * @method CarbonInterval minute($minutes = 1) Alias for minutes(). + * @method CarbonInterval seconds($seconds = 1) Set the seconds portion of the current interval. + * @method CarbonInterval second($seconds = 1) Alias for seconds(). + */ +class CarbonInterval extends \DateInterval +{ + /** + * Interval spec period designators + */ + const PERIOD_PREFIX = 'P'; + const PERIOD_YEARS = 'Y'; + const PERIOD_MONTHS = 'M'; + const PERIOD_DAYS = 'D'; + const PERIOD_TIME_PREFIX = 'T'; + const PERIOD_HOURS = 'H'; + const PERIOD_MINUTES = 'M'; + const PERIOD_SECONDS = 'S'; + /** + * A translator to ... er ... translate stuff + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + /** + * @var array|null + */ + protected static $cascadeFactors; + /** + * @var array|null + */ + private static $flipCascadeFactors; + /** + * The registered macros. + * + * @var array + */ + protected static $macros = array(); + /** + * Before PHP 5.4.20/5.5.4 instead of FALSE days will be set to -99999 when the interval instance + * was created by DateTime::diff(). + */ + const PHP_DAYS_FALSE = -99999; + /** + * Mapping of units and factors for cascading. + * + * Should only be modified by changing the factors or referenced constants. + * + * @return array + */ + public static function getCascadeFactors() + { + return static::$cascadeFactors ?: array('minutes' => array(\ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::SECONDS_PER_MINUTE, 'seconds'), 'hours' => array(\ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::MINUTES_PER_HOUR, 'minutes'), 'dayz' => array(\ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::HOURS_PER_DAY, 'hours'), 'months' => array(\ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::DAYS_PER_WEEK * \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::WEEKS_PER_MONTH, 'dayz'), 'years' => array(\ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::MONTHS_PER_YEAR, 'months')); + } + private static function standardizeUnit($unit) + { + $unit = \rtrim($unit, 'sz') . 's'; + return $unit === 'days' ? 'dayz' : $unit; + } + private static function getFlipCascadeFactors() + { + if (!self::$flipCascadeFactors) { + self::$flipCascadeFactors = array(); + foreach (static::getCascadeFactors() as $to => $tuple) { + list($factor, $from) = $tuple; + self::$flipCascadeFactors[self::standardizeUnit($from)] = array(self::standardizeUnit($to), $factor); + } + } + return self::$flipCascadeFactors; + } + /** + * @param array $cascadeFactors + */ + public static function setCascadeFactors(array $cascadeFactors) + { + self::$flipCascadeFactors = null; + static::$cascadeFactors = $cascadeFactors; + } + /** + * Determine if the interval was created via DateTime:diff() or not. + * + * @param DateInterval $interval + * + * @return bool + */ + private static function wasCreatedFromDiff(\DateInterval $interval) + { + return $interval->days !== \false && $interval->days !== static::PHP_DAYS_FALSE; + } + /////////////////////////////////////////////////////////////////// + //////////////////////////// CONSTRUCTORS ///////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Create a new CarbonInterval instance. + * + * @param int $years + * @param int $months + * @param int $weeks + * @param int $days + * @param int $hours + * @param int $minutes + * @param int $seconds + */ + public function __construct($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null) + { + $spec = $years; + if (!\is_string($spec) || \floatval($years) || \preg_match('/^[0-9.]/', $years)) { + $spec = static::PERIOD_PREFIX; + $spec .= $years > 0 ? $years . static::PERIOD_YEARS : ''; + $spec .= $months > 0 ? $months . static::PERIOD_MONTHS : ''; + $specDays = 0; + $specDays += $weeks > 0 ? $weeks * static::getDaysPerWeek() : 0; + $specDays += $days > 0 ? $days : 0; + $spec .= $specDays > 0 ? $specDays . static::PERIOD_DAYS : ''; + if ($hours > 0 || $minutes > 0 || $seconds > 0) { + $spec .= static::PERIOD_TIME_PREFIX; + $spec .= $hours > 0 ? $hours . static::PERIOD_HOURS : ''; + $spec .= $minutes > 0 ? $minutes . static::PERIOD_MINUTES : ''; + $spec .= $seconds > 0 ? $seconds . static::PERIOD_SECONDS : ''; + } + if ($spec === static::PERIOD_PREFIX) { + // Allow the zero interval. + $spec .= '0' . static::PERIOD_YEARS; + } + } + parent::__construct($spec); + } + /** + * Returns the factor for a given source-to-target couple. + * + * @param string $source + * @param string $target + * + * @return int|null + */ + public static function getFactor($source, $target) + { + $source = self::standardizeUnit($source); + $target = self::standardizeUnit($target); + $factors = static::getFlipCascadeFactors(); + if (isset($factors[$source])) { + list($to, $factor) = $factors[$source]; + if ($to === $target) { + return $factor; + } + } + return null; + } + /** + * Returns current config for days per week. + * + * @return int + */ + public static function getDaysPerWeek() + { + return static::getFactor('dayz', 'weeks') ?: \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::DAYS_PER_WEEK; + } + /** + * Returns current config for hours per day. + * + * @return int + */ + public static function getHoursPerDay() + { + return static::getFactor('hours', 'dayz') ?: \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::HOURS_PER_DAY; + } + /** + * Returns current config for minutes per hour. + * + * @return int + */ + public static function getMinutesPerHours() + { + return static::getFactor('minutes', 'hours') ?: \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::MINUTES_PER_HOUR; + } + /** + * Returns current config for seconds per minute. + * + * @return int + */ + public static function getSecondsPerMinutes() + { + return static::getFactor('seconds', 'minutes') ?: \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::SECONDS_PER_MINUTE; + } + /** + * Create a new CarbonInterval instance from specific values. + * This is an alias for the constructor that allows better fluent + * syntax as it allows you to do CarbonInterval::create(1)->fn() rather than + * (new CarbonInterval(1))->fn(). + * + * @param int $years + * @param int $months + * @param int $weeks + * @param int $days + * @param int $hours + * @param int $minutes + * @param int $seconds + * + * @return static + */ + public static function create($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null) + { + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds); + } + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + $date = new static($this->spec()); + $date->invert = $this->invert; + return $date; + } + /** + * Provide static helpers to create instances. Allows CarbonInterval::years(3). + * + * Note: This is done using the magic method to allow static and instance methods to + * have the same names. + * + * @param string $name + * @param array $args + * + * @return static + */ + public static function __callStatic($name, $args) + { + $arg = \count($args) === 0 ? 1 : $args[0]; + switch ($name) { + case 'years': + case 'year': + return new static($arg); + case 'months': + case 'month': + return new static(null, $arg); + case 'weeks': + case 'week': + return new static(null, null, $arg); + case 'days': + case 'dayz': + case 'day': + return new static(null, null, null, $arg); + case 'hours': + case 'hour': + return new static(null, null, null, null, $arg); + case 'minutes': + case 'minute': + return new static(null, null, null, null, null, $arg); + case 'seconds': + case 'second': + return new static(null, null, null, null, null, null, $arg); + } + if (static::hasMacro($name)) { + return \call_user_func_array(array(new static(0), $name), $args); + } + } + /** + * Creates a CarbonInterval from string. + * + * Format: + * + * Suffix | Unit | Example | DateInterval expression + * -------|---------|---------|------------------------ + * y | years | 1y | P1Y + * mo | months | 3mo | P3M + * w | weeks | 2w | P2W + * d | days | 28d | P28D + * h | hours | 4h | PT4H + * m | minutes | 12m | PT12M + * s | seconds | 59s | PT59S + * + * e. g. `1w 3d 4h 32m 23s` is converted to 10 days 4 hours 32 minutes and 23 seconds. + * + * Special cases: + * - An empty string will return a zero interval + * - Fractions are allowed for weeks, days, hours and minutes and will be converted + * and rounded to the next smaller value (caution: 0.5w = 4d) + * + * @param string $intervalDefinition + * + * @return static + */ + public static function fromString($intervalDefinition) + { + if (empty($intervalDefinition)) { + return new static(0); + } + $years = 0; + $months = 0; + $weeks = 0; + $days = 0; + $hours = 0; + $minutes = 0; + $seconds = 0; + $pattern = '/(\\d+(?:\\.\\d+)?)\\h*([^\\d\\h]*)/i'; + \preg_match_all($pattern, $intervalDefinition, $parts, \PREG_SET_ORDER); + while ($match = \array_shift($parts)) { + list($part, $value, $unit) = $match; + $intValue = \intval($value); + $fraction = \floatval($value) - $intValue; + switch (\strtolower($unit)) { + case 'year': + case 'years': + case 'y': + $years += $intValue; + break; + case 'month': + case 'months': + case 'mo': + $months += $intValue; + break; + case 'week': + case 'weeks': + case 'w': + $weeks += $intValue; + if ($fraction) { + $parts[] = array(null, $fraction * static::getDaysPerWeek(), 'd'); + } + break; + case 'day': + case 'days': + case 'd': + $days += $intValue; + if ($fraction) { + $parts[] = array(null, $fraction * static::getHoursPerDay(), 'h'); + } + break; + case 'hour': + case 'hours': + case 'h': + $hours += $intValue; + if ($fraction) { + $parts[] = array(null, $fraction * static::getMinutesPerHours(), 'm'); + } + break; + case 'minute': + case 'minutes': + case 'm': + $minutes += $intValue; + if ($fraction) { + $seconds += \round($fraction * static::getSecondsPerMinutes()); + } + break; + case 'second': + case 'seconds': + case 's': + $seconds += $intValue; + break; + default: + throw new \InvalidArgumentException(\sprintf('Invalid part %s in definition %s', $part, $intervalDefinition)); + } + } + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds); + } + /** + * Create a CarbonInterval instance from a DateInterval one. Can not instance + * DateInterval objects created from DateTime::diff() as you can't externally + * set the $days field. + * + * Pass false as second argument to get a microseconds-precise interval. Else + * microseconds in the original interval will not be kept. + * + * @param DateInterval $di + * @param bool $trimMicroseconds (true by default) + * + * @return static + */ + public static function instance(\DateInterval $di, $trimMicroseconds = \true) + { + $microseconds = $trimMicroseconds || \version_compare(\PHP_VERSION, '7.1.0-dev', '<') ? 0 : $di->f; + $instance = new static(static::getDateIntervalSpec($di)); + if ($microseconds) { + $instance->f = $microseconds; + } + $instance->invert = $di->invert; + foreach (array('y', 'm', 'd', 'h', 'i', 's') as $unit) { + if ($di->{$unit} < 0) { + $instance->{$unit} *= -1; + } + } + return $instance; + } + /** + * Make a CarbonInterval instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be intervals (skip dates + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @return static|null + */ + public static function make($var) + { + if ($var instanceof \DateInterval) { + return static::instance($var); + } + if (\is_string($var)) { + $var = \trim($var); + if (\substr($var, 0, 1) === 'P') { + return new static($var); + } + if (\preg_match('/^(?:\\h*\\d+(?:\\.\\d+)?\\h*[a-z]+)+$/i', $var)) { + return static::fromString($var); + } + } + } + /////////////////////////////////////////////////////////////////// + /////////////////////// LOCALIZATION ////////////////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Initialize the translator instance if necessary. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + protected static function translator() + { + if (static::$translator === null) { + static::$translator = \ILAB\MediaCloud\Utilities\Misc\Carbon\Translator::get(); + } + return static::$translator; + } + /** + * Get the translator instance in use. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator() + { + return static::translator(); + } + /** + * Set the translator instance to use. + * + * @param TranslatorInterface $translator + */ + public static function setTranslator(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface $translator) + { + static::$translator = $translator; + } + /** + * Get the current translator locale. + * + * @return string + */ + public static function getLocale() + { + return static::translator()->getLocale(); + } + /** + * Set the current translator locale. + * + * @param string $locale + */ + public static function setLocale($locale) + { + return static::translator()->setLocale($locale) !== \false; + } + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + /** + * Get a part of the CarbonInterval object. + * + * @param string $name + * + * @throws \InvalidArgumentException + * + * @return int|float + */ + public function __get($name) + { + if (\substr($name, 0, 5) === 'total') { + return $this->total(\substr($name, 5)); + } + switch ($name) { + case 'years': + return $this->y; + case 'months': + return $this->m; + case 'dayz': + return $this->d; + case 'hours': + return $this->h; + case 'minutes': + return $this->i; + case 'seconds': + return $this->s; + case 'weeks': + return (int) \floor($this->d / static::getDaysPerWeek()); + case 'daysExcludeWeeks': + case 'dayzExcludeWeeks': + return $this->d % static::getDaysPerWeek(); + default: + throw new \InvalidArgumentException(\sprintf("Unknown getter '%s'", $name)); + } + } + /** + * Set a part of the CarbonInterval object. + * + * @param string $name + * @param int $val + * + * @throws \InvalidArgumentException + */ + public function __set($name, $val) + { + switch ($name) { + case 'years': + $this->y = $val; + break; + case 'months': + $this->m = $val; + break; + case 'weeks': + $this->d = $val * static::getDaysPerWeek(); + break; + case 'dayz': + $this->d = $val; + break; + case 'hours': + $this->h = $val; + break; + case 'minutes': + $this->i = $val; + break; + case 'seconds': + $this->s = $val; + break; + } + } + /** + * Allow setting of weeks and days to be cumulative. + * + * @param int $weeks Number of weeks to set + * @param int $days Number of days to set + * + * @return static + */ + public function weeksAndDays($weeks, $days) + { + $this->dayz = $weeks * static::getDaysPerWeek() + $days; + return $this; + } + /** + * Register a custom macro. + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + /** + * Remove all macros. + */ + public static function resetMacros() + { + static::$macros = array(); + } + /** + * Register macros from a mixin object. + * + * @param object $mixin + * + * @throws \ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + $reflection = new \ReflectionClass($mixin); + $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED); + foreach ($methods as $method) { + $method->setAccessible(\true); + static::macro($method->name, $method->invoke($mixin)); + } + } + /** + * Check if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + $reflection = new \ReflectionFunction($macro); + $reflectionParameters = $reflection->getParameters(); + $expectedCount = \count($reflectionParameters); + $actualCount = \count($parameters); + if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') { + for ($i = $actualCount; $i < $expectedCount - 1; $i++) { + $parameters[] = $reflectionParameters[$i]->getDefaultValue(); + } + $parameters[] = $this; + } + if ($macro instanceof \Closure && \method_exists($macro, 'bindTo')) { + $macro = $macro->bindTo($this, \get_class($this)); + } + return \call_user_func_array($macro, $parameters); + } + /** + * Allow fluent calls on the setters... CarbonInterval::years(3)->months(5)->day(). + * + * Note: This is done using the magic method to allow static and instance methods to + * have the same names. + * + * @param string $name + * @param array $args + * + * @return static + */ + public function __call($name, $args) + { + if (static::hasMacro($name)) { + return $this->callMacro($name, $args); + } + $arg = \count($args) === 0 ? 1 : $args[0]; + switch ($name) { + case 'years': + case 'year': + $this->years = $arg; + break; + case 'months': + case 'month': + $this->months = $arg; + break; + case 'weeks': + case 'week': + $this->dayz = $arg * static::getDaysPerWeek(); + break; + case 'days': + case 'dayz': + case 'day': + $this->dayz = $arg; + break; + case 'hours': + case 'hour': + $this->hours = $arg; + break; + case 'minutes': + case 'minute': + $this->minutes = $arg; + break; + case 'seconds': + case 'second': + $this->seconds = $arg; + break; + } + return $this; + } + /** + * Get the current interval in a human readable format in the current locale. + * + * @param bool $short (false by default), returns short units if true + * + * @return string + */ + public function forHumans($short = \false) + { + $periods = array('year' => array('y', $this->years), 'month' => array('m', $this->months), 'week' => array('w', $this->weeks), 'day' => array('d', $this->daysExcludeWeeks), 'hour' => array('h', $this->hours), 'minute' => array('min', $this->minutes), 'second' => array('s', $this->seconds)); + $parts = array(); + foreach ($periods as $unit => $options) { + list($shortUnit, $count) = $options; + if ($count > 0) { + $parts[] = static::translator()->transChoice($short ? $shortUnit : $unit, $count, array(':count' => $count)); + } + } + return \implode(' ', $parts); + } + /** + * Format the instance as a string using the forHumans() function. + * + * @return string + */ + public function __toString() + { + return $this->forHumans(); + } + /** + * Convert the interval to a CarbonPeriod. + * + * @return CarbonPeriod + */ + public function toPeriod() + { + return \ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonPeriod::createFromArray(\array_merge(array($this), \func_get_args())); + } + /** + * Invert the interval. + * + * @return $this + */ + public function invert() + { + $this->invert = $this->invert ? 0 : 1; + return $this; + } + /** + * Add the passed interval to the current instance. + * + * @param DateInterval $interval + * + * @return static + */ + public function add(\DateInterval $interval) + { + $sign = ($this->invert === 1) !== ($interval->invert === 1) ? -1 : 1; + if (static::wasCreatedFromDiff($interval)) { + $this->dayz += $interval->days * $sign; + } else { + $this->years += $interval->y * $sign; + $this->months += $interval->m * $sign; + $this->dayz += $interval->d * $sign; + $this->hours += $interval->h * $sign; + $this->minutes += $interval->i * $sign; + $this->seconds += $interval->s * $sign; + } + if (($this->years || $this->months || $this->dayz || $this->hours || $this->minutes || $this->seconds) && $this->years <= 0 && $this->months <= 0 && $this->dayz <= 0 && $this->hours <= 0 && $this->minutes <= 0 && $this->seconds <= 0) { + $this->years *= -1; + $this->months *= -1; + $this->dayz *= -1; + $this->hours *= -1; + $this->minutes *= -1; + $this->seconds *= -1; + $this->invert(); + } + return $this; + } + /** + * Multiply current instance given number of times + * + * @param float $factor + * + * @return $this + */ + public function times($factor) + { + if ($factor < 0) { + $this->invert = $this->invert ? 0 : 1; + $factor = -$factor; + } + $this->years = (int) \round($this->years * $factor); + $this->months = (int) \round($this->months * $factor); + $this->dayz = (int) \round($this->dayz * $factor); + $this->hours = (int) \round($this->hours * $factor); + $this->minutes = (int) \round($this->minutes * $factor); + $this->seconds = (int) \round($this->seconds * $factor); + return $this; + } + /** + * Get the interval_spec string of a date interval. + * + * @param DateInterval $interval + * + * @return string + */ + public static function getDateIntervalSpec(\DateInterval $interval) + { + $date = \array_filter(array(static::PERIOD_YEARS => \abs($interval->y), static::PERIOD_MONTHS => \abs($interval->m), static::PERIOD_DAYS => \abs($interval->d))); + $time = \array_filter(array(static::PERIOD_HOURS => \abs($interval->h), static::PERIOD_MINUTES => \abs($interval->i), static::PERIOD_SECONDS => \abs($interval->s))); + $specString = static::PERIOD_PREFIX; + foreach ($date as $key => $value) { + $specString .= $value . $key; + } + if (\count($time) > 0) { + $specString .= static::PERIOD_TIME_PREFIX; + foreach ($time as $key => $value) { + $specString .= $value . $key; + } + } + return $specString === static::PERIOD_PREFIX ? 'PT0S' : $specString; + } + /** + * Get the interval_spec string. + * + * @return string + */ + public function spec() + { + return static::getDateIntervalSpec($this); + } + /** + * Comparing 2 date intervals. + * + * @param DateInterval $a + * @param DateInterval $b + * + * @return int + */ + public static function compareDateIntervals(\DateInterval $a, \DateInterval $b) + { + $current = \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::now(); + $passed = $current->copy()->add($b); + $current->add($a); + if ($current < $passed) { + return -1; + } + if ($current > $passed) { + return 1; + } + return 0; + } + /** + * Comparing with passed interval. + * + * @param DateInterval $interval + * + * @return int + */ + public function compare(\DateInterval $interval) + { + return static::compareDateIntervals($this, $interval); + } + /** + * Convert overflowed values into bigger units. + * + * @return $this + */ + public function cascade() + { + foreach (static::getFlipCascadeFactors() as $source => $cascade) { + list($target, $factor) = $cascade; + if ($source === 'dayz' && $target === 'weeks') { + continue; + } + $value = $this->{$source}; + $this->{$source} = $modulo = $value % $factor; + $this->{$target} += ($value - $modulo) / $factor; + } + return $this; + } + /** + * Get amount of given unit equivalent to the interval. + * + * @param string $unit + * + * @throws \InvalidArgumentException + * + * @return float + */ + public function total($unit) + { + $realUnit = $unit = \strtolower($unit); + if (\in_array($unit, array('days', 'weeks'))) { + $realUnit = 'dayz'; + } elseif (!\in_array($unit, array('seconds', 'minutes', 'hours', 'dayz', 'months', 'years'))) { + throw new \InvalidArgumentException("Unknown unit '{$unit}'."); + } + $result = 0; + $cumulativeFactor = 0; + $unitFound = \false; + foreach (static::getFlipCascadeFactors() as $source => $cascade) { + list($target, $factor) = $cascade; + if ($source === $realUnit) { + $unitFound = \true; + $result += $this->{$source}; + $cumulativeFactor = 1; + } + if ($factor === \false) { + if ($unitFound) { + break; + } + $result = 0; + $cumulativeFactor = 0; + continue; + } + if ($target === $realUnit) { + $unitFound = \true; + } + if ($cumulativeFactor) { + $cumulativeFactor *= $factor; + $result += $this->{$target} * $cumulativeFactor; + continue; + } + $result = ($result + $this->{$source}) / $factor; + } + if (isset($target) && !$cumulativeFactor) { + $result += $this->{$target}; + } + if (!$unitFound) { + throw new \InvalidArgumentException("Unit {$unit} have no configuration to get total from other units."); + } + if ($unit === 'weeks') { + return $result / static::getDaysPerWeek(); + } + return $result; + } +} diff --git a/classes/Utilities/Misc/Carbon/CarbonPeriod.php b/classes/Utilities/Misc/Carbon/CarbonPeriod.php new file mode 100755 index 00000000..81c62e20 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/CarbonPeriod.php @@ -0,0 +1,1222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Carbon; + +use BadMethodCallException; +use Closure; +use Countable; +use DateInterval; +use DateTime; +use DateTimeInterface; +use InvalidArgumentException; +use Iterator; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +use RuntimeException; +/** + * Substitution of DatePeriod with some modifications and many more features. + * Fully compatible with PHP 5.3+! + * + * @method static CarbonPeriod start($date, $inclusive = null) Create instance specifying start date. + * @method static CarbonPeriod since($date, $inclusive = null) Alias for start(). + * @method static CarbonPeriod sinceNow($inclusive = null) Create instance with start date set to now. + * @method static CarbonPeriod end($date = null, $inclusive = null) Create instance specifying end date. + * @method static CarbonPeriod until($date = null, $inclusive = null) Alias for end(). + * @method static CarbonPeriod untilNow($inclusive = null) Create instance with end date set to now. + * @method static CarbonPeriod dates($start, $end = null) Create instance with start and end date. + * @method static CarbonPeriod between($start, $end = null) Create instance with start and end date. + * @method static CarbonPeriod recurrences($recurrences = null) Create instance with maximum number of recurrences. + * @method static CarbonPeriod times($recurrences = null) Alias for recurrences(). + * @method static CarbonPeriod options($options = null) Create instance with options. + * @method static CarbonPeriod toggle($options, $state = null) Create instance with options toggled on or off. + * @method static CarbonPeriod filter($callback, $name = null) Create instance with filter added to the stack. + * @method static CarbonPeriod push($callback, $name = null) Alias for filter(). + * @method static CarbonPeriod prepend($callback, $name = null) Create instance with filter prepened to the stack. + * @method static CarbonPeriod filters(array $filters) Create instance with filters stack. + * @method static CarbonPeriod interval($interval) Create instance with given date interval. + * @method static CarbonPeriod each($interval) Create instance with given date interval. + * @method static CarbonPeriod every($interval) Create instance with given date interval. + * @method static CarbonPeriod step($interval) Create instance with given date interval. + * @method static CarbonPeriod stepBy($interval) Create instance with given date interval. + * @method static CarbonPeriod invert() Create instance with inverted date interval. + * @method static CarbonPeriod years($years = 1) Create instance specifying a number of years for date interval. + * @method static CarbonPeriod year($years = 1) Alias for years(). + * @method static CarbonPeriod months($months = 1) Create instance specifying a number of months for date interval. + * @method static CarbonPeriod month($months = 1) Alias for months(). + * @method static CarbonPeriod weeks($weeks = 1) Create instance specifying a number of weeks for date interval. + * @method static CarbonPeriod week($weeks = 1) Alias for weeks(). + * @method static CarbonPeriod days($days = 1) Create instance specifying a number of days for date interval. + * @method static CarbonPeriod dayz($days = 1) Alias for days(). + * @method static CarbonPeriod day($days = 1) Alias for days(). + * @method static CarbonPeriod hours($hours = 1) Create instance specifying a number of hours for date interval. + * @method static CarbonPeriod hour($hours = 1) Alias for hours(). + * @method static CarbonPeriod minutes($minutes = 1) Create instance specifying a number of minutes for date interval. + * @method static CarbonPeriod minute($minutes = 1) Alias for minutes(). + * @method static CarbonPeriod seconds($seconds = 1) Create instance specifying a number of seconds for date interval. + * @method static CarbonPeriod second($seconds = 1) Alias for seconds(). + * @method CarbonPeriod start($date, $inclusive = null) Change the period start date. + * @method CarbonPeriod since($date, $inclusive = null) Alias for start(). + * @method CarbonPeriod sinceNow($inclusive = null) Change the period start date to now. + * @method CarbonPeriod end($date = null, $inclusive = null) Change the period end date. + * @method CarbonPeriod until($date = null, $inclusive = null) Alias for end(). + * @method CarbonPeriod untilNow($inclusive = null) Change the period end date to now. + * @method CarbonPeriod dates($start, $end = null) Change the period start and end date. + * @method CarbonPeriod recurrences($recurrences = null) Change the maximum number of recurrences. + * @method CarbonPeriod times($recurrences = null) Alias for recurrences(). + * @method CarbonPeriod options($options = null) Change the period options. + * @method CarbonPeriod toggle($options, $state = null) Toggle given options on or off. + * @method CarbonPeriod filter($callback, $name = null) Add a filter to the stack. + * @method CarbonPeriod push($callback, $name = null) Alias for filter(). + * @method CarbonPeriod prepend($callback, $name = null) Prepend a filter to the stack. + * @method CarbonPeriod filters(array $filters = array()) Set filters stack. + * @method CarbonPeriod interval($interval) Change the period date interval. + * @method CarbonPeriod invert() Invert the period date interval. + * @method CarbonPeriod years($years = 1) Set the years portion of the date interval. + * @method CarbonPeriod year($years = 1) Alias for years(). + * @method CarbonPeriod months($months = 1) Set the months portion of the date interval. + * @method CarbonPeriod month($months = 1) Alias for months(). + * @method CarbonPeriod weeks($weeks = 1) Set the weeks portion of the date interval. + * @method CarbonPeriod week($weeks = 1) Alias for weeks(). + * @method CarbonPeriod days($days = 1) Set the days portion of the date interval. + * @method CarbonPeriod dayz($days = 1) Alias for days(). + * @method CarbonPeriod day($days = 1) Alias for days(). + * @method CarbonPeriod hours($hours = 1) Set the hours portion of the date interval. + * @method CarbonPeriod hour($hours = 1) Alias for hours(). + * @method CarbonPeriod minutes($minutes = 1) Set the minutes portion of the date interval. + * @method CarbonPeriod minute($minutes = 1) Alias for minutes(). + * @method CarbonPeriod seconds($seconds = 1) Set the seconds portion of the date interval. + * @method CarbonPeriod second($seconds = 1) Alias for seconds(). + */ +class CarbonPeriod implements \Iterator, \Countable +{ + /** + * Built-in filters. + * + * @var string + */ + const RECURRENCES_FILTER = 'Carbon\\CarbonPeriod::filterRecurrences'; + const END_DATE_FILTER = 'Carbon\\CarbonPeriod::filterEndDate'; + /** + * Special value which can be returned by filters to end iteration. Also a filter. + * + * @var string + */ + const END_ITERATION = 'Carbon\\CarbonPeriod::endIteration'; + /** + * Available options. + * + * @var int + */ + const EXCLUDE_START_DATE = 1; + const EXCLUDE_END_DATE = 2; + /** + * Number of maximum attempts before giving up on finding next valid date. + * + * @var int + */ + const NEXT_MAX_ATTEMPTS = 1000; + /** + * The registered macros. + * + * @var array + */ + protected static $macros = array(); + /** + * Underlying date interval instance. Always present, one day by default. + * + * @var CarbonInterval + */ + protected $dateInterval; + /** + * Whether current date interval was set by default. + * + * @var bool + */ + protected $isDefaultInterval; + /** + * The filters stack. + * + * @var array + */ + protected $filters = array(); + /** + * Period start date. Applied on rewind. Always present, now by default. + * + * @var Carbon + */ + protected $startDate; + /** + * Period end date. For inverted interval should be before the start date. Applied via a filter. + * + * @var Carbon|null + */ + protected $endDate; + /** + * Limit for number of recurrences. Applied via a filter. + * + * @var int|null + */ + protected $recurrences; + /** + * Iteration options. + * + * @var int + */ + protected $options; + /** + * Index of current date. Always sequential, even if some dates are skipped by filters. + * Equal to null only before the first iteration. + * + * @var int + */ + protected $key; + /** + * Current date. May temporarily hold unaccepted value when looking for a next valid date. + * Equal to null only before the first iteration. + * + * @var Carbon + */ + protected $current; + /** + * Timezone of current date. Taken from the start date. + * + * @var \DateTimeZone|null + */ + protected $timezone; + /** + * The cached validation result for current date. + * + * @var bool|string|null + */ + protected $validationResult; + /** + * Create a new instance. + * + * @return static + */ + public static function create() + { + return static::createFromArray(\func_get_args()); + } + /** + * Create a new instance from an array of parameters. + * + * @param array $params + * + * @return static + */ + public static function createFromArray(array $params) + { + // PHP 5.3 equivalent of new static(...$params). + $reflection = new \ReflectionClass(\get_class()); + /** @var static $instance */ + $instance = $reflection->newInstanceArgs($params); + return $instance; + } + /** + * Create CarbonPeriod from ISO 8601 string. + * + * @param string $iso + * @param int|null $options + * + * @return static + */ + public static function createFromIso($iso, $options = null) + { + $params = static::parseIso8601($iso); + $instance = static::createFromArray($params); + if ($options !== null) { + $instance->setOptions($options); + } + return $instance; + } + /** + * Return whether given interval contains non zero value of any time unit. + * + * @param \DateInterval $interval + * + * @return bool + */ + protected static function intervalHasTime(\DateInterval $interval) + { + // The array_key_exists and get_object_vars are used as a workaround to check microsecond support. + // Both isset and property_exists will fail on PHP 7.0.14 - 7.0.21 due to the following bug: + // https://bugs.php.net/bug.php?id=74852 + return $interval->h || $interval->i || $interval->s || \array_key_exists('f', \get_object_vars($interval)) && $interval->f; + } + /** + * Return whether given callable is a string pointing to one of Carbon's is* methods + * and should be automatically converted to a filter callback. + * + * @param callable $callable + * + * @return bool + */ + protected static function isCarbonPredicateMethod($callable) + { + return \is_string($callable) && \substr($callable, 0, 2) === 'is' && (\method_exists('ILAB\\MediaCloud\\Utilities\\Misc\\Carbon\\Carbon', $callable) || \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::hasMacro($callable)); + } + /** + * Return whether given variable is an ISO 8601 specification. + * + * Note: Check is very basic, as actual validation will be done later when parsing. + * We just want to ensure that variable is not any other type of a valid parameter. + * + * @param mixed $var + * + * @return bool + */ + protected static function isIso8601($var) + { + if (!\is_string($var)) { + return \false; + } + // Match slash but not within a timezone name. + $part = '[a-z]+(?:[_-][a-z]+)*'; + \preg_match("#\\b{$part}/{$part}\\b|(/)#i", $var, $match); + return isset($match[1]); + } + /** + * Parse given ISO 8601 string into an array of arguments. + * + * @param string $iso + * + * @return array + */ + protected static function parseIso8601($iso) + { + $result = array(); + $interval = null; + $start = null; + $end = null; + foreach (\explode('/', $iso) as $key => $part) { + if ($key === 0 && \preg_match('/^R([0-9]*)$/', $part, $match)) { + $parsed = \strlen($match[1]) ? (int) $match[1] : null; + } elseif ($interval === null && ($parsed = \ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonInterval::make($part))) { + $interval = $part; + } elseif ($start === null && ($parsed = \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::make($part))) { + $start = $part; + } elseif ($end === null && ($parsed = \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::make(static::addMissingParts($start, $part)))) { + $end = $part; + } else { + throw new \InvalidArgumentException("Invalid ISO 8601 specification: {$iso}."); + } + $result[] = $parsed; + } + return $result; + } + /** + * Add missing parts of the target date from the soure date. + * + * @param string $source + * @param string $target + * + * @return string + */ + protected static function addMissingParts($source, $target) + { + $pattern = '/' . \preg_replace('/[0-9]+/', '[0-9]+', \preg_quote($target, '/')) . '$/'; + $result = \preg_replace($pattern, $target, $source, 1, $count); + return $count ? $result : $target; + } + /** + * Register a custom macro. + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + /** + * Remove all macros. + */ + public static function resetMacros() + { + static::$macros = array(); + } + /** + * Register macros from a mixin object. + * + * @param object $mixin + * + * @throws \ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + $reflection = new \ReflectionClass($mixin); + $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED); + foreach ($methods as $method) { + $method->setAccessible(\true); + static::macro($method->name, $method->invoke($mixin)); + } + } + /** + * Check if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + /** + * Provide static proxy for instance aliases. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + return \call_user_func_array(array(new static(), $method), $parameters); + } + /** + * CarbonPeriod constructor. + * + * @throws InvalidArgumentException + */ + public function __construct() + { + // Parse and assign arguments one by one. First argument may be an ISO 8601 spec, + // which will be first parsed into parts and then processed the same way. + $arguments = \func_get_args(); + if (\count($arguments) && static::isIso8601($iso = $arguments[0])) { + \array_splice($arguments, 0, 1, static::parseIso8601($iso)); + } + foreach ($arguments as $argument) { + if ($this->dateInterval === null && ($parsed = \ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonInterval::make($argument))) { + $this->setDateInterval($parsed); + } elseif ($this->startDate === null && ($parsed = \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::make($argument))) { + $this->setStartDate($parsed); + } elseif ($this->endDate === null && ($parsed = \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::make($argument))) { + $this->setEndDate($parsed); + } elseif ($this->recurrences === null && $this->endDate === null && \is_numeric($argument)) { + $this->setRecurrences($argument); + } elseif ($this->options === null && (\is_int($argument) || $argument === null)) { + $this->setOptions($argument); + } else { + throw new \InvalidArgumentException('Invalid constructor parameters.'); + } + } + if ($this->startDate === null) { + $this->setStartDate(\ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::now()); + } + if ($this->dateInterval === null) { + $this->setDateInterval(\ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonInterval::day()); + $this->isDefaultInterval = \true; + } + if ($this->options === null) { + $this->setOptions(0); + } + } + /** + * Change the period date interval. + * + * @param DateInterval|string $interval + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setDateInterval($interval) + { + if (!($interval = \ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonInterval::make($interval))) { + throw new \InvalidArgumentException('Invalid interval.'); + } + if ($interval->spec() === 'PT0S') { + throw new \InvalidArgumentException('Empty interval is not accepted.'); + } + $this->dateInterval = $interval; + $this->isDefaultInterval = \false; + $this->handleChangedParameters(); + return $this; + } + /** + * Invert the period date interval. + * + * @return $this + */ + public function invertDateInterval() + { + $interval = $this->dateInterval->invert(); + return $this->setDateInterval($interval); + } + /** + * Set start and end date. + * + * @param DateTime|DateTimeInterface|string $start + * @param DateTime|DateTimeInterface|string|null $end + * + * @return $this + */ + public function setDates($start, $end) + { + $this->setStartDate($start); + $this->setEndDate($end); + return $this; + } + /** + * Change the period options. + * + * @param int|null $options + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setOptions($options) + { + if (!\is_int($options) && !\is_null($options)) { + throw new \InvalidArgumentException('Invalid options.'); + } + $this->options = $options ?: 0; + $this->handleChangedParameters(); + return $this; + } + /** + * Get the period options. + * + * @return int + */ + public function getOptions() + { + return $this->options; + } + /** + * Toggle given options on or off. + * + * @param int $options + * @param bool|null $state + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function toggleOptions($options, $state = null) + { + if ($state === null) { + $state = ($this->options & $options) !== $options; + } + return $this->setOptions($state ? $this->options | $options : $this->options & ~$options); + } + /** + * Toggle EXCLUDE_START_DATE option. + * + * @param bool $state + * + * @return $this + */ + public function excludeStartDate($state = \true) + { + return $this->toggleOptions(static::EXCLUDE_START_DATE, $state); + } + /** + * Toggle EXCLUDE_END_DATE option. + * + * @param bool $state + * + * @return $this + */ + public function excludeEndDate($state = \true) + { + return $this->toggleOptions(static::EXCLUDE_END_DATE, $state); + } + /** + * Get the underlying date interval. + * + * @return CarbonInterval + */ + public function getDateInterval() + { + return $this->dateInterval->copy(); + } + /** + * Get start date of the period. + * + * @return Carbon + */ + public function getStartDate() + { + return $this->startDate->copy(); + } + /** + * Get end date of the period. + * + * @return Carbon|null + */ + public function getEndDate() + { + if ($this->endDate) { + return $this->endDate->copy(); + } + } + /** + * Get number of recurrences. + * + * @return int|null + */ + public function getRecurrences() + { + return $this->recurrences; + } + /** + * Returns true if the start date should be excluded. + * + * @return bool + */ + public function isStartExcluded() + { + return ($this->options & static::EXCLUDE_START_DATE) !== 0; + } + /** + * Returns true if the end date should be excluded. + * + * @return bool + */ + public function isEndExcluded() + { + return ($this->options & static::EXCLUDE_END_DATE) !== 0; + } + /** + * Add a filter to the stack. + * + * @param callable $callback + * @param string $name + * + * @return $this + */ + public function addFilter($callback, $name = null) + { + $tuple = $this->createFilterTuple(\func_get_args()); + $this->filters[] = $tuple; + $this->handleChangedParameters(); + return $this; + } + /** + * Prepend a filter to the stack. + * + * @param callable $callback + * @param string $name + * + * @return $this + */ + public function prependFilter($callback, $name = null) + { + $tuple = $this->createFilterTuple(\func_get_args()); + \array_unshift($this->filters, $tuple); + $this->handleChangedParameters(); + return $this; + } + /** + * Create a filter tuple from raw parameters. + * + * Will create an automatic filter callback for one of Carbon's is* methods. + * + * @param array $parameters + * + * @return array + */ + protected function createFilterTuple(array $parameters) + { + $method = \array_shift($parameters); + if (!$this->isCarbonPredicateMethod($method)) { + return array($method, \array_shift($parameters)); + } + return array(function ($date) use($method, $parameters) { + return \call_user_func_array(array($date, $method), $parameters); + }, $method); + } + /** + * Remove a filter by instance or name. + * + * @param callable|string $filter + * + * @return $this + */ + public function removeFilter($filter) + { + $key = \is_callable($filter) ? 0 : 1; + $this->filters = \array_values(\array_filter($this->filters, function ($tuple) use($key, $filter) { + return $tuple[$key] !== $filter; + })); + $this->updateInternalState(); + $this->handleChangedParameters(); + return $this; + } + /** + * Return whether given instance or name is in the filter stack. + * + * @param callable|string $filter + * + * @return bool + */ + public function hasFilter($filter) + { + $key = \is_callable($filter) ? 0 : 1; + foreach ($this->filters as $tuple) { + if ($tuple[$key] === $filter) { + return \true; + } + } + return \false; + } + /** + * Get filters stack. + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + /** + * Set filters stack. + * + * @param array $filters + * + * @return $this + */ + public function setFilters(array $filters) + { + $this->filters = $filters; + $this->updateInternalState(); + $this->handleChangedParameters(); + return $this; + } + /** + * Reset filters stack. + * + * @return $this + */ + public function resetFilters() + { + $this->filters = array(); + if ($this->endDate !== null) { + $this->filters[] = array(static::END_DATE_FILTER, null); + } + if ($this->recurrences !== null) { + $this->filters[] = array(static::RECURRENCES_FILTER, null); + } + $this->handleChangedParameters(); + return $this; + } + /** + * Update properties after removing built-in filters. + * + * @return void + */ + protected function updateInternalState() + { + if (!$this->hasFilter(static::END_DATE_FILTER)) { + $this->endDate = null; + } + if (!$this->hasFilter(static::RECURRENCES_FILTER)) { + $this->recurrences = null; + } + } + /** + * Add a recurrences filter (set maximum number of recurrences). + * + * @param int|null $recurrences + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setRecurrences($recurrences) + { + if (!\is_numeric($recurrences) && !\is_null($recurrences) || $recurrences < 0) { + throw new \InvalidArgumentException('Invalid number of recurrences.'); + } + if ($recurrences === null) { + return $this->removeFilter(static::RECURRENCES_FILTER); + } + $this->recurrences = (int) $recurrences; + if (!$this->hasFilter(static::RECURRENCES_FILTER)) { + return $this->addFilter(static::RECURRENCES_FILTER); + } + $this->handleChangedParameters(); + return $this; + } + /** + * Recurrences filter callback (limits number of recurrences). + * + * @param \Carbon\Carbon $current + * @param int $key + * + * @return bool|string + */ + protected function filterRecurrences($current, $key) + { + if ($key < $this->recurrences) { + return \true; + } + return static::END_ITERATION; + } + /** + * Change the period start date. + * + * @param DateTime|DateTimeInterface|string $date + * @param bool|null $inclusive + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setStartDate($date, $inclusive = null) + { + if (!($date = \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::make($date))) { + throw new \InvalidArgumentException('Invalid start date.'); + } + $this->startDate = $date; + if ($inclusive !== null) { + $this->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive); + } + return $this; + } + /** + * Change the period end date. + * + * @param DateTime|DateTimeInterface|string|null $date + * @param bool|null $inclusive + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setEndDate($date, $inclusive = null) + { + if (!\is_null($date) && !($date = \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::make($date))) { + throw new \InvalidArgumentException('Invalid end date.'); + } + if (!$date) { + return $this->removeFilter(static::END_DATE_FILTER); + } + $this->endDate = $date; + if ($inclusive !== null) { + $this->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive); + } + if (!$this->hasFilter(static::END_DATE_FILTER)) { + return $this->addFilter(static::END_DATE_FILTER); + } + $this->handleChangedParameters(); + return $this; + } + /** + * End date filter callback. + * + * @param \Carbon\Carbon $current + * + * @return bool|string + */ + protected function filterEndDate($current) + { + if (!$this->isEndExcluded() && $current == $this->endDate) { + return \true; + } + if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) { + return \true; + } + return static::END_ITERATION; + } + /** + * End iteration filter callback. + * + * @return string + */ + protected function endIteration() + { + return static::END_ITERATION; + } + /** + * Handle change of the parameters. + */ + protected function handleChangedParameters() + { + $this->validationResult = null; + } + /** + * Validate current date and stop iteration when necessary. + * + * Returns true when current date is valid, false if it is not, or static::END_ITERATION + * when iteration should be stopped. + * + * @return bool|string + */ + protected function validateCurrentDate() + { + if ($this->current === null) { + $this->rewind(); + } + // Check after the first rewind to avoid repeating the initial validation. + if ($this->validationResult !== null) { + return $this->validationResult; + } + return $this->validationResult = $this->checkFilters(); + } + /** + * Check whether current value and key pass all the filters. + * + * @return bool|string + */ + protected function checkFilters() + { + $current = $this->prepareForReturn($this->current); + foreach ($this->filters as $tuple) { + $result = \call_user_func($tuple[0], $current->copy(), $this->key, $this); + if ($result === static::END_ITERATION) { + return static::END_ITERATION; + } + if (!$result) { + return \false; + } + } + return \true; + } + /** + * Prepare given date to be returned to the external logic. + * + * @param Carbon $date + * + * @return Carbon + */ + protected function prepareForReturn(\ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon $date) + { + $date = $date->copy(); + if ($this->timezone) { + $date->setTimezone($this->timezone); + } + return $date; + } + /** + * Check if the current position is valid. + * + * @return bool + */ + public function valid() + { + return $this->validateCurrentDate() === \true; + } + /** + * Return the current key. + * + * @return int|null + */ + public function key() + { + if ($this->valid()) { + return $this->key; + } + } + /** + * Return the current date. + * + * @return Carbon|null + */ + public function current() + { + if ($this->valid()) { + return $this->prepareForReturn($this->current); + } + } + /** + * Move forward to the next date. + * + * @throws \RuntimeException + * + * @return void + */ + public function next() + { + if ($this->current === null) { + $this->rewind(); + } + if ($this->validationResult !== static::END_ITERATION) { + $this->key++; + $this->incrementCurrentDateUntilValid(); + } + } + /** + * Rewind to the start date. + * + * Iterating over a date in the UTC timezone avoids bug during backward DST change. + * + * @see https://bugs.php.net/bug.php?id=72255 + * @see https://bugs.php.net/bug.php?id=74274 + * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time + * + * @throws \RuntimeException + * + * @return void + */ + public function rewind() + { + $this->key = 0; + $this->current = $this->startDate->copy(); + $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null; + if ($this->timezone) { + $this->current->setTimezone('UTC'); + } + $this->validationResult = null; + if ($this->isStartExcluded() || $this->validateCurrentDate() === \false) { + $this->incrementCurrentDateUntilValid(); + } + } + /** + * Skip iterations and returns iteration state (false if ended, true if still valid). + * + * @param int $count steps number to skip (1 by default) + * + * @return bool + */ + public function skip($count = 1) + { + for ($i = $count; $this->valid() && $i > 0; $i--) { + $this->next(); + } + return $this->valid(); + } + /** + * Keep incrementing the current date until a valid date is found or the iteration is ended. + * + * @throws \RuntimeException + * + * @return void + */ + protected function incrementCurrentDateUntilValid() + { + $attempts = 0; + do { + $this->current->add($this->dateInterval); + $this->validationResult = null; + if (++$attempts > static::NEXT_MAX_ATTEMPTS) { + throw new \RuntimeException('Could not find next valid date.'); + } + } while ($this->validateCurrentDate() === \false); + } + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function toIso8601String() + { + $parts = array(); + if ($this->recurrences !== null) { + $parts[] = 'R' . $this->recurrences; + } + $parts[] = $this->startDate->toIso8601String(); + $parts[] = $this->dateInterval->spec(); + if ($this->endDate !== null) { + $parts[] = $this->endDate->toIso8601String(); + } + return \implode('/', $parts); + } + /** + * Convert the date period into a string. + * + * @return string + */ + public function toString() + { + $translator = \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::getTranslator(); + $parts = array(); + $format = !$this->startDate->isStartOfDay() || $this->endDate && !$this->endDate->isStartOfDay() ? 'Y-m-d H:i:s' : 'Y-m-d'; + if ($this->recurrences !== null) { + $parts[] = $translator->transChoice('period_recurrences', $this->recurrences, array(':count' => $this->recurrences)); + } + $parts[] = $translator->trans('period_interval', array(':interval' => $this->dateInterval->forHumans())); + $parts[] = $translator->trans('period_start_date', array(':date' => $this->startDate->format($format))); + if ($this->endDate !== null) { + $parts[] = $translator->trans('period_end_date', array(':date' => $this->endDate->format($format))); + } + $result = \implode(' ', $parts); + return \mb_strtoupper(\mb_substr($result, 0, 1)) . \mb_substr($result, 1); + } + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function spec() + { + return $this->toIso8601String(); + } + /** + * Convert the date period into an array without changing current iteration state. + * + * @return array + */ + public function toArray() + { + $state = array($this->key, $this->current ? $this->current->copy() : null, $this->validationResult); + $result = \iterator_to_array($this); + list($this->key, $this->current, $this->validationResult) = $state; + return $result; + } + /** + * Count dates in the date period. + * + * @return int + */ + public function count() + { + return \count($this->toArray()); + } + /** + * Return the first date in the date period. + * + * @return Carbon|null + */ + public function first() + { + if ($array = $this->toArray()) { + return $array[0]; + } + } + /** + * Return the last date in the date period. + * + * @return Carbon|null + */ + public function last() + { + if ($array = $this->toArray()) { + return $array[\count($array) - 1]; + } + } + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + $reflection = new \ReflectionFunction($macro); + $reflectionParameters = $reflection->getParameters(); + $expectedCount = \count($reflectionParameters); + $actualCount = \count($parameters); + if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') { + for ($i = $actualCount; $i < $expectedCount - 1; $i++) { + $parameters[] = $reflectionParameters[$i]->getDefaultValue(); + } + $parameters[] = $this; + } + if ($macro instanceof \Closure && \method_exists($macro, 'bindTo')) { + $macro = $macro->bindTo($this, \get_class($this)); + } + return \call_user_func_array($macro, $parameters); + } + /** + * Convert the date period into a string. + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + /** + * Add aliases for setters. + * + * CarbonPeriod::days(3)->hours(5)->invert() + * ->sinceNow()->until('2010-01-10') + * ->filter(...) + * ->count() + * + * Note: We use magic method to let static and instance aliases with the same names. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->callMacro($method, $parameters); + } + $first = \count($parameters) >= 1 ? $parameters[0] : null; + $second = \count($parameters) >= 2 ? $parameters[1] : null; + switch ($method) { + case 'start': + case 'since': + return $this->setStartDate($first, $second); + case 'sinceNow': + return $this->setStartDate(new \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon(), $first); + case 'end': + case 'until': + return $this->setEndDate($first, $second); + case 'untilNow': + return $this->setEndDate(new \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon(), $first); + case 'dates': + case 'between': + return $this->setDates($first, $second); + case 'recurrences': + case 'times': + return $this->setRecurrences($first); + case 'options': + return $this->setOptions($first); + case 'toggle': + return $this->toggleOptions($first, $second); + case 'filter': + case 'push': + return $this->addFilter($first, $second); + case 'prepend': + return $this->prependFilter($first, $second); + case 'filters': + return $this->setFilters($first ?: array()); + case 'interval': + case 'each': + case 'every': + case 'step': + case 'stepBy': + return $this->setDateInterval($first); + case 'invert': + return $this->invertDateInterval(); + case 'years': + case 'year': + case 'months': + case 'month': + case 'weeks': + case 'week': + case 'days': + case 'dayz': + case 'day': + case 'hours': + case 'hour': + case 'minutes': + case 'minute': + case 'seconds': + case 'second': + return $this->setDateInterval(\call_user_func( + // Override default P1D when instantiating via fluent setters. + array($this->isDefaultInterval ? new \ILAB\MediaCloud\Utilities\Misc\Carbon\CarbonInterval('PT0S') : $this->dateInterval, $method), + \count($parameters) === 0 ? 1 : $first + )); + } + throw new \BadMethodCallException("Method {$method} does not exist."); + } +} diff --git a/classes/Utilities/Misc/Carbon/Exceptions/InvalidDateException.php b/classes/Utilities/Misc/Carbon/Exceptions/InvalidDateException.php new file mode 100755 index 00000000..121cfe53 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Exceptions/InvalidDateException.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 ILAB\MediaCloud\Utilities\Misc\Carbon\Exceptions; + +use Exception; +use InvalidArgumentException; +class InvalidDateException extends \InvalidArgumentException +{ + /** + * The invalid field. + * + * @var string + */ + private $field; + /** + * The invalid value. + * + * @var mixed + */ + private $value; + /** + * Constructor. + * + * @param string $field + * @param mixed $value + * @param int $code + * @param \Exception|null $previous + */ + public function __construct($field, $value, $code = 0, \Exception $previous = null) + { + $this->field = $field; + $this->value = $value; + parent::__construct($field . ' : ' . $value . ' is not a valid value.', $code, $previous); + } + /** + * Get the invalid field. + * + * @return string + */ + public function getField() + { + return $this->field; + } + /** + * Get the invalid value. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/classes/Utilities/Misc/Carbon/Lang/af.php b/classes/Utilities/Misc/Carbon/Lang/af.php new file mode 100755 index 00000000..09f8bbc5 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/af.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count jaar|:count jare', 'y' => ':count jaar|:count jare', 'month' => ':count maand|:count maande', 'm' => ':count maand|:count maande', 'week' => ':count week|:count weke', 'w' => ':count week|:count weke', 'day' => ':count dag|:count dae', 'd' => ':count dag|:count dae', 'hour' => ':count uur|:count ure', 'h' => ':count uur|:count ure', 'minute' => ':count minuut|:count minute', 'min' => ':count minuut|:count minute', 'second' => ':count sekond|:count sekondes', 's' => ':count sekond|:count sekondes', 'ago' => ':time terug', 'from_now' => ':time van nou af', 'after' => ':time na', 'before' => ':time voor'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ar.php b/classes/Utilities/Misc/Carbon/Lang/ar.php new file mode 100755 index 00000000..fd0e69c9 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ar.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '{0}سنة|{1}سنة|{2}سنتين|[3,10]:count سنوات|[11,Inf]:count سنة', 'y' => '{0}سنة|{1}سنة|{2}سنتين|[3,10]:count سنوات|[11,Inf]:count سنة', 'month' => '{0}شهر|{1} شهر|{2}شهرين|[3,10]:count أشهر|[11,Inf]:count شهر', 'm' => '{0}شهر|{1} شهر|{2}شهرين|[3,10]:count أشهر|[11,Inf]:count شهر', 'week' => '{0}أسبوع|{1}أسبوع|{2}أسبوعين|[3,10]:count أسابيع|[11,Inf]:count أسبوع', 'w' => '{0}أسبوع|{1}أسبوع|{2}أسبوعين|[3,10]:count أسابيع|[11,Inf]:count أسبوع', 'day' => '{0}يوم|{1}يوم|{2}يومين|[3,10]:count أيام|[11,Inf] يوم', 'd' => '{0}يوم|{1}يوم|{2}يومين|[3,10]:count أيام|[11,Inf] يوم', 'hour' => '{0}ساعة|{1}ساعة|{2}ساعتين|[3,10]:count ساعات|[11,Inf]:count ساعة', 'h' => '{0}ساعة|{1}ساعة|{2}ساعتين|[3,10]:count ساعات|[11,Inf]:count ساعة', 'minute' => '{0}دقيقة|{1}دقيقة|{2}دقيقتين|[3,10]:count دقائق|[11,Inf]:count دقيقة', 'min' => '{0}دقيقة|{1}دقيقة|{2}دقيقتين|[3,10]:count دقائق|[11,Inf]:count دقيقة', 'second' => '{0}ثانية|{1}ثانية|{2}ثانيتين|[3,10]:count ثوان|[11,Inf]:count ثانية', 's' => '{0}ثانية|{1}ثانية|{2}ثانيتين|[3,10]:count ثوان|[11,Inf]:count ثانية', 'ago' => 'منذ :time', 'from_now' => ':time من الآن', 'after' => 'بعد :time', 'before' => 'قبل :time'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ar_Shakl.php b/classes/Utilities/Misc/Carbon/Lang/ar_Shakl.php new file mode 100755 index 00000000..d2880a08 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ar_Shakl.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '[0,1] سَنَة|{2} سَنَتَيْن|[3,10]:count سَنَوَات|[11,Inf]:count سَنَة', 'y' => '[0,1] سَنَة|{2} سَنَتَيْن|[3,10]:count سَنَوَات|[11,Inf]:count سَنَة', 'month' => '[0,1] شَهْرَ|{2} شَهْرَيْن|[3,10]:count أَشْهÙر|[11,Inf]:count شَهْرَ', 'm' => '[0,1] شَهْرَ|{2} شَهْرَيْن|[3,10]:count أَشْهÙر|[11,Inf]:count شَهْرَ', 'week' => '[0,1] Ø£ÙسْبÙوع|{2} Ø£ÙسْبÙوعَيْن|[3,10]:count أَسَابÙيع|[11,Inf]:count Ø£ÙسْبÙوع', 'w' => '[0,1] Ø£ÙسْبÙوع|{2} Ø£ÙسْبÙوعَيْن|[3,10]:count أَسَابÙيع|[11,Inf]:count Ø£ÙسْبÙوع', 'day' => '[0,1] يَوْم|{2} يَوْمَيْن|[3,10]:count أَيَّام|[11,Inf] يَوْم', 'd' => '[0,1] يَوْم|{2} يَوْمَيْن|[3,10]:count أَيَّام|[11,Inf] يَوْم', 'hour' => '[0,1] سَاعَة|{2} سَاعَتَيْن|[3,10]:count سَاعَات|[11,Inf]:count سَاعَة', 'h' => '[0,1] سَاعَة|{2} سَاعَتَيْن|[3,10]:count سَاعَات|[11,Inf]:count سَاعَة', 'minute' => '[0,1] دَقÙيقَة|{2} دَقÙيقَتَيْن|[3,10]:count دَقَائÙÙ‚|[11,Inf]:count دَقÙيقَة', 'min' => '[0,1] دَقÙيقَة|{2} دَقÙيقَتَيْن|[3,10]:count دَقَائÙÙ‚|[11,Inf]:count دَقÙيقَة', 'second' => '[0,1] ثَانÙÙŠÙŽØ©|{2} ثَانÙيَتَيْن|[3,10]:count ثَوَان|[11,Inf]:count ثَانÙÙŠÙŽØ©', 's' => '[0,1] ثَانÙÙŠÙŽØ©|{2} ثَانÙيَتَيْن|[3,10]:count ثَوَان|[11,Inf]:count ثَانÙÙŠÙŽØ©', 'ago' => 'Ù…Ùنْذ٠:time', 'from_now' => 'Ù…ÙÙ†ÙŽ الْآن :time', 'after' => 'بَعْدَ :time', 'before' => 'قَبْلَ :time'); diff --git a/classes/Utilities/Misc/Carbon/Lang/az.php b/classes/Utilities/Misc/Carbon/Lang/az.php new file mode 100755 index 00000000..afc7ac1d --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/az.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count il', 'y' => ':count il', 'month' => ':count ay', 'm' => ':count ay', 'week' => ':count hÉ™ftÉ™', 'w' => ':count hÉ™ftÉ™', 'day' => ':count gün', 'd' => ':count gün', 'hour' => ':count saat', 'h' => ':count saat', 'minute' => ':count dÉ™qiqÉ™', 'min' => ':count dÉ™qiqÉ™', 'second' => ':count saniyÉ™', 's' => ':count saniyÉ™', 'ago' => ':time É™vvÉ™l', 'from_now' => ':time sonra', 'after' => ':time sonra', 'before' => ':time É™vvÉ™l', 'diff_now' => 'indi', 'diff_yesterday' => 'dünÉ™n', 'diff_tomorrow' => 'sabah', 'diff_before_yesterday' => 'sraÄŸagün', 'diff_after_tomorrow' => 'birisi gün', 'period_recurrences' => ':count dÉ™fÉ™dÉ™n bir', 'period_interval' => 'hÉ™r :interval', 'period_start_date' => ':date tarixindÉ™n baÅŸlayaraq', 'period_end_date' => ':date tarixinÉ™dÉ™k'); diff --git a/classes/Utilities/Misc/Carbon/Lang/bg.php b/classes/Utilities/Misc/Carbon/Lang/bg.php new file mode 100755 index 00000000..15825201 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/bg.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count година|:count години', 'y' => ':count година|:count години', 'month' => ':count меÑец|:count меÑеца', 'm' => ':count меÑец|:count меÑеца', 'week' => ':count Ñедмица|:count Ñедмици', 'w' => ':count Ñедмица|:count Ñедмици', 'day' => ':count ден|:count дни', 'd' => ':count ден|:count дни', 'hour' => ':count чаÑ|:count чаÑа', 'h' => ':count чаÑ|:count чаÑа', 'minute' => ':count минута|:count минути', 'min' => ':count минута|:count минути', 'second' => ':count Ñекунда|:count Ñекунди', 's' => ':count Ñекунда|:count Ñекунди', 'ago' => 'преди :time', 'from_now' => ':time от Ñега', 'after' => 'Ñлед :time', 'before' => 'преди :time'); diff --git a/classes/Utilities/Misc/Carbon/Lang/bn.php b/classes/Utilities/Misc/Carbon/Lang/bn.php new file mode 100755 index 00000000..e082f908 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/bn.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '১ বছর|:count বছর', 'y' => '১ বছর|:count বছর', 'month' => '১ মাস|:count মাস', 'm' => '১ মাস|:count মাস', 'week' => '১ সপà§à¦¤à¦¾à¦¹|:count সপà§à¦¤à¦¾à¦¹', 'w' => '১ সপà§à¦¤à¦¾à¦¹|:count সপà§à¦¤à¦¾à¦¹', 'day' => '১ দিন|:count দিন', 'd' => '১ দিন|:count দিন', 'hour' => '১ ঘনà§à¦Ÿà¦¾|:count ঘনà§à¦Ÿà¦¾', 'h' => '১ ঘনà§à¦Ÿà¦¾|:count ঘনà§à¦Ÿà¦¾', 'minute' => '১ মিনিট|:count মিনিট', 'min' => '১ মিনিট|:count মিনিট', 'second' => '১ সেকেনà§à¦¡|:count সেকেনà§à¦¡', 's' => '১ সেকেনà§à¦¡|:count সেকেনà§à¦¡', 'ago' => ':time পূরà§à¦¬à§‡', 'from_now' => 'à¦à¦–ন থেকে :time', 'after' => ':time পরে', 'before' => ':time আগে', 'diff_now' => 'à¦à¦–ন', 'diff_yesterday' => 'গতকাল', 'diff_tomorrow' => 'আগামীকাল', 'period_recurrences' => ':count বার|:count বার', 'period_interval' => 'পà§à¦°à¦¤à¦¿ :interval', 'period_start_date' => ':date থেকে', 'period_end_date' => ':date পরà§à¦¯à¦¨à§à¦¤'); diff --git a/classes/Utilities/Misc/Carbon/Lang/bs_BA.php b/classes/Utilities/Misc/Carbon/Lang/bs_BA.php new file mode 100755 index 00000000..e5c21657 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/bs_BA.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count godina|:count godine|:count godina', 'y' => ':count godina|:count godine|:count godina', 'month' => ':count mjesec|:count mjeseca|:count mjeseci', 'm' => ':count mjesec|:count mjeseca|:count mjeseci', 'week' => ':count nedjelja|:count nedjelje|:count nedjelja', 'w' => ':count nedjelja|:count nedjelje|:count nedjelja', 'day' => ':count dan|:count dana|:count dana', 'd' => ':count dan|:count dana|:count dana', 'hour' => ':count sat|:count sata|:count sati', 'h' => ':count sat|:count sata|:count sati', 'minute' => ':count minut|:count minuta|:count minuta', 'min' => ':count minut|:count minuta|:count minuta', 'second' => ':count sekund|:count sekunda|:count sekundi', 's' => ':count sekund|:count sekunda|:count sekundi', 'ago' => 'prije :time', 'from_now' => 'za :time', 'after' => 'nakon :time', 'before' => ':time ranije'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ca.php b/classes/Utilities/Misc/Carbon/Lang/ca.php new file mode 100755 index 00000000..9f4634dc --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ca.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count any|:count anys', 'y' => ':count any|:count anys', 'month' => ':count mes|:count mesos', 'm' => ':count mes|:count mesos', 'week' => ':count setmana|:count setmanes', 'w' => ':count setmana|:count setmanes', 'day' => ':count dia|:count dies', 'd' => ':count dia|:count dies', 'hour' => ':count hora|:count hores', 'h' => ':count hora|:count hores', 'minute' => ':count minut|:count minuts', 'min' => ':count minut|:count minuts', 'second' => ':count segon|:count segons', 's' => ':count segon|:count segons', 'ago' => 'fa :time', 'from_now' => 'd\'aquí :time', 'after' => ':time després', 'before' => ':time abans', 'diff_now' => 'ara mateix', 'diff_yesterday' => 'ahir', 'diff_tomorrow' => 'demà', 'diff_before_yesterday' => "abans d'ahir", 'diff_after_tomorrow' => 'demà passat', 'period_recurrences' => ':count cop|:count cops', 'period_interval' => 'cada :interval', 'period_start_date' => 'de :date', 'period_end_date' => 'fins a :date'); diff --git a/classes/Utilities/Misc/Carbon/Lang/cs.php b/classes/Utilities/Misc/Carbon/Lang/cs.php new file mode 100755 index 00000000..fc540d6a --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/cs.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count rok|:count roky|:count let', 'y' => ':count rok|:count roky|:count let', 'month' => ':count mÄ›síc|:count mÄ›síce|:count mÄ›síců', 'm' => ':count mÄ›síc|:count mÄ›síce|:count mÄ›síců', 'week' => ':count týden|:count týdny|:count týdnů', 'w' => ':count týden|:count týdny|:count týdnů', 'day' => ':count den|:count dny|:count dní', 'd' => ':count den|:count dny|:count dní', 'hour' => ':count hodinu|:count hodiny|:count hodin', 'h' => ':count hodinu|:count hodiny|:count hodin', 'minute' => ':count minutu|:count minuty|:count minut', 'min' => ':count minutu|:count minuty|:count minut', 'second' => ':count sekundu|:count sekundy|:count sekund', 's' => ':count sekundu|:count sekundy|:count sekund', 'ago' => ':time nazpÄ›t', 'from_now' => 'za :time', 'after' => ':time pozdÄ›ji', 'before' => ':time pÅ™edtím'); diff --git a/classes/Utilities/Misc/Carbon/Lang/cy.php b/classes/Utilities/Misc/Carbon/Lang/cy.php new file mode 100755 index 00000000..495c0aa9 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/cy.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '1 flwyddyn|:count blynedd', 'y' => ':countbl', 'month' => '1 mis|:count fis', 'm' => ':countmi', 'week' => ':count wythnos', 'w' => ':countw', 'day' => ':count diwrnod', 'd' => ':countd', 'hour' => ':count awr', 'h' => ':counth', 'minute' => ':count munud', 'min' => ':countm', 'second' => ':count eiliad', 's' => ':counts', 'ago' => ':time yn ôl', 'from_now' => ':time o hyn ymlaen', 'after' => ':time ar ôl', 'before' => ':time o\'r blaen'); diff --git a/classes/Utilities/Misc/Carbon/Lang/da.php b/classes/Utilities/Misc/Carbon/Lang/da.php new file mode 100755 index 00000000..d8744c5d --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/da.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count Ã¥r|:count Ã¥r', 'y' => ':count Ã¥r|:count Ã¥r', 'month' => ':count mÃ¥ned|:count mÃ¥neder', 'm' => ':count mÃ¥ned|:count mÃ¥neder', 'week' => ':count uge|:count uger', 'w' => ':count uge|:count uger', 'day' => ':count dag|:count dage', 'd' => ':count dag|:count dage', 'hour' => ':count time|:count timer', 'h' => ':count time|:count timer', 'minute' => ':count minut|:count minutter', 'min' => ':count minut|:count minutter', 'second' => ':count sekund|:count sekunder', 's' => ':count sekund|:count sekunder', 'ago' => ':time siden', 'from_now' => 'om :time', 'after' => ':time efter', 'before' => ':time før'); diff --git a/classes/Utilities/Misc/Carbon/Lang/de.php b/classes/Utilities/Misc/Carbon/Lang/de.php new file mode 100755 index 00000000..56cc8033 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/de.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count Jahr|:count Jahre', 'y' => ':countJ|:countJ', 'month' => ':count Monat|:count Monate', 'm' => ':countMon|:countMon', 'week' => ':count Woche|:count Wochen', 'w' => ':countWo|:countWo', 'day' => ':count Tag|:count Tage', 'd' => ':countTg|:countTg', 'hour' => ':count Stunde|:count Stunden', 'h' => ':countStd|:countStd', 'minute' => ':count Minute|:count Minuten', 'min' => ':countMin|:countMin', 'second' => ':count Sekunde|:count Sekunden', 's' => ':countSek|:countSek', 'ago' => 'vor :time', 'from_now' => 'in :time', 'after' => ':time später', 'before' => ':time zuvor', 'year_from_now' => ':count Jahr|:count Jahren', 'month_from_now' => ':count Monat|:count Monaten', 'week_from_now' => ':count Woche|:count Wochen', 'day_from_now' => ':count Tag|:count Tagen', 'year_ago' => ':count Jahr|:count Jahren', 'month_ago' => ':count Monat|:count Monaten', 'week_ago' => ':count Woche|:count Wochen', 'day_ago' => ':count Tag|:count Tagen', 'diff_now' => 'Gerade eben', 'diff_yesterday' => 'Gestern', 'diff_tomorrow' => 'Heute', 'diff_before_yesterday' => 'Vorgestern', 'diff_after_tomorrow' => 'Ãœbermorgen'); diff --git a/classes/Utilities/Misc/Carbon/Lang/dv_MV.php b/classes/Utilities/Misc/Carbon/Lang/dv_MV.php new file mode 100755 index 00000000..8840a608 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/dv_MV.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '{0}Þ‡Þ¦Þ€Þ¦ÞƒÞ¬Þ‡Þ°|[1,Inf]:count Þ‡Þ¦Þ€Þ¦ÞƒÞª', 'y' => '{0}Þ‡Þ¦Þ€Þ¦ÞƒÞ¬Þ‡Þ°|[1,Inf]:count Þ‡Þ¦Þ€Þ¦ÞƒÞª', 'month' => '{0}Þ‰Þ¦Þ‡Þ°ÞÞ¦ÞƒÞ¬Þ‡Þ°|[1,Inf]:count Þ‰Þ¦ÞÞ°', 'm' => '{0}Þ‰Þ¦Þ‡Þ°ÞÞ¦ÞƒÞ¬Þ‡Þ°|[1,Inf]:count Þ‰Þ¦ÞÞ°', 'week' => '{0}ހަފްތާއެއް|[1,Inf]:count ހަފްތާ', 'w' => '{0}ހަފްތާއެއް|[1,Inf]:count ހަފްތާ', 'day' => '{0}Þ‹ÞªÞˆÞ¦ÞÞ°|[1,Inf]:count Þ‹ÞªÞˆÞ¦ÞÞ°', 'd' => '{0}Þ‹ÞªÞˆÞ¦ÞÞ°|[1,Inf]:count Þ‹ÞªÞˆÞ¦ÞÞ°', 'hour' => '{0}ÞŽÞ¦Þ‘Þ¨Þ‡Þ¨ÞƒÞ¬Þ‡Þ°|[1,Inf]:count ÞŽÞ¦Þ‘Þ¨', 'h' => '{0}ÞŽÞ¦Þ‘Þ¨Þ‡Þ¨ÞƒÞ¬Þ‡Þ°|[1,Inf]:count ÞŽÞ¦Þ‘Þ¨', 'minute' => '{0}Þ‰Þ¨Þ‚Þ¬Þ“Þ¬Þ‡Þ°|[1,Inf]:count Þ‰Þ¨Þ‚Þ¬Þ“Þ°', 'min' => '{0}Þ‰Þ¨Þ‚Þ¬Þ“Þ¬Þ‡Þ°|[1,Inf]:count Þ‰Þ¨Þ‚Þ¬Þ“Þ°', 'second' => '{0}Þިކުންތެއް|[1,Inf]:count Þިކުންތު', 's' => '{0}Þިކުންތެއް|[1,Inf]:count Þިކުންތު', 'ago' => ':time Þ†ÞªÞƒÞ¨Þ‚Þ°', 'from_now' => ':time ÞŠÞ¦Þ€ÞªÞ‚Þ°', 'after' => ':time ÞŠÞ¦Þ€ÞªÞ‚Þ°', 'before' => ':time Þ†ÞªÞƒÞ¨'); diff --git a/classes/Utilities/Misc/Carbon/Lang/el.php b/classes/Utilities/Misc/Carbon/Lang/el.php new file mode 100755 index 00000000..0f0454c7 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/el.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count χÏόνος|:count χÏόνια', 'y' => ':count χÏόνος|:count χÏόνια', 'month' => ':count μήνας|:count μήνες', 'm' => ':count μήνας|:count μήνες', 'week' => ':count εβδομάδα|:count εβδομάδες', 'w' => ':count εβδομάδα|:count εβδομάδες', 'day' => ':count μέÏα|:count μέÏες', 'd' => ':count μέÏα|:count μέÏες', 'hour' => ':count ÏŽÏα|:count ÏŽÏες', 'h' => ':count ÏŽÏα|:count ÏŽÏες', 'minute' => ':count λεπτό|:count λεπτά', 'min' => ':count λεπτό|:count λεπτά', 'second' => ':count δευτεÏόλεπτο|:count δευτεÏόλεπτα', 's' => ':count δευτεÏόλεπτο|:count δευτεÏόλεπτα', 'ago' => 'Ï€Ïιν από :time', 'from_now' => 'σε :time από Ï„ÏŽÏα', 'after' => ':time μετά', 'before' => ':time Ï€Ïιν'); diff --git a/classes/Utilities/Misc/Carbon/Lang/en.php b/classes/Utilities/Misc/Carbon/Lang/en.php new file mode 100755 index 00000000..b6981fda --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/en.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count year|:count years', 'y' => ':countyr|:countyrs', 'month' => ':count month|:count months', 'm' => ':countmo|:countmos', 'week' => ':count week|:count weeks', 'w' => ':countw|:countw', 'day' => ':count day|:count days', 'd' => ':countd|:countd', 'hour' => ':count hour|:count hours', 'h' => ':counth|:counth', 'minute' => ':count minute|:count minutes', 'min' => ':countm|:countm', 'second' => ':count second|:count seconds', 's' => ':counts|:counts', 'ago' => ':time ago', 'from_now' => ':time from now', 'after' => ':time after', 'before' => ':time before', 'diff_now' => 'just now', 'diff_yesterday' => 'yesterday', 'diff_tomorrow' => 'tomorrow', 'diff_before_yesterday' => 'before yesterday', 'diff_after_tomorrow' => 'after tomorrow', 'period_recurrences' => 'once|:count times', 'period_interval' => 'every :interval', 'period_start_date' => 'from :date', 'period_end_date' => 'to :date'); diff --git a/classes/Utilities/Misc/Carbon/Lang/eo.php b/classes/Utilities/Misc/Carbon/Lang/eo.php new file mode 100755 index 00000000..6d389c3d --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/eo.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count jaro|:count jaroj', 'y' => ':count jaro|:count jaroj', 'month' => ':count monato|:count monatoj', 'm' => ':count monato|:count monatoj', 'week' => ':count semajno|:count semajnoj', 'w' => ':count semajno|:count semajnoj', 'day' => ':count tago|:count tagoj', 'd' => ':count tago|:count tagoj', 'hour' => ':count horo|:count horoj', 'h' => ':count horo|:count horoj', 'minute' => ':count minuto|:count minutoj', 'min' => ':count minuto|:count minutoj', 'second' => ':count sekundo|:count sekundoj', 's' => ':count sekundo|:count sekundoj', 'ago' => 'antaÅ­ :time', 'from_now' => 'je :time', 'after' => ':time poste', 'before' => ':time antaÅ­e'); diff --git a/classes/Utilities/Misc/Carbon/Lang/es.php b/classes/Utilities/Misc/Carbon/Lang/es.php new file mode 100755 index 00000000..d8d5984b --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/es.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count año|:count años', 'y' => ':count año|:count años', 'month' => ':count mes|:count meses', 'm' => ':count mes|:count meses', 'week' => ':count semana|:count semanas', 'w' => ':count semana|:count semanas', 'day' => ':count día|:count días', 'd' => ':count día|:count días', 'hour' => ':count hora|:count horas', 'h' => ':count hora|:count horas', 'minute' => ':count minuto|:count minutos', 'min' => ':count minuto|:count minutos', 'second' => ':count segundo|:count segundos', 's' => ':count segundo|:count segundos', 'ago' => 'hace :time', 'from_now' => 'dentro de :time', 'after' => ':time después', 'before' => ':time antes', 'diff_now' => 'ahora mismo', 'diff_yesterday' => 'ayer', 'diff_tomorrow' => 'mañana', 'diff_before_yesterday' => 'antier', 'diff_after_tomorrow' => 'pasado mañana'); diff --git a/classes/Utilities/Misc/Carbon/Lang/et.php b/classes/Utilities/Misc/Carbon/Lang/et.php new file mode 100755 index 00000000..4484982a --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/et.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count aasta|:count aastat', 'y' => ':count aasta|:count aastat', 'month' => ':count kuu|:count kuud', 'm' => ':count kuu|:count kuud', 'week' => ':count nädal|:count nädalat', 'w' => ':count nädal|:count nädalat', 'day' => ':count päev|:count päeva', 'd' => ':count päev|:count päeva', 'hour' => ':count tund|:count tundi', 'h' => ':count tund|:count tundi', 'minute' => ':count minut|:count minutit', 'min' => ':count minut|:count minutit', 'second' => ':count sekund|:count sekundit', 's' => ':count sekund|:count sekundit', 'ago' => ':time tagasi', 'from_now' => ':time pärast', 'after' => ':time pärast', 'before' => ':time enne', 'year_from_now' => ':count aasta', 'month_from_now' => ':count kuu', 'week_from_now' => ':count nädala', 'day_from_now' => ':count päeva', 'hour_from_now' => ':count tunni', 'minute_from_now' => ':count minuti', 'second_from_now' => ':count sekundi'); diff --git a/classes/Utilities/Misc/Carbon/Lang/eu.php b/classes/Utilities/Misc/Carbon/Lang/eu.php new file mode 100755 index 00000000..24e3e209 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/eu.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => 'Urte 1|:count urte', 'y' => 'Urte 1|:count urte', 'month' => 'Hile 1|:count hile', 'm' => 'Hile 1|:count hile', 'week' => 'Aste 1|:count aste', 'w' => 'Aste 1|:count aste', 'day' => 'Egun 1|:count egun', 'd' => 'Egun 1|:count egun', 'hour' => 'Ordu 1|:count ordu', 'h' => 'Ordu 1|:count ordu', 'minute' => 'Minutu 1|:count minutu', 'min' => 'Minutu 1|:count minutu', 'second' => 'Segundu 1|:count segundu', 's' => 'Segundu 1|:count segundu', 'ago' => 'Orain dela :time', 'from_now' => ':time barru', 'after' => ':time geroago', 'before' => ':time lehenago'); diff --git a/classes/Utilities/Misc/Carbon/Lang/fa.php b/classes/Utilities/Misc/Carbon/Lang/fa.php new file mode 100755 index 00000000..0cb33116 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/fa.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count سال', 'y' => ':count سال', 'month' => ':count ماه', 'm' => ':count ماه', 'week' => ':count Ù‡Ùته', 'w' => ':count Ù‡Ùته', 'day' => ':count روز', 'd' => ':count روز', 'hour' => ':count ساعت', 'h' => ':count ساعت', 'minute' => ':count دقیقه', 'min' => ':count دقیقه', 'second' => ':count ثانیه', 's' => ':count ثانیه', 'ago' => ':time پیش', 'from_now' => ':time بعد', 'after' => ':time پس از', 'before' => ':time پیش از'); diff --git a/classes/Utilities/Misc/Carbon/Lang/fi.php b/classes/Utilities/Misc/Carbon/Lang/fi.php new file mode 100755 index 00000000..5a4efe14 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/fi.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count vuosi|:count vuotta', 'y' => ':count vuosi|:count vuotta', 'month' => ':count kuukausi|:count kuukautta', 'm' => ':count kuukausi|:count kuukautta', 'week' => ':count viikko|:count viikkoa', 'w' => ':count viikko|:count viikkoa', 'day' => ':count päivä|:count päivää', 'd' => ':count päivä|:count päivää', 'hour' => ':count tunti|:count tuntia', 'h' => ':count tunti|:count tuntia', 'minute' => ':count minuutti|:count minuuttia', 'min' => ':count minuutti|:count minuuttia', 'second' => ':count sekunti|:count sekuntia', 's' => ':count sekunti|:count sekuntia', 'ago' => ':time sitten', 'from_now' => ':time tästä hetkestä', 'after' => ':time sen jälkeen', 'before' => ':time ennen'); diff --git a/classes/Utilities/Misc/Carbon/Lang/fo.php b/classes/Utilities/Misc/Carbon/Lang/fo.php new file mode 100755 index 00000000..08dbcf9b --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/fo.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count ár|:count ár', 'y' => ':count ár|:count ár', 'month' => ':count mánaður|:count mánaðir', 'm' => ':count mánaður|:count mánaðir', 'week' => ':count vika|:count vikur', 'w' => ':count vika|:count vikur', 'day' => ':count dag|:count dagar', 'd' => ':count dag|:count dagar', 'hour' => ':count tími|:count tímar', 'h' => ':count tími|:count tímar', 'minute' => ':count minutt|:count minuttir', 'min' => ':count minutt|:count minuttir', 'second' => ':count sekund|:count sekundir', 's' => ':count sekund|:count sekundir', 'ago' => ':time síðan', 'from_now' => 'um :time', 'after' => ':time aftaná', 'before' => ':time áðrenn'); diff --git a/classes/Utilities/Misc/Carbon/Lang/fr.php b/classes/Utilities/Misc/Carbon/Lang/fr.php new file mode 100755 index 00000000..75608259 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/fr.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count an|:count ans', 'y' => ':count an|:count ans', 'month' => ':count mois', 'm' => ':count mois', 'week' => ':count semaine|:count semaines', 'w' => ':count sem.', 'day' => ':count jour|:count jours', 'd' => ':count j.', 'hour' => ':count heure|:count heures', 'h' => ':count h.', 'minute' => ':count minute|:count minutes', 'min' => ':count min.', 'second' => ':count seconde|:count secondes', 's' => ':count sec.', 'ago' => 'il y a :time', 'from_now' => 'dans :time', 'after' => ':time après', 'before' => ':time avant', 'diff_now' => "à l'instant", 'diff_yesterday' => 'hier', 'diff_tomorrow' => 'demain', 'diff_before_yesterday' => 'avant-hier', 'diff_after_tomorrow' => 'après-demain', 'period_recurrences' => ':count fois', 'period_interval' => 'tous les :interval', 'period_start_date' => 'de :date', 'period_end_date' => 'à :date'); diff --git a/classes/Utilities/Misc/Carbon/Lang/gl.php b/classes/Utilities/Misc/Carbon/Lang/gl.php new file mode 100755 index 00000000..9d44e257 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/gl.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count ano|:count anos', 'month' => ':count mes|:count meses', 'week' => ':count semana|:count semanas', 'day' => ':count día|:count días', 'hour' => ':count hora|:count horas', 'minute' => ':count minuto|:count minutos', 'second' => ':count segundo|:count segundos', 'ago' => 'fai :time', 'from_now' => 'dentro de :time', 'after' => ':time despois', 'before' => ':time antes'); diff --git a/classes/Utilities/Misc/Carbon/Lang/gu.php b/classes/Utilities/Misc/Carbon/Lang/gu.php new file mode 100755 index 00000000..ce6a6edb --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/gu.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count વરà«àª·|:count વરà«àª·à«‹', 'y' => ':countવરà«àª·|:countવરà«àª·à«‹', 'month' => ':count મહિનો|:count મહિના', 'm' => ':countમહિનો|:countમહિના', 'week' => ':count અઠવાડિયà«àª‚|:count અઠવાડિયા', 'w' => ':countઅઠ.|:countઅઠ.', 'day' => ':count દિવસ|:count દિવસો', 'd' => ':countદિ.|:countદિ.', 'hour' => ':count કલાક|:count કલાકો', 'h' => ':countક.|:countક.', 'minute' => ':count મિનિટ|:count મિનિટ', 'min' => ':countમિ.|:countમિ.', 'second' => ':count સેકેનà«àª¡|:count સેકેનà«àª¡', 's' => ':countસે.|:countસે.', 'ago' => ':time પહેલા', 'from_now' => ':time અતà«àª¯àª¾àª°àª¥à«€', 'after' => ':time પછી', 'before' => ':time પહેલા'); diff --git a/classes/Utilities/Misc/Carbon/Lang/he.php b/classes/Utilities/Misc/Carbon/Lang/he.php new file mode 100755 index 00000000..7b03299c --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/he.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => 'שנה|{2}שנתיי×|:count שני×', 'y' => 'שנה|{2}שנתיי×|:count שני×', 'month' => 'חודש|{2}חודשיי×|:count חודשי×', 'm' => 'חודש|{2}חודשיי×|:count חודשי×', 'week' => 'שבוע|{2}שבועיי×|:count שבועות', 'w' => 'שבוע|{2}שבועיי×|:count שבועות', 'day' => 'יו×|{2}יומיי×|:count ימי×', 'd' => 'יו×|{2}יומיי×|:count ימי×', 'hour' => 'שעה|{2}שעתיי×|:count שעות', 'h' => 'שעה|{2}שעתיי×|:count שעות', 'minute' => 'דקה|{2}דקותיי×|:count דקות', 'min' => 'דקה|{2}דקותיי×|:count דקות', 'second' => 'שניה|:count שניות', 's' => 'שניה|:count שניות', 'ago' => 'לפני :time', 'from_now' => 'בעוד :time', 'after' => '×חרי :time', 'before' => 'לפני :time'); diff --git a/classes/Utilities/Misc/Carbon/Lang/hi.php b/classes/Utilities/Misc/Carbon/Lang/hi.php new file mode 100755 index 00000000..ad7fa492 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/hi.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '1 वरà¥à¤·|:count वरà¥à¤·à¥‹à¤‚', 'y' => '1 वरà¥à¤·|:count वरà¥à¤·à¥‹à¤‚', 'month' => '1 माह|:count महीने', 'm' => '1 माह|:count महीने', 'week' => '1 सपà¥à¤¤à¤¾à¤¹|:count सपà¥à¤¤à¤¾à¤¹', 'w' => '1 सपà¥à¤¤à¤¾à¤¹|:count सपà¥à¤¤à¤¾à¤¹', 'day' => '1 दिन|:count दिनों', 'd' => '1 दिन|:count दिनों', 'hour' => '1 घंटा|:count घंटे', 'h' => '1 घंटा|:count घंटे', 'minute' => '1 मिनट|:count मिनटों', 'min' => '1 मिनट|:count मिनटों', 'second' => '1 सेकंड|:count सेकंड', 's' => '1 सेकंड|:count सेकंड', 'ago' => ':time पूरà¥à¤µ', 'from_now' => ':time से', 'after' => ':time के बाद', 'before' => ':time के पहले'); diff --git a/classes/Utilities/Misc/Carbon/Lang/hr.php b/classes/Utilities/Misc/Carbon/Lang/hr.php new file mode 100755 index 00000000..404e9737 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/hr.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count godinu|:count godine|:count godina', 'y' => ':count godinu|:count godine|:count godina', 'month' => ':count mjesec|:count mjeseca|:count mjeseci', 'm' => ':count mjesec|:count mjeseca|:count mjeseci', 'week' => ':count tjedan|:count tjedna|:count tjedana', 'w' => ':count tjedan|:count tjedna|:count tjedana', 'day' => ':count dan|:count dana|:count dana', 'd' => ':count dan|:count dana|:count dana', 'hour' => ':count sat|:count sata|:count sati', 'h' => ':count sat|:count sata|:count sati', 'minute' => ':count minutu|:count minute |:count minuta', 'min' => ':count minutu|:count minute |:count minuta', 'second' => ':count sekundu|:count sekunde|:count sekundi', 's' => ':count sekundu|:count sekunde|:count sekundi', 'ago' => 'prije :time', 'from_now' => 'za :time', 'after' => 'za :time', 'before' => 'prije :time'); diff --git a/classes/Utilities/Misc/Carbon/Lang/hu.php b/classes/Utilities/Misc/Carbon/Lang/hu.php new file mode 100755 index 00000000..a63c3a61 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/hu.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count év', 'y' => ':count év', 'month' => ':count hónap', 'm' => ':count hónap', 'week' => ':count hét', 'w' => ':count hét', 'day' => ':count nap', 'd' => ':count nap', 'hour' => ':count óra', 'h' => ':count óra', 'minute' => ':count perc', 'min' => ':count perc', 'second' => ':count másodperc', 's' => ':count másodperc', 'ago' => ':time', 'from_now' => ':time múlva', 'after' => ':time késÅ‘bb', 'before' => ':time korábban', 'year_ago' => ':count éve', 'month_ago' => ':count hónapja', 'week_ago' => ':count hete', 'day_ago' => ':count napja', 'hour_ago' => ':count órája', 'minute_ago' => ':count perce', 'second_ago' => ':count másodperce', 'year_after' => ':count évvel', 'month_after' => ':count hónappal', 'week_after' => ':count héttel', 'day_after' => ':count nappal', 'hour_after' => ':count órával', 'minute_after' => ':count perccel', 'second_after' => ':count másodperccel', 'year_before' => ':count évvel', 'month_before' => ':count hónappal', 'week_before' => ':count héttel', 'day_before' => ':count nappal', 'hour_before' => ':count órával', 'minute_before' => ':count perccel', 'second_before' => ':count másodperccel'); diff --git a/classes/Utilities/Misc/Carbon/Lang/hy.php b/classes/Utilities/Misc/Carbon/Lang/hy.php new file mode 100755 index 00000000..b97f2bc8 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/hy.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count Õ¿Õ¡Ö€Õ«', 'y' => ':countÕ¿', 'month' => ':count Õ¡Õ´Õ«Õ½', 'm' => ':countÕ¡Õ´', 'week' => ':count Õ·Õ¡Õ¢Õ¡Õ©', 'w' => ':countÕ·', 'day' => ':count Ö…Ö€', 'd' => ':countÖ…Ö€', 'hour' => ':count ÕªÕ¡Õ´', 'h' => ':countÕª', 'minute' => ':count Ö€Õ¸ÕºÕ¥', 'min' => ':countÖ€', 'second' => ':count Õ¾Õ¡Ö€Õ¯ÕµÕ¡Õ¶', 's' => ':countÕ¾Ö€Õ¯', 'ago' => ':time Õ¡Õ¼Õ¡Õ»', 'from_now' => ':time Õ¶Õ¥Ö€Õ¯Õ¡ ÕºÕ¡Õ°Õ«Ö', 'after' => ':time Õ°Õ¥Õ¿Õ¸', 'before' => ':time Õ¡Õ¼Õ¡Õ»'); diff --git a/classes/Utilities/Misc/Carbon/Lang/id.php b/classes/Utilities/Misc/Carbon/Lang/id.php new file mode 100755 index 00000000..67cef284 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/id.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count tahun', 'y' => ':count tahun', 'month' => ':count bulan', 'm' => ':count bulan', 'week' => ':count minggu', 'w' => ':count minggu', 'day' => ':count hari', 'd' => ':count hari', 'hour' => ':count jam', 'h' => ':count jam', 'minute' => ':count menit', 'min' => ':count menit', 'second' => ':count detik', 's' => ':count detik', 'ago' => ':time yang lalu', 'from_now' => ':time dari sekarang', 'after' => ':time setelah', 'before' => ':time sebelum'); diff --git a/classes/Utilities/Misc/Carbon/Lang/is.php b/classes/Utilities/Misc/Carbon/Lang/is.php new file mode 100755 index 00000000..fb909605 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/is.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '1 ár|:count ár', 'y' => '1 ár|:count ár', 'month' => '1 mánuður|:count mánuðir', 'm' => '1 mánuður|:count mánuðir', 'week' => '1 vika|:count vikur', 'w' => '1 vika|:count vikur', 'day' => '1 dagur|:count dagar', 'd' => '1 dagur|:count dagar', 'hour' => '1 klukkutími|:count klukkutímar', 'h' => '1 klukkutími|:count klukkutímar', 'minute' => '1 mínúta|:count mínútur', 'min' => '1 mínúta|:count mínútur', 'second' => '1 sekúnda|:count sekúndur', 's' => '1 sekúnda|:count sekúndur', 'ago' => ':time síðan', 'from_now' => ':time síðan', 'after' => ':time eftir', 'before' => ':time fyrir'); diff --git a/classes/Utilities/Misc/Carbon/Lang/it.php b/classes/Utilities/Misc/Carbon/Lang/it.php new file mode 100755 index 00000000..fcfe0071 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/it.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count anno|:count anni', 'y' => ':count anno|:count anni', 'month' => ':count mese|:count mesi', 'm' => ':count mese|:count mesi', 'week' => ':count settimana|:count settimane', 'w' => ':count settimana|:count settimane', 'day' => ':count giorno|:count giorni', 'd' => ':count giorno|:count giorni', 'hour' => ':count ora|:count ore', 'h' => ':count ora|:count ore', 'minute' => ':count minuto|:count minuti', 'min' => ':count minuto|:count minuti', 'second' => ':count secondo|:count secondi', 's' => ':count secondo|:count secondi', 'ago' => ':time fa', 'from_now' => 'tra :time', 'after' => ':time dopo', 'before' => ':time prima', 'diff_now' => 'proprio ora', 'diff_yesterday' => 'ieri', 'diff_tomorrow' => 'domani', 'diff_before_yesterday' => "l'altro ieri", 'diff_after_tomorrow' => 'dopodomani'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ja.php b/classes/Utilities/Misc/Carbon/Lang/ja.php new file mode 100755 index 00000000..1f5a5159 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ja.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':countå¹´', 'y' => ':countå¹´', 'month' => ':countヶ月', 'm' => ':countヶ月', 'week' => ':count週間', 'w' => ':count週間', 'day' => ':countæ—¥', 'd' => ':countæ—¥', 'hour' => ':count時間', 'h' => ':count時間', 'minute' => ':count分', 'min' => ':count分', 'second' => ':count秒', 's' => ':count秒', 'ago' => ':timeå‰', 'from_now' => '今ã‹ã‚‰:time', 'after' => ':time後', 'before' => ':timeå‰'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ka.php b/classes/Utilities/Misc/Carbon/Lang/ka.php new file mode 100755 index 00000000..65280099 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ka.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count წლის', 'y' => ':count წლის', 'month' => ':count თვის', 'm' => ':count თვის', 'week' => ':count კვირის', 'w' => ':count კვირის', 'day' => ':count დღის', 'd' => ':count დღის', 'hour' => ':count სáƒáƒáƒ—ის', 'h' => ':count სáƒáƒáƒ—ის', 'minute' => ':count წუთის', 'min' => ':count წუთის', 'second' => ':count წáƒáƒ›áƒ˜áƒ¡', 's' => ':count წáƒáƒ›áƒ˜áƒ¡', 'ago' => ':time უკáƒáƒœ', 'from_now' => ':time შემდეგ', 'after' => ':time შემდეგ', 'before' => ':time უკáƒáƒœ'); diff --git a/classes/Utilities/Misc/Carbon/Lang/kk.php b/classes/Utilities/Misc/Carbon/Lang/kk.php new file mode 100755 index 00000000..c044154a --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/kk.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count жыл', 'y' => ':count жыл', 'month' => ':count ай', 'm' => ':count ай', 'week' => ':count апта', 'w' => ':count апта', 'day' => ':count күн', 'd' => ':count күн', 'hour' => ':count Ñағат', 'h' => ':count Ñағат', 'minute' => ':count минут', 'min' => ':count минут', 'second' => ':count Ñекунд', 's' => ':count Ñекунд', 'ago' => ':time бұрын', 'from_now' => ':time кейін', 'after' => ':time кейін', 'before' => ':time бұрын'); diff --git a/classes/Utilities/Misc/Carbon/Lang/km.php b/classes/Utilities/Misc/Carbon/Lang/km.php new file mode 100755 index 00000000..b07fbfa5 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/km.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count ឆ្នាំ', 'y' => ':count ឆ្នាំ', 'month' => ':count ážáŸ‚', 'm' => ':count ážáŸ‚', 'week' => ':count សប្ដាហáŸ', 'w' => ':count សប្ដាហáŸ', 'day' => ':count ážáŸ’ងៃ', 'd' => ':count ážáŸ’ងៃ', 'hour' => ':count ម៉ោង', 'h' => ':count ម៉ោង', 'minute' => ':count នាទី', 'min' => ':count នាទី', 'second' => ':count វិនាទី', 's' => ':count វិនាទី', 'ago' => ':timeមុន', 'from_now' => ':timeពី​ឥឡូវ', 'after' => 'នៅ​ក្រោយ :time', 'before' => 'នៅ​មុន :time'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ko.php b/classes/Utilities/Misc/Carbon/Lang/ko.php new file mode 100755 index 00000000..13609a24 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ko.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count ë…„', 'y' => ':count ë…„', 'month' => ':count 개월', 'm' => ':count 개월', 'week' => ':count 주ì¼', 'w' => ':count 주ì¼', 'day' => ':count ì¼', 'd' => ':count ì¼', 'hour' => ':count 시간', 'h' => ':count 시간', 'minute' => ':count 분', 'min' => ':count 분', 'second' => ':count ì´ˆ', 's' => ':count ì´ˆ', 'ago' => ':time ì „', 'from_now' => ':time 후', 'after' => ':time ì´í›„', 'before' => ':time ì´ì „'); diff --git a/classes/Utilities/Misc/Carbon/Lang/lt.php b/classes/Utilities/Misc/Carbon/Lang/lt.php new file mode 100755 index 00000000..9f728757 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/lt.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count metus|:count metus|:count metų', 'y' => ':count metus|:count metus|:count metų', 'month' => ':count mÄ—nesį|:count mÄ—nesius|:count mÄ—nesių', 'm' => ':count mÄ—nesį|:count mÄ—nesius|:count mÄ—nesių', 'week' => ':count savaitÄ™|:count savaites|:count savaiÄių', 'w' => ':count savaitÄ™|:count savaites|:count savaiÄių', 'day' => ':count dienÄ…|:count dienas|:count dienų', 'd' => ':count dienÄ…|:count dienas|:count dienų', 'hour' => ':count valandÄ…|:count valandas|:count valandų', 'h' => ':count valandÄ…|:count valandas|:count valandų', 'minute' => ':count minutÄ™|:count minutes|:count minuÄių', 'min' => ':count minutÄ™|:count minutes|:count minuÄių', 'second' => ':count sekundÄ™|:count sekundes|:count sekundžių', 's' => ':count sekundÄ™|:count sekundes|:count sekundžių', 'second_from_now' => ':count sekundÄ—s|:count sekundžių|:count sekundžių', 'minute_from_now' => ':count minutÄ—s|:count minuÄių|:count minuÄių', 'hour_from_now' => ':count valandos|:count valandų|:count valandų', 'day_from_now' => ':count dienos|:count dienų|:count dienų', 'week_from_now' => ':count savaitÄ—s|:count savaiÄių|:count savaiÄių', 'month_from_now' => ':count mÄ—nesio|:count mÄ—nesių|:count mÄ—nesių', 'year_from_now' => ':count metų', 'ago' => 'prieÅ¡ :time', 'from_now' => 'už :time', 'after' => 'po :time', 'before' => ':time nuo dabar'); diff --git a/classes/Utilities/Misc/Carbon/Lang/lv.php b/classes/Utilities/Misc/Carbon/Lang/lv.php new file mode 100755 index 00000000..c42957c3 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/lv.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '0 gadiem|:count gada|:count gadiem', 'y' => '0 gadiem|:count gada|:count gadiem', 'month' => '0 mÄ“neÅ¡iem|:count mÄ“neÅ¡a|:count mÄ“neÅ¡iem', 'm' => '0 mÄ“neÅ¡iem|:count mÄ“neÅ¡a|:count mÄ“neÅ¡iem', 'week' => '0 nedēļÄm|:count nedēļas|:count nedēļÄm', 'w' => '0 nedēļÄm|:count nedēļas|:count nedēļÄm', 'day' => '0 dienÄm|:count dienas|:count dienÄm', 'd' => '0 dienÄm|:count dienas|:count dienÄm', 'hour' => '0 stundÄm|:count stundas|:count stundÄm', 'h' => '0 stundÄm|:count stundas|:count stundÄm', 'minute' => '0 minÅ«tÄ“m|:count minÅ«tes|:count minÅ«tÄ“m', 'min' => '0 minÅ«tÄ“m|:count minÅ«tes|:count minÅ«tÄ“m', 'second' => '0 sekundÄ“m|:count sekundes|:count sekundÄ“m', 's' => '0 sekundÄ“m|:count sekundes|:count sekundÄ“m', 'ago' => 'pirms :time', 'from_now' => 'pÄ“c :time', 'after' => ':time vÄ“lÄk', 'before' => ':time pirms', 'year_after' => '0 gadus|:count gadu|:count gadus', 'month_after' => '0 mÄ“neÅ¡us|:count mÄ“nesi|:count mÄ“neÅ¡us', 'week_after' => '0 nedēļas|:count nedēļu|:count nedēļas', 'day_after' => '0 dienas|:count dienu|:count dienas', 'hour_after' => '0 stundas|:count stundu|:count stundas', 'minute_after' => '0 minÅ«tes|:count minÅ«ti|:count minÅ«tes', 'second_after' => '0 sekundes|:count sekundi|:count sekundes', 'year_before' => '0 gadus|:count gadu|:count gadus', 'month_before' => '0 mÄ“neÅ¡us|:count mÄ“nesi|:count mÄ“neÅ¡us', 'week_before' => '0 nedēļas|:count nedēļu|:count nedēļas', 'day_before' => '0 dienas|:count dienu|:count dienas', 'hour_before' => '0 stundas|:count stundu|:count stundas', 'minute_before' => '0 minÅ«tes|:count minÅ«ti|:count minÅ«tes', 'second_before' => '0 sekundes|:count sekundi|:count sekundes'); diff --git a/classes/Utilities/Misc/Carbon/Lang/mk.php b/classes/Utilities/Misc/Carbon/Lang/mk.php new file mode 100755 index 00000000..515ce849 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/mk.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count година|:count години', 'month' => ':count меÑец|:count меÑеци', 'week' => ':count Ñедмица|:count Ñедмици', 'day' => ':count ден|:count дена', 'hour' => ':count чаÑ|:count чаÑа', 'minute' => ':count минута|:count минути', 'second' => ':count Ñекунда|:count Ñекунди', 'ago' => 'пред :time', 'from_now' => ':time од Ñега', 'after' => 'по :time', 'before' => 'пред :time'); diff --git a/classes/Utilities/Misc/Carbon/Lang/mn.php b/classes/Utilities/Misc/Carbon/Lang/mn.php new file mode 100755 index 00000000..3371d171 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/mn.php @@ -0,0 +1,60 @@ + + * + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @translator Batmandakh Erdenebileg + */ +return array( + 'year' => ':count жил', + 'y' => ':count жил', + 'month' => ':count Ñар', + 'm' => ':count Ñар', + 'week' => ':count долоо хоног', + 'w' => ':count долоо хоног', + 'day' => ':count өдөр', + 'd' => ':count өдөр', + 'hour' => ':count цаг', + 'h' => ':countц', + 'minute' => ':count минут', + 'min' => ':countм', + 'second' => ':count Ñекунд', + 's' => ':countÑ', + 'ago' => ':timeн өмнө', + 'year_ago' => ':count жилий', + 'month_ago' => ':count Ñары', + 'day_ago' => ':count хоногий', + 'hour_ago' => ':count цагий', + 'minute_ago' => ':count минуты', + 'second_ago' => ':count Ñекунды', + 'from_now' => 'Ð¾Ð´Ð¾Ð¾Ð³Ð¾Ð¾Ñ :time', + 'year_from_now' => ':count жилийн дараа', + 'month_from_now' => ':count Ñарын дараа', + 'day_from_now' => ':count хоногийн дараа', + 'hour_from_now' => ':count цагийн дараа', + 'minute_from_now' => ':count минутын дараа', + 'second_from_now' => ':count Ñекундын дараа', + // Does it required to make translation for before, after as follows? hmm, I think we've made it with ago and from now keywords already. Anyway, I've included it just in case of undesired action... + 'after' => ':timeн дараа', + 'year_after' => ':count жилий', + 'month_after' => ':count Ñары', + 'day_after' => ':count хоногий', + 'hour_after' => ':count цагий', + 'minute_after' => ':count минуты', + 'second_after' => ':count Ñекунды', + 'before' => ':timeн өмнө', + 'year_before' => ':count жилий', + 'month_before' => ':count Ñары', + 'day_before' => ':count хоногий', + 'hour_before' => ':count цагий', + 'minute_before' => ':count минуты', + 'second_before' => ':count Ñекунды', +); diff --git a/classes/Utilities/Misc/Carbon/Lang/ms.php b/classes/Utilities/Misc/Carbon/Lang/ms.php new file mode 100755 index 00000000..7f15cf72 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ms.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count tahun', 'y' => ':count tahun', 'month' => ':count bulan', 'm' => ':count bulan', 'week' => ':count minggu', 'w' => ':count minggu', 'day' => ':count hari', 'd' => ':count hari', 'hour' => ':count jam', 'h' => ':count jam', 'minute' => ':count minit', 'min' => ':count minit', 'second' => ':count saat', 's' => ':count saat', 'ago' => ':time yang lalu', 'from_now' => ':time dari sekarang', 'after' => ':time selepas', 'before' => ':time sebelum'); diff --git a/classes/Utilities/Misc/Carbon/Lang/my.php b/classes/Utilities/Misc/Carbon/Lang/my.php new file mode 100755 index 00000000..7f6cb5a3 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/my.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count နှစ်|:count နှစ်', 'y' => ':count နှစ်|:count နှစ်', 'month' => ':count လ|:count လ', 'm' => ':count လ|:count လ', 'week' => ':count ပá€á€º|:count ပá€á€º', 'w' => ':count ပá€á€º|:count ပá€á€º', 'day' => ':count ရက်|:count ရက်', 'd' => ':count ရက်|:count ရက်', 'hour' => ':count နာရီ|:count နာရီ', 'h' => ':count နာရီ|:count နာရီ', 'minute' => ':count မိနစ်|:count မိနစ်', 'min' => ':count မိနစ်|:count မိနစ်', 'second' => ':count စက္ကန့်|:count စက္ကန့်', 's' => ':count စက္ကန့်|:count စက္ကန့်', 'ago' => 'လွန်á€á€²á€·á€žá€±á€¬ :time က', 'from_now' => 'ယá€á€¯á€™á€¾á€…áနောက် :time အကြာ', 'after' => ':time ကြာပြီးနောက်', 'before' => ':time မá€á€­á€¯á€„်á€á€„်', 'diff_now' => 'အá€á€¯á€œá€±á€¸á€á€„်', 'diff_yesterday' => 'မနေ့က', 'diff_tomorrow' => 'မနက်ဖြန်', 'diff_before_yesterday' => 'á€á€™á€¼á€”်နေ့က', 'diff_after_tomorrow' => 'á€á€˜á€€á€ºá€á€«', 'period_recurrences' => ':count ကြိမ်'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ne.php b/classes/Utilities/Misc/Carbon/Lang/ne.php new file mode 100755 index 00000000..02f512e4 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ne.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count वरà¥à¤·', 'y' => ':count वरà¥à¤·', 'month' => ':count महिना', 'm' => ':count महिना', 'week' => ':count हपà¥à¤¤à¤¾', 'w' => ':count हपà¥à¤¤à¤¾', 'day' => ':count दिन', 'd' => ':count दिन', 'hour' => ':count घणà¥à¤Ÿà¤¾', 'h' => ':count घणà¥à¤Ÿà¤¾', 'minute' => ':count मिनेट', 'min' => ':count मिनेट', 'second' => ':count सेकेणà¥à¤¡', 's' => ':count सेकेणà¥à¤¡', 'ago' => ':time पहिले', 'from_now' => ':time देखि', 'after' => ':time पछि', 'before' => ':time अघि'); diff --git a/classes/Utilities/Misc/Carbon/Lang/nl.php b/classes/Utilities/Misc/Carbon/Lang/nl.php new file mode 100755 index 00000000..ce8afed8 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/nl.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count jaar', 'y' => ':count jaar', 'month' => ':count maand|:count maanden', 'm' => ':count maand|:count maanden', 'week' => ':count week|:count weken', 'w' => ':count week|:count weken', 'day' => ':count dag|:count dagen', 'd' => ':count dag|:count dagen', 'hour' => ':count uur', 'h' => ':count uur', 'minute' => ':count minuut|:count minuten', 'min' => ':count minuut|:count minuten', 'second' => ':count seconde|:count seconden', 's' => ':count seconde|:count seconden', 'ago' => ':time geleden', 'from_now' => 'over :time', 'after' => ':time later', 'before' => ':time eerder', 'diff_now' => 'nu', 'diff_yesterday' => 'gisteren', 'diff_tomorrow' => 'morgen', 'diff_after_tomorrow' => 'overmorgen', 'diff_before_yesterday' => 'eergisteren'); diff --git a/classes/Utilities/Misc/Carbon/Lang/no.php b/classes/Utilities/Misc/Carbon/Lang/no.php new file mode 100755 index 00000000..98313b94 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/no.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count Ã¥r|:count Ã¥r', 'y' => ':count Ã¥r|:count Ã¥r', 'month' => ':count mÃ¥ned|:count mÃ¥neder', 'm' => ':count mÃ¥ned|:count mÃ¥neder', 'week' => ':count uke|:count uker', 'w' => ':count uke|:count uker', 'day' => ':count dag|:count dager', 'd' => ':count dag|:count dager', 'hour' => ':count time|:count timer', 'h' => ':count time|:count timer', 'minute' => ':count minutt|:count minutter', 'min' => ':count minutt|:count minutter', 'second' => ':count sekund|:count sekunder', 's' => ':count sekund|:count sekunder', 'ago' => ':time siden', 'from_now' => 'om :time', 'after' => ':time etter', 'before' => ':time før', 'diff_now' => 'akkurat nÃ¥', 'diff_yesterday' => 'i gÃ¥r', 'diff_tomorrow' => 'i morgen', 'diff_before_yesterday' => 'i forgÃ¥rs', 'diff_after_tomorrow' => 'i overmorgen'); diff --git a/classes/Utilities/Misc/Carbon/Lang/oc.php b/classes/Utilities/Misc/Carbon/Lang/oc.php new file mode 100755 index 00000000..ecb0773f --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/oc.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\PluralizationRules::set(function ($number) { + return $number == 1 ? 0 : 1; +}, 'oc'); +return array('year' => ':count an|:count ans', 'y' => ':count an|:count ans', 'month' => ':count mes|:count meses', 'm' => ':count mes|:count meses', 'week' => ':count setmana|:count setmanas', 'w' => ':count setmana|:count setmanas', 'day' => ':count jorn|:count jorns', 'd' => ':count jorn|:count jorns', 'hour' => ':count ora|:count oras', 'h' => ':count ora|:count oras', 'minute' => ':count minuta|:count minutas', 'min' => ':count minuta|:count minutas', 'second' => ':count segonda|:count segondas', 's' => ':count segonda|:count segondas', 'ago' => 'fa :time', 'from_now' => 'dins :time', 'after' => ':time aprèp', 'before' => ':time abans', 'diff_now' => 'ara meteis', 'diff_yesterday' => 'ièr', 'diff_tomorrow' => 'deman', 'diff_before_yesterday' => 'ièr delà', 'diff_after_tomorrow' => 'deman passat', 'period_recurrences' => ':count còp|:count còps', 'period_interval' => 'cada :interval', 'period_start_date' => 'de :date', 'period_end_date' => 'fins a :date'); diff --git a/classes/Utilities/Misc/Carbon/Lang/pl.php b/classes/Utilities/Misc/Carbon/Lang/pl.php new file mode 100755 index 00000000..1034ca99 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/pl.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count rok|:count lata|:count lat', 'y' => ':countr|:countl', 'month' => ':count miesiÄ…c|:count miesiÄ…ce|:count miesiÄ™cy', 'm' => ':countmies', 'week' => ':count tydzieÅ„|:count tygodnie|:count tygodni', 'w' => ':counttyg', 'day' => ':count dzieÅ„|:count dni|:count dni', 'd' => ':countd', 'hour' => ':count godzina|:count godziny|:count godzin', 'h' => ':countg', 'minute' => ':count minuta|:count minuty|:count minut', 'min' => ':countm', 'second' => ':count sekunda|:count sekundy|:count sekund', 's' => ':counts', 'ago' => ':time temu', 'from_now' => ':time od teraz', 'after' => ':time po', 'before' => ':time przed', 'diff_now' => 'przed chwilÄ…', 'diff_yesterday' => 'wczoraj', 'diff_tomorrow' => 'jutro', 'diff_before_yesterday' => 'przedwczoraj', 'diff_after_tomorrow' => 'pojutrze'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ps.php b/classes/Utilities/Misc/Carbon/Lang/ps.php new file mode 100755 index 00000000..af8eb9cd --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ps.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count کال|:count کاله', 'y' => ':countکال|:countکاله', 'month' => ':count مياشت|:count مياشتي', 'm' => ':countمياشت|:countمياشتي', 'week' => ':count اونÛ|:count اونÛ', 'w' => ':countاونÛ|:countاونÛ', 'day' => ':count ورÚ|:count ورÚÙŠ', 'd' => ':countورÚ|:countورÚÙŠ', 'hour' => ':count ساعت|:count ساعته', 'h' => ':countساعت|:countساعته', 'minute' => ':count دقيقه|:count دقيقÛ', 'min' => ':countدقيقه|:countدقيقÛ', 'second' => ':count ثانيه|:count ثانيÛ', 's' => ':countثانيه|:countثانيÛ', 'ago' => ':time دمخه', 'from_now' => ':time له اوس څخه', 'after' => ':time وروسته', 'before' => ':time دمخه'); diff --git a/classes/Utilities/Misc/Carbon/Lang/pt.php b/classes/Utilities/Misc/Carbon/Lang/pt.php new file mode 100755 index 00000000..5fb82b33 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/pt.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count ano|:count anos', 'y' => ':count ano|:count anos', 'month' => ':count mês|:count meses', 'm' => ':count mês|:count meses', 'week' => ':count semana|:count semanas', 'w' => ':count semana|:count semanas', 'day' => ':count dia|:count dias', 'd' => ':count dia|:count dias', 'hour' => ':count hora|:count horas', 'h' => ':count hora|:count horas', 'minute' => ':count minuto|:count minutos', 'min' => ':count minuto|:count minutos', 'second' => ':count segundo|:count segundos', 's' => ':count segundo|:count segundos', 'ago' => ':time atrás', 'from_now' => 'em :time', 'after' => ':time depois', 'before' => ':time antes'); diff --git a/classes/Utilities/Misc/Carbon/Lang/pt_BR.php b/classes/Utilities/Misc/Carbon/Lang/pt_BR.php new file mode 100755 index 00000000..3d4bf072 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/pt_BR.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count ano|:count anos', 'y' => ':counta|:counta', 'month' => ':count mês|:count meses', 'm' => ':countm|:countm', 'week' => ':count semana|:count semanas', 'w' => ':countsem|:countsem', 'day' => ':count dia|:count dias', 'd' => ':countd|:countd', 'hour' => ':count hora|:count horas', 'h' => ':counth|:counth', 'minute' => ':count minuto|:count minutos', 'min' => ':countmin|:countmin', 'second' => ':count segundo|:count segundos', 's' => ':counts|:counts', 'ago' => 'há :time', 'from_now' => 'em :time', 'after' => 'após :time', 'before' => ':time atrás', 'diff_now' => 'agora', 'diff_yesterday' => 'ontem', 'diff_tomorrow' => 'amanhã', 'diff_before_yesterday' => 'anteontem', 'diff_after_tomorrow' => 'depois de amanhã', 'period_recurrences' => 'uma|:count vez', 'period_interval' => 'toda :interval', 'period_start_date' => 'de :date', 'period_end_date' => 'até :date'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ro.php b/classes/Utilities/Misc/Carbon/Lang/ro.php new file mode 100755 index 00000000..a0d060fb --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ro.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => 'un an|:count ani|:count ani', 'y' => 'un an|:count ani|:count ani', 'month' => 'o lună|:count luni|:count luni', 'm' => 'o lună|:count luni|:count luni', 'week' => 'o săptămână|:count săptămâni|:count săptămâni', 'w' => 'o săptămână|:count săptămâni|:count săptămâni', 'day' => 'o zi|:count zile|:count zile', 'd' => 'o zi|:count zile|:count zile', 'hour' => 'o oră|:count ore|:count ore', 'h' => 'o oră|:count ore|:count ore', 'minute' => 'un minut|:count minute|:count minute', 'min' => 'un minut|:count minute|:count minute', 'second' => 'o secundă|:count secunde|:count secunde', 's' => 'o secundă|:count secunde|:count secunde', 'ago' => 'acum :time', 'from_now' => ':time de acum', 'after' => 'peste :time', 'before' => 'acum :time'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ru.php b/classes/Utilities/Misc/Carbon/Lang/ru.php new file mode 100755 index 00000000..821f9618 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ru.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count год|:count года|:count лет', 'y' => ':count г|:count г|:count л', 'month' => ':count меÑÑц|:count меÑÑца|:count меÑÑцев', 'm' => ':count м|:count м|:count м', 'week' => ':count неделю|:count недели|:count недель', 'w' => ':count н|:count н|:count н', 'day' => ':count день|:count днÑ|:count дней', 'd' => ':count д|:count д|:count д', 'hour' => ':count чаÑ|:count чаÑа|:count чаÑов', 'h' => ':count ч|:count ч|:count ч', 'minute' => ':count минуту|:count минуты|:count минут', 'min' => ':count мин|:count мин|:count мин', 'second' => ':count Ñекунду|:count Ñекунды|:count Ñекунд', 's' => ':count Ñ|:count Ñ|:count Ñ', 'ago' => ':time назад', 'from_now' => 'через :time', 'after' => ':time поÑле', 'before' => ':time до'); diff --git a/classes/Utilities/Misc/Carbon/Lang/sh.php b/classes/Utilities/Misc/Carbon/Lang/sh.php new file mode 100755 index 00000000..c86b8856 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sh.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\PluralizationRules::set(function ($number) { + return 1 == $number % 10 && 11 != $number % 100 ? 0 : ($number % 10 >= 2 && $number % 10 <= 4 && ($number % 100 < 10 || $number % 100 >= 20) ? 1 : 2); +}, 'sh'); +return array('year' => ':count godina|:count godine|:count godina', 'y' => ':count godina|:count godine|:count godina', 'month' => ':count mesec|:count meseca|:count meseci', 'm' => ':count mesec|:count meseca|:count meseci', 'week' => ':count nedelja|:count nedelje|:count nedelja', 'w' => ':count nedelja|:count nedelje|:count nedelja', 'day' => ':count dan|:count dana|:count dana', 'd' => ':count dan|:count dana|:count dana', 'hour' => ':count Äas|:count Äasa|:count Äasova', 'h' => ':count Äas|:count Äasa|:count Äasova', 'minute' => ':count minut|:count minuta|:count minuta', 'min' => ':count minut|:count minuta|:count minuta', 'second' => ':count sekund|:count sekunda|:count sekundi', 's' => ':count sekund|:count sekunda|:count sekundi', 'ago' => 'pre :time', 'from_now' => 'za :time', 'after' => 'nakon :time', 'before' => ':time raniјe'); diff --git a/classes/Utilities/Misc/Carbon/Lang/sk.php b/classes/Utilities/Misc/Carbon/Lang/sk.php new file mode 100755 index 00000000..4f06bfa9 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sk.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => 'rok|:count roky|:count rokov', 'y' => 'rok|:count roky|:count rokov', 'month' => 'mesiac|:count mesiace|:count mesiacov', 'm' => 'mesiac|:count mesiace|:count mesiacov', 'week' => 'týždeň|:count týždne|:count týždňov', 'w' => 'týždeň|:count týždne|:count týždňov', 'day' => 'deň|:count dni|:count dní', 'd' => 'deň|:count dni|:count dní', 'hour' => 'hodinu|:count hodiny|:count hodín', 'h' => 'hodinu|:count hodiny|:count hodín', 'minute' => 'minútu|:count minúty|:count minút', 'min' => 'minútu|:count minúty|:count minút', 'second' => 'sekundu|:count sekundy|:count sekúnd', 's' => 'sekundu|:count sekundy|:count sekúnd', 'ago' => 'pred :time', 'from_now' => 'za :time', 'after' => 'o :time neskôr', 'before' => ':time predtým', 'year_ago' => 'rokom|:count rokmi|:count rokmi', 'month_ago' => 'mesiacom|:count mesiacmi|:count mesiacmi', 'week_ago' => 'týždňom|:count týždňami|:count týždňami', 'day_ago' => 'dňom|:count dňami|:count dňami', 'hour_ago' => 'hodinou|:count hodinami|:count hodinami', 'minute_ago' => 'minútou|:count minútami|:count minútami', 'second_ago' => 'sekundou|:count sekundami|:count sekundami'); diff --git a/classes/Utilities/Misc/Carbon/Lang/sl.php b/classes/Utilities/Misc/Carbon/Lang/sl.php new file mode 100755 index 00000000..ff706cd5 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sl.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count leto|:count leti|:count leta|:count let', 'y' => ':count leto|:count leti|:count leta|:count let', 'month' => ':count mesec|:count meseca|:count mesece|:count mesecev', 'm' => ':count mesec|:count meseca|:count mesece|:count mesecev', 'week' => ':count teden|:count tedna|:count tedne|:count tednov', 'w' => ':count teden|:count tedna|:count tedne|:count tednov', 'day' => ':count dan|:count dni|:count dni|:count dni', 'd' => ':count dan|:count dni|:count dni|:count dni', 'hour' => ':count uro|:count uri|:count ure|:count ur', 'h' => ':count uro|:count uri|:count ure|:count ur', 'minute' => ':count minuto|:count minuti|:count minute|:count minut', 'min' => ':count minuto|:count minuti|:count minute|:count minut', 'second' => ':count sekundo|:count sekundi|:count sekunde|:count sekund', 's' => ':count sekundo|:count sekundi|:count sekunde|:count sekund', 'year_ago' => ':count letom|:count leti|:count leti|:count leti', 'month_ago' => ':count mesecem|:count meseci|:count meseci|:count meseci', 'week_ago' => ':count tednom|:count tednoma|:count tedni|:count tedni', 'day_ago' => ':count dnem|:count dnevoma|:count dnevi|:count dnevi', 'hour_ago' => ':count uro|:count urama|:count urami|:count urami', 'minute_ago' => ':count minuto|:count minutama|:count minutami|:count minutami', 'second_ago' => ':count sekundo|:count sekundama|:count sekundami|:count sekundami', 'ago' => 'pred :time', 'from_now' => 'Äez :time', 'after' => 'Äez :time', 'before' => 'pred :time', 'diff_now' => 'ravnokar', 'diff_yesterday' => 'vÄeraj', 'diff_tomorrow' => 'jutri', 'diff_before_yesterday' => 'predvÄerajÅ¡njim', 'diff_after_tomorrow' => 'pojutriÅ¡njem'); diff --git a/classes/Utilities/Misc/Carbon/Lang/sq.php b/classes/Utilities/Misc/Carbon/Lang/sq.php new file mode 100755 index 00000000..470662bd --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sq.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count vit|:count vjet', 'y' => ':count vit|:count vjet', 'month' => ':count muaj|:count muaj', 'm' => ':count muaj|:count muaj', 'week' => ':count javë|:count javë', 'w' => ':count javë|:count javë', 'day' => ':count ditë|:count ditë', 'd' => ':count ditë|:count ditë', 'hour' => ':count orë|:count orë', 'h' => ':count orë|:count orë', 'minute' => ':count minutë|:count minuta', 'min' => ':count minutë|:count minuta', 'second' => ':count sekondë|:count sekonda', 's' => ':count sekondë|:count sekonda', 'ago' => ':time më parë', 'from_now' => ':time nga tani', 'after' => ':time pas', 'before' => ':time para'); diff --git a/classes/Utilities/Misc/Carbon/Lang/sr.php b/classes/Utilities/Misc/Carbon/Lang/sr.php new file mode 100755 index 00000000..06030bbb --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sr.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count godina|:count godine|:count godina', 'y' => ':count godina|:count godine|:count godina', 'month' => ':count mesec|:count meseca|:count meseci', 'm' => ':count mesec|:count meseca|:count meseci', 'week' => ':count nedelja|:count nedelje|:count nedelja', 'w' => ':count nedelja|:count nedelje|:count nedelja', 'day' => ':count dan|:count dana|:count dana', 'd' => ':count dan|:count dana|:count dana', 'hour' => ':count sat|:count sata|:count sati', 'h' => ':count sat|:count sata|:count sati', 'minute' => ':count minut|:count minuta |:count minuta', 'min' => ':count minut|:count minuta |:count minuta', 'second' => ':count sekund|:count sekunde|:count sekunde', 's' => ':count sekund|:count sekunde|:count sekunde', 'ago' => 'pre :time', 'from_now' => ':time od sada', 'after' => 'nakon :time', 'before' => 'pre :time', 'year_from_now' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina', 'year_ago' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina', 'week_from_now' => '{1} :count nedelju|{2,3,4} :count nedelje|[5,Inf[ :count nedelja', 'week_ago' => '{1} :count nedelju|{2,3,4} :count nedelje|[5,Inf[ :count nedelja'); diff --git a/classes/Utilities/Misc/Carbon/Lang/sr_Cyrl.php b/classes/Utilities/Misc/Carbon/Lang/sr_Cyrl.php new file mode 100755 index 00000000..a20fa7b7 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sr_Cyrl.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[ :count година', 'y' => ':count г.', 'month' => '{1} :count меÑец|{2,3,4}:count меÑеца|[5,Inf[ :count меÑеци', 'm' => ':count м.', 'week' => '{1} :count недеља|{2,3,4}:count недеље|[5,Inf[ :count недеља', 'w' => ':count нед.', 'day' => '{1,21,31} :count дан|[2,Inf[ :count дана', 'd' => ':count д.', 'hour' => '{1,21} :count Ñат|{2,3,4,22,23,24}:count Ñата|[5,Inf[ :count Ñати', 'h' => ':count ч.', 'minute' => '{1,21,31,41,51} :count минут|[2,Inf[ :count минута', 'min' => ':count мин.', 'second' => '{1,21,31,41,51} :count Ñекунд|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count Ñекунде|[5,Inf[:count Ñекунди', 's' => ':count Ñек.', 'ago' => 'пре :time', 'from_now' => 'за :time', 'after' => ':time након', 'before' => ':time пре', 'year_from_now' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година', 'year_ago' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година', 'week_from_now' => '{1} :count недељу|{2,3,4} :count недеље|[5,Inf[ :count недеља', 'week_ago' => '{1} :count недељу|{2,3,4} :count недеље|[5,Inf[ :count недеља', 'diff_now' => 'управо Ñада', 'diff_yesterday' => 'јуче', 'diff_tomorrow' => 'Ñутра', 'diff_before_yesterday' => 'прекјуче', 'diff_after_tomorrow' => 'прекоÑутра'); diff --git a/classes/Utilities/Misc/Carbon/Lang/sr_Cyrl_ME.php b/classes/Utilities/Misc/Carbon/Lang/sr_Cyrl_ME.php new file mode 100755 index 00000000..defbfa03 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sr_Cyrl_ME.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[ :count година', 'y' => ':count г.', 'month' => '{1} :count мјеÑец|{2,3,4}:count мјеÑеца|[5,Inf[ :count мјеÑеци', 'm' => ':count мј.', 'week' => '{1} :count недјеља|{2,3,4}:count недјеље|[5,Inf[ :count недјеља', 'w' => ':count нед.', 'day' => '{1,21,31} :count дан|[2,Inf[ :count дана', 'd' => ':count д.', 'hour' => '{1,21} :count Ñат|{2,3,4,22,23,24}:count Ñата|[5,Inf[ :count Ñати', 'h' => ':count ч.', 'minute' => '{1,21,31,41,51} :count минут|[2,Inf[ :count минута', 'min' => ':count мин.', 'second' => '{1,21,31,41,51} :count Ñекунд|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count Ñекунде|[5,Inf[:count Ñекунди', 's' => ':count Ñек.', 'ago' => 'прије :time', 'from_now' => 'за :time', 'after' => ':time након', 'before' => ':time прије', 'year_from_now' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година', 'year_ago' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година', 'week_from_now' => '{1} :count недјељу|{2,3,4} :count недјеље|[5,Inf[ :count недјеља', 'week_ago' => '{1} :count недјељу|{2,3,4} :count недјеље|[5,Inf[ :count недјеља', 'diff_now' => 'управо Ñада', 'diff_yesterday' => 'јуче', 'diff_tomorrow' => 'Ñутра', 'diff_before_yesterday' => 'прекјуче', 'diff_after_tomorrow' => 'прекоÑјутра'); diff --git a/classes/Utilities/Misc/Carbon/Lang/sr_Latn_ME.php b/classes/Utilities/Misc/Carbon/Lang/sr_Latn_ME.php new file mode 100755 index 00000000..7a26a0a4 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sr_Latn_ME.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count godine|[0,Inf[ :count godina', 'y' => ':count g.', 'month' => '{1} :count mjesec|{2,3,4}:count mjeseca|[5,Inf[ :count mjeseci', 'm' => ':count mj.', 'week' => '{1} :count nedjelja|{2,3,4}:count nedjelje|[5,Inf[ :count nedjelja', 'w' => ':count ned.', 'day' => '{1,21,31} :count dan|[2,Inf[ :count dana', 'd' => ':count d.', 'hour' => '{1,21} :count sat|{2,3,4,22,23,24}:count sata|[5,Inf[ :count sati', 'h' => ':count Ä.', 'minute' => '{1,21,31,41,51} :count minut|[2,Inf[ :count minuta', 'min' => ':count min.', 'second' => '{1,21,31,41,51} :count sekund|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count sekunde|[5,Inf[:count sekundi', 's' => ':count sek.', 'ago' => 'prije :time', 'from_now' => 'za :time', 'after' => ':time nakon', 'before' => ':time prije', 'year_from_now' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina', 'year_ago' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina', 'week_from_now' => '{1} :count nedjelju|{2,3,4} :count nedjelje|[5,Inf[ :count nedjelja', 'week_ago' => '{1} :count nedjelju|{2,3,4} :count nedjelje|[5,Inf[ :count nedjelja', 'diff_now' => 'upravo sada', 'diff_yesterday' => 'juÄe', 'diff_tomorrow' => 'sutra', 'diff_before_yesterday' => 'prekjuÄe', 'diff_after_tomorrow' => 'preksutra'); diff --git a/classes/Utilities/Misc/Carbon/Lang/sr_ME.php b/classes/Utilities/Misc/Carbon/Lang/sr_ME.php new file mode 100755 index 00000000..da09d785 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sr_ME.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return require __DIR__ . '/sr_Latn_ME.php'; diff --git a/classes/Utilities/Misc/Carbon/Lang/sv.php b/classes/Utilities/Misc/Carbon/Lang/sv.php new file mode 100755 index 00000000..771bcb60 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sv.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count Ã¥r|:count Ã¥r', 'y' => ':count Ã¥r|:count Ã¥r', 'month' => ':count mÃ¥nad|:count mÃ¥nader', 'm' => ':count mÃ¥nad|:count mÃ¥nader', 'week' => ':count vecka|:count veckor', 'w' => ':count vecka|:count veckor', 'day' => ':count dag|:count dagar', 'd' => ':count dag|:count dagar', 'hour' => ':count timme|:count timmar', 'h' => ':count timme|:count timmar', 'minute' => ':count minut|:count minuter', 'min' => ':count minut|:count minuter', 'second' => ':count sekund|:count sekunder', 's' => ':count sekund|:count sekunder', 'ago' => ':time sedan', 'from_now' => 'om :time', 'after' => ':time efter', 'before' => ':time före'); diff --git a/classes/Utilities/Misc/Carbon/Lang/sw.php b/classes/Utilities/Misc/Carbon/Lang/sw.php new file mode 100755 index 00000000..f0d0a305 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/sw.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => 'mwaka 1|miaka :count', 'y' => 'mwaka 1|miaka :count', 'month' => 'mwezi 1|miezi :count', 'm' => 'mwezi 1|miezi :count', 'week' => 'wiki 1|wiki :count', 'w' => 'wiki 1|wiki :count', 'day' => 'siku 1|siku :count', 'd' => 'siku 1|siku :count', 'hour' => 'saa 1|masaa :count', 'h' => 'saa 1|masaa :count', 'minute' => 'dakika 1|dakika :count', 'min' => 'dakika 1|dakika :count', 'second' => 'sekunde 1|sekunde :count', 's' => 'sekunde 1|sekunde :count', 'ago' => ':time ziliyopita', 'from_now' => ':time kwanzia sasa', 'after' => ':time baada', 'before' => ':time kabla'); diff --git a/classes/Utilities/Misc/Carbon/Lang/th.php b/classes/Utilities/Misc/Carbon/Lang/th.php new file mode 100755 index 00000000..4591334d --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/th.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count ปี', 'y' => ':count ปี', 'month' => ':count เดือน', 'm' => ':count เดือน', 'week' => ':count สัปดาห์', 'w' => ':count สัปดาห์', 'day' => ':count วัน', 'd' => ':count วัน', 'hour' => ':count ชั่วโมง', 'h' => ':count ชั่วโมง', 'minute' => ':count นาที', 'min' => ':count นาที', 'second' => ':count วินาที', 's' => ':count วินาที', 'ago' => ':timeที่à¹à¸¥à¹‰à¸§', 'from_now' => ':timeต่อจาà¸à¸™à¸µà¹‰', 'after' => ':timeหลังจาà¸à¸™à¸µà¹‰', 'before' => ':timeà¸à¹ˆà¸­à¸™'); diff --git a/classes/Utilities/Misc/Carbon/Lang/tr.php b/classes/Utilities/Misc/Carbon/Lang/tr.php new file mode 100755 index 00000000..78516201 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/tr.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count yıl', 'y' => ':count yıl', 'month' => ':count ay', 'm' => ':count ay', 'week' => ':count hafta', 'w' => ':count hafta', 'day' => ':count gün', 'd' => ':count gün', 'hour' => ':count saat', 'h' => ':count saat', 'minute' => ':count dakika', 'min' => ':count dakika', 'second' => ':count saniye', 's' => ':count saniye', 'ago' => ':time önce', 'from_now' => ':time sonra', 'after' => ':time sonra', 'before' => ':time önce'); diff --git a/classes/Utilities/Misc/Carbon/Lang/uk.php b/classes/Utilities/Misc/Carbon/Lang/uk.php new file mode 100755 index 00000000..7a2a13b4 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/uk.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count рік|:count роки|:count років', 'y' => ':count рік|:count роки|:count років', 'month' => ':count міÑÑць|:count міÑÑці|:count міÑÑців', 'm' => ':count міÑÑць|:count міÑÑці|:count міÑÑців', 'week' => ':count тиждень|:count тижні|:count тижнів', 'w' => ':count тиждень|:count тижні|:count тижнів', 'day' => ':count день|:count дні|:count днів', 'd' => ':count день|:count дні|:count днів', 'hour' => ':count година|:count години|:count годин', 'h' => ':count година|:count години|:count годин', 'minute' => ':count хвилину|:count хвилини|:count хвилин', 'min' => ':count хвилину|:count хвилини|:count хвилин', 'second' => ':count Ñекунду|:count Ñекунди|:count Ñекунд', 's' => ':count Ñекунду|:count Ñекунди|:count Ñекунд', 'ago' => ':time тому', 'from_now' => 'через :time', 'after' => ':time піÑлÑ', 'before' => ':time до', 'diff_now' => 'щойно', 'diff_yesterday' => 'вчора', 'diff_tomorrow' => 'завтра', 'diff_before_yesterday' => 'позавчора', 'diff_after_tomorrow' => 'піÑлÑзавтра', 'period_recurrences' => 'один раз|:count рази|:count разів', 'period_interval' => 'кожні :interval', 'period_start_date' => 'з :date', 'period_end_date' => 'до :date'); diff --git a/classes/Utilities/Misc/Carbon/Lang/ur.php b/classes/Utilities/Misc/Carbon/Lang/ur.php new file mode 100755 index 00000000..5e47947b --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/ur.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count سال', 'month' => ':count ماه', 'week' => ':count ÛÙتے', 'day' => ':count روز', 'hour' => ':count گھنٹے', 'minute' => ':count منٹ', 'second' => ':count سیکنڈ', 'ago' => ':time Ù¾ÛÙ„Û’', 'from_now' => ':time بعد', 'after' => ':time بعد', 'before' => ':time Ù¾ÛÙ„Û’'); diff --git a/classes/Utilities/Misc/Carbon/Lang/uz.php b/classes/Utilities/Misc/Carbon/Lang/uz.php new file mode 100755 index 00000000..9f63bb80 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/uz.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count yil', 'y' => ':count yil', 'month' => ':count oy', 'm' => ':count oy', 'week' => ':count hafta', 'w' => ':count hafta', 'day' => ':count kun', 'd' => ':count kun', 'hour' => ':count soat', 'h' => ':count soat', 'minute' => ':count daqiqa', 'min' => ':count daq', 'second' => ':count soniya', 's' => ':count s', 'ago' => ':time avval', 'from_now' => ':time dan keyin', 'after' => ':time keyin', 'before' => ':time oldin'); diff --git a/classes/Utilities/Misc/Carbon/Lang/vi.php b/classes/Utilities/Misc/Carbon/Lang/vi.php new file mode 100755 index 00000000..fc1e7e03 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/vi.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':count năm', 'y' => ':count năm', 'month' => ':count tháng', 'm' => ':count tháng', 'week' => ':count tuần', 'w' => ':count tuần', 'day' => ':count ngày', 'd' => ':count ngày', 'hour' => ':count giá»', 'h' => ':count giá»', 'minute' => ':count phút', 'min' => ':count phút', 'second' => ':count giây', 's' => ':count giây', 'ago' => ':time trÆ°á»›c', 'from_now' => ':time từ bây giá»', 'after' => ':time sau', 'before' => ':time trÆ°á»›c'); diff --git a/classes/Utilities/Misc/Carbon/Lang/zh.php b/classes/Utilities/Misc/Carbon/Lang/zh.php new file mode 100755 index 00000000..d369080c --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/zh.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':countå¹´', 'y' => ':countå¹´', 'month' => ':count个月', 'm' => ':count个月', 'week' => ':count周', 'w' => ':count周', 'day' => ':count天', 'd' => ':count天', 'hour' => ':countå°æ—¶', 'h' => ':countå°æ—¶', 'minute' => ':count分钟', 'min' => ':count分钟', 'second' => ':count秒', 's' => ':count秒', 'ago' => ':timeå‰', 'from_now' => 'è·çŽ°åœ¨:time', 'after' => ':timeåŽ', 'before' => ':timeå‰'); diff --git a/classes/Utilities/Misc/Carbon/Lang/zh_TW.php b/classes/Utilities/Misc/Carbon/Lang/zh_TW.php new file mode 100755 index 00000000..16309e60 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Lang/zh_TW.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array('year' => ':countå¹´', 'y' => ':countå¹´', 'month' => ':count月', 'm' => ':count月', 'week' => ':count週', 'w' => ':count週', 'day' => ':count天', 'd' => ':count天', 'hour' => ':countå°æ™‚', 'h' => ':countå°æ™‚', 'minute' => ':count分é˜', 'min' => ':count分é˜', 'second' => ':count秒', 's' => ':count秒', 'ago' => ':timeå‰', 'from_now' => 'è·ç¾åœ¨:time', 'after' => ':time後', 'before' => ':timeå‰'); diff --git a/classes/Utilities/Misc/Carbon/Laravel/ServiceProvider.php b/classes/Utilities/Misc/Carbon/Laravel/ServiceProvider.php new file mode 100755 index 00000000..2e04f9a7 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Laravel/ServiceProvider.php @@ -0,0 +1,34 @@ +app['events']; + if ($events instanceof \ILAB\MediaCloud\Utilities\Misc\Illuminate\Events\EventDispatcher || $events instanceof \ILAB\MediaCloud\Utilities\Misc\Illuminate\Events\Dispatcher) { + $events->listen(\class_exists('ILAB\\MediaCloud\\Utilities\\Misc\\Illuminate\\Foundation\\Events\\LocaleUpdated') ? 'Illuminate\\Foundation\\Events\\LocaleUpdated' : 'locale.changed', function () use($service) { + $service->updateLocale(); + }); + $service->updateLocale(); + } + } + public function updateLocale() + { + $translator = $this->app['translator']; + if ($translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Translator || $translator instanceof \ILAB\MediaCloud\Utilities\Misc\Illuminate\Translation\Translator) { + \ILAB\MediaCloud\Utilities\Misc\Carbon\Carbon::setLocale($translator->getLocale()); + } + } + public function register() + { + // Needed for Laravel < 5.3 compatibility + } +} diff --git a/classes/Utilities/Misc/Carbon/Translator.php b/classes/Utilities/Misc/Carbon/Translator.php new file mode 100755 index 00000000..156acb25 --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Translator.php @@ -0,0 +1,121 @@ +addLoader('array', new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\ArrayLoader()); + parent::__construct($locale, $formatter, $cacheDir, $debug); + } + /** + * Reset messages of a locale (all locale if no locale passed). + * Remove custom messages and reload initial messages from matching + * file in Lang directory. + * + * @param string|null $locale + * + * @return bool + */ + public function resetMessages($locale = null) + { + if ($locale === null) { + static::$messages = array(); + return \true; + } + if (\file_exists($filename = __DIR__ . '/Lang/' . $locale . '.php')) { + static::$messages[$locale] = (require $filename); + $this->addResource('array', static::$messages[$locale], $locale); + return \true; + } + return \false; + } + /** + * Init messages language from matching file in Lang directory. + * + * @param string $locale + * + * @return bool + */ + protected function loadMessagesFromFile($locale) + { + if (isset(static::$messages[$locale])) { + return \true; + } + return $this->resetMessages($locale); + } + /** + * Set messages of a locale and take file first if present. + * + * @param string $locale + * @param array $messages + * + * @return $this + */ + public function setMessages($locale, $messages) + { + $this->loadMessagesFromFile($locale); + $this->addResource('array', $messages, $locale); + static::$messages[$locale] = \array_merge(isset(static::$messages[$locale]) ? static::$messages[$locale] : array(), $messages); + return $this; + } + /** + * Get messages of a locale, if none given, return all the + * languages. + * + * @param string|null $locale + * + * @return array + */ + public function getMessages($locale = null) + { + return $locale === null ? static::$messages : static::$messages[$locale]; + } + /** + * Set the current translator locale and indicate if the source locale file exists + * + * @param string $locale locale ex. en + * + * @return bool + */ + public function setLocale($locale) + { + $locale = \preg_replace_callback('/[-_]([a-z]{2,})/', function ($matches) { + // _2-letters is a region, _3+-letters is a variant + return '_' . \call_user_func(\strlen($matches[1]) > 2 ? 'ucfirst' : 'strtoupper', $matches[1]); + }, \strtolower($locale)); + if ($this->loadMessagesFromFile($locale)) { + parent::setLocale($locale); + return \true; + } + return \false; + } +} diff --git a/classes/Utilities/Misc/Carbon/Upgrade.php b/classes/Utilities/Misc/Carbon/Upgrade.php new file mode 100755 index 00000000..e953881a --- /dev/null +++ b/classes/Utilities/Misc/Carbon/Upgrade.php @@ -0,0 +1,97 @@ + '5.8.0', 'laravel/cashier' => '9.0.1', 'illuminate/support' => '5.8.0', 'laravel/dusk' => '5.0.0'); + protected static $otherLibraries = array('spatie/laravel-analytics' => '3.6.4', 'jenssegers/date' => '3.5.0'); + /** + * @param \UpdateHelper\UpdateHelper $helper + */ + public function check(\ILAB\MediaCloud\Utilities\Misc\UpdateHelper\UpdateHelper $helper) + { + $helper->write(array('Carbon 1 is deprecated, see how to migrate to Carbon 2.', 'https://carbon.nesbot.com/docs/#api-carbon-2')); + if (static::SUGGEST_ON_UPDATE || static::ASK_ON_UPDATE || $helper->getIo()->isVerbose()) { + $laravelUpdate = array(); + foreach (static::$laravelLibraries as $name => $version) { + if ($helper->hasAsDependency($name) && $helper->isDependencyLesserThan($name, $version)) { + $laravelUpdate[$name] = $version; + } + } + if (\count($laravelUpdate)) { + $output = array(' Please consider upgrading your Laravel dependencies to be compatible with Carbon 2:'); + foreach ($laravelUpdate as $name => $version) { + $output[] = " - {$name} at least to version {$version}"; + } + $output[] = ''; + $output[] = " If you can't update Laravel, check https://carbon.nesbot.com/ to see how to"; + $output[] = ' install Carbon 2 using alias version and our adapter kylekatarnls/laravel-carbon-2'; + $output[] = ''; + $helper->write($output); + } + foreach (static::$otherLibraries as $name => $version) { + if ($helper->hasAsDependency($name) && $helper->isDependencyLesserThan($name, $version)) { + $helper->write(" Please consider upgrading {$name} at least to {$version} to be compatible with Carbon 2.\n"); + } + } + if (static::ASK_ON_UPDATE) { + static::askForUpgrade($helper); + return; + } + } + $path = \implode(\DIRECTORY_SEPARATOR, array('.', 'vendor', 'bin', 'upgrade-carbon')); + if (!\file_exists($path)) { + $path = \realpath(__DIR__ . '/../../bin/upgrade-carbon'); + } + $helper->write(' You can run ' . \escapeshellarg($path) . ' to get help in updating carbon and other frameworks and libraries that depend on it.'); + } + private static function getUpgradeQuestion($upgrades) + { + $message = "Do you want us to try the following upgrade:\n"; + foreach ($upgrades as $name => $version) { + $message .= " - {$name}: {$version}\n"; + } + return $message . '[Y/N] '; + } + public static function askForUpgrade(\ILAB\MediaCloud\Utilities\Misc\UpdateHelper\UpdateHelper $helper, $upgradeIfNotInteractive = \false) + { + $upgrades = array('nesbot/carbon' => '^2.0.0'); + foreach (array(static::$laravelLibraries, static::$otherLibraries) as $libraries) { + foreach ($libraries as $name => $version) { + if ($helper->hasAsDependency($name) && $helper->isDependencyLesserThan($name, $version)) { + $upgrades[$name] = "^{$version}"; + } + } + } + $shouldUpgrade = $helper->isInteractive() ? $helper->getIo()->askConfirmation(static::getUpgradeQuestion($upgrades)) : $upgradeIfNotInteractive; + if ($shouldUpgrade) { + $helper->setDependencyVersions($upgrades)->update(); + } + } + public static function upgrade(\ILAB\MediaCloud\Utilities\Misc\Composer\Script\Event $event = null) + { + if (!$event) { + $composer = new \ILAB\MediaCloud\Utilities\Misc\Composer\Composer(); + $baseDir = __DIR__ . '/../carbon'; + if (\file_exists("{$baseDir}/autoload.php")) { + $baseDir .= '/..'; + } + $composer->setConfig(new \ILAB\MediaCloud\Utilities\Misc\Composer\Config(\true, $baseDir)); + $event = new \ILAB\MediaCloud\Utilities\Misc\Composer\Script\Event('upgrade-carbon', $composer, new \ILAB\MediaCloud\Utilities\Misc\Composer\IO\ConsoleIO(new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Input\StringInput(''), new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Output\ConsoleOutput(), new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Helper\HelperSet(array(new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Helper\QuestionHelper())))); + } + static::askForUpgrade(new \ILAB\MediaCloud\Utilities\Misc\UpdateHelper\UpdateHelper($event), \true); + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/AbstractOperation.php b/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/AbstractOperation.php new file mode 100755 index 00000000..dc007735 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/AbstractOperation.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Catalogue; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface; +/** + * Base catalogues binary operation class. + * + * A catalogue binary operation performs operation on + * source (the left argument) and target (the right argument) catalogues. + * + * @author Jean-François Simon + */ +abstract class AbstractOperation implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Catalogue\OperationInterface +{ + protected $source; + protected $target; + protected $result; + /** + * @var array|null The domains affected by this operation + */ + private $domains; + /** + * This array stores 'all', 'new' and 'obsolete' messages for all valid domains. + * + * The data structure of this array is as follows: + * + * [ + * 'domain 1' => [ + * 'all' => [...], + * 'new' => [...], + * 'obsolete' => [...] + * ], + * 'domain 2' => [ + * 'all' => [...], + * 'new' => [...], + * 'obsolete' => [...] + * ], + * ... + * ] + * + * @var array The array that stores 'all', 'new' and 'obsolete' messages + */ + protected $messages; + /** + * @throws LogicException + */ + public function __construct(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface $source, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface $target) + { + if ($source->getLocale() !== $target->getLocale()) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException('Operated catalogues must belong to the same locale.'); + } + $this->source = $source; + $this->target = $target; + $this->result = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue($source->getLocale()); + $this->messages = []; + } + /** + * {@inheritdoc} + */ + public function getDomains() + { + if (null === $this->domains) { + $this->domains = \array_values(\array_unique(\array_merge($this->source->getDomains(), $this->target->getDomains()))); + } + return $this->domains; + } + /** + * {@inheritdoc} + */ + public function getMessages($domain) + { + if (!\in_array($domain, $this->getDomains())) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('Invalid domain: %s.', $domain)); + } + if (!isset($this->messages[$domain]['all'])) { + $this->processDomain($domain); + } + return $this->messages[$domain]['all']; + } + /** + * {@inheritdoc} + */ + public function getNewMessages($domain) + { + if (!\in_array($domain, $this->getDomains())) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('Invalid domain: %s.', $domain)); + } + if (!isset($this->messages[$domain]['new'])) { + $this->processDomain($domain); + } + return $this->messages[$domain]['new']; + } + /** + * {@inheritdoc} + */ + public function getObsoleteMessages($domain) + { + if (!\in_array($domain, $this->getDomains())) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('Invalid domain: %s.', $domain)); + } + if (!isset($this->messages[$domain]['obsolete'])) { + $this->processDomain($domain); + } + return $this->messages[$domain]['obsolete']; + } + /** + * {@inheritdoc} + */ + public function getResult() + { + foreach ($this->getDomains() as $domain) { + if (!isset($this->messages[$domain])) { + $this->processDomain($domain); + } + } + return $this->result; + } + /** + * Performs operation on source and target catalogues for the given domain and + * stores the results. + * + * @param string $domain The domain which the operation will be performed for + */ + protected abstract function processDomain($domain); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/MergeOperation.php b/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/MergeOperation.php new file mode 100755 index 00000000..e769816e --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/MergeOperation.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Catalogue; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface; +/** + * Merge operation between two catalogues as follows: + * all = source ∪ target = {x: x ∈ source ∨ x ∈ target} + * new = all ∖ source = {x: x ∈ target ∧ x ∉ source} + * obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ source ∧ x ∉ target} = ∅ + * Basically, the result contains messages from both catalogues. + * + * @author Jean-François Simon + */ +class MergeOperation extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Catalogue\AbstractOperation +{ + /** + * {@inheritdoc} + */ + protected function processDomain($domain) + { + $this->messages[$domain] = ['all' => [], 'new' => [], 'obsolete' => []]; + $intlDomain = $domain . \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + foreach ($this->source->all($domain) as $id => $message) { + $this->messages[$domain]['all'][$id] = $message; + $this->result->add([$id => $message], $this->source->defines($id, $intlDomain) ? $intlDomain : $domain); + if (null !== ($keyMetadata = $this->source->getMetadata($id, $domain))) { + $this->result->setMetadata($id, $keyMetadata, $domain); + } + } + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $this->result->add([$id => $message], $this->target->defines($id, $intlDomain) ? $intlDomain : $domain); + if (null !== ($keyMetadata = $this->target->getMetadata($id, $domain))) { + $this->result->setMetadata($id, $keyMetadata, $domain); + } + } + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/OperationInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/OperationInterface.php new file mode 100755 index 00000000..cf654c68 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/OperationInterface.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Catalogue; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface; +/** + * Represents an operation on catalogue(s). + * + * An instance of this interface performs an operation on one or more catalogues and + * stores intermediate and final results of the operation. + * + * The first catalogue in its argument(s) is called the 'source catalogue' or 'source' and + * the following results are stored: + * + * Messages: also called 'all', are valid messages for the given domain after the operation is performed. + * + * New Messages: also called 'new' (new = all ∖ source = {x: x ∈ all ∧ x ∉ source}). + * + * Obsolete Messages: also called 'obsolete' (obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ all}). + * + * Result: also called 'result', is the resulting catalogue for the given domain that holds the same messages as 'all'. + * + * @author Jean-François Simon + */ +interface OperationInterface +{ + /** + * Returns domains affected by operation. + * + * @return array + */ + public function getDomains(); + /** + * Returns all valid messages ('all') after operation. + * + * @param string $domain + * + * @return array + */ + public function getMessages($domain); + /** + * Returns new messages ('new') after operation. + * + * @param string $domain + * + * @return array + */ + public function getNewMessages($domain); + /** + * Returns obsolete messages ('obsolete') after operation. + * + * @param string $domain + * + * @return array + */ + public function getObsoleteMessages($domain); + /** + * Returns resulting catalogue ('result'). + * + * @return MessageCatalogueInterface + */ + public function getResult(); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/TargetOperation.php b/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/TargetOperation.php new file mode 100755 index 00000000..7a2b9806 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Catalogue/TargetOperation.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Catalogue; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface; +/** + * Target operation between two catalogues: + * intersection = source ∩ target = {x: x ∈ source ∧ x ∈ target} + * all = intersection ∪ (target ∖ intersection) = target + * new = all ∖ source = {x: x ∈ target ∧ x ∉ source} + * obsolete = source ∖ all = source ∖ target = {x: x ∈ source ∧ x ∉ target} + * Basically, the result contains messages from the target catalogue. + * + * @author Michael Lee + */ +class TargetOperation extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Catalogue\AbstractOperation +{ + /** + * {@inheritdoc} + */ + protected function processDomain($domain) + { + $this->messages[$domain] = ['all' => [], 'new' => [], 'obsolete' => []]; + $intlDomain = $domain . \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + // For 'all' messages, the code can't be simplified as ``$this->messages[$domain]['all'] = $target->all($domain);``, + // because doing so will drop messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} + // + // For 'new' messages, the code can't be simplified as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));`` + // because doing so will not exclude messages like {x: x ∈ target ∧ x ∉ source.all ∧ x ∈ source.fallback} + // + // For 'obsolete' messages, the code can't be simplified as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))`` + // because doing so will not exclude messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} + foreach ($this->source->all($domain) as $id => $message) { + if ($this->target->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->result->add([$id => $message], $this->target->defines($id, $intlDomain) ? $intlDomain : $domain); + if (null !== ($keyMetadata = $this->source->getMetadata($id, $domain))) { + $this->result->setMetadata($id, $keyMetadata, $domain); + } + } else { + $this->messages[$domain]['obsolete'][$id] = $message; + } + } + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $this->result->add([$id => $message], $this->target->defines($id, $intlDomain) ? $intlDomain : $domain); + if (null !== ($keyMetadata = $this->target->getMetadata($id, $domain))) { + $this->result->setMetadata($id, $keyMetadata, $domain); + } + } + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Command/XliffLintCommand.php b/classes/Utilities/Misc/Symfony/Component/Translation/Command/XliffLintCommand.php new file mode 100755 index 00000000..0b95e10e --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Command/XliffLintCommand.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Command; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Command\Command; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Exception\RuntimeException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Input\InputArgument; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Input\InputInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Input\InputOption; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Output\OutputInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Style\SymfonyStyle; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Util\XliffUtils; +/** + * Validates XLIFF files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + * @author Javier Eguiluz + */ +class XliffLintCommand extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Command\Command +{ + protected static $defaultName = 'lint:xliff'; + private $format; + private $displayCorrectFiles; + private $directoryIteratorProvider; + private $isReadableProvider; + private $requireStrictFileNames; + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = \true) + { + parent::__construct($name); + $this->directoryIteratorProvider = $directoryIteratorProvider; + $this->isReadableProvider = $isReadableProvider; + $this->requireStrictFileNames = $requireStrictFileNames; + } + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setDescription('Lints a XLIFF file and outputs encountered errors')->addArgument('filename', \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Input\InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')->addOption('format', null, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Input\InputOption::VALUE_REQUIRED, 'The output format', 'txt')->setHelp(<<%command.name% command lints a XLIFF file and outputs to STDOUT +the first encountered syntax error. + +You can validates XLIFF contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +EOF +); + } + protected function execute(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Input\InputInterface $input, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Output\OutputInterface $output) + { + $io = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Style\SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $this->format = $input->getOption('format'); + $this->displayCorrectFiles = $output->isVerbose(); + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(\file_get_contents('php://stdin'))]); + } + // @deprecated to be removed in 5.0 + if (!$filenames) { + if (0 !== \ftell(\STDIN)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Exception\RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + @\trigger_error('Piping content from STDIN to the "lint:xliff" command without passing the dash symbol "-" as argument is deprecated since Symfony 4.4.', \E_USER_DEPRECATED); + return $this->display($io, [$this->validate(\file_get_contents('php://stdin'))]); + } + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Exception\RuntimeException(\sprintf('File or directory "%s" is not readable.', $filename)); + } + foreach ($this->getFiles($filename) as $file) { + $filesInfo[] = $this->validate(\file_get_contents($file), $file); + } + } + return $this->display($io, $filesInfo); + } + private function validate(string $content, string $file = null) : array + { + $errors = []; + // Avoid: Warning DOMDocument::loadXML(): Empty string supplied as input + if ('' === \trim($content)) { + return ['file' => $file, 'valid' => \true]; + } + $internal = \libxml_use_internal_errors(\true); + $document = new \DOMDocument(); + $document->loadXML($content); + if (null !== ($targetLanguage = $this->getTargetLanguageFromFile($document))) { + $normalizedLocale = \preg_quote(\str_replace('-', '_', $targetLanguage), '/'); + // strict file names require translation files to be named '____.locale.xlf' + // otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed + // also, the regexp matching must be case-insensitive, as defined for 'target-language' values + // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language + $expectedFilenamePattern = $this->requireStrictFileNames ? \sprintf('/^.*\\.(?i:%s)\\.(?:xlf|xliff)/', $normalizedLocale) : \sprintf('/^(?:.*\\.(?i:%s)|(?i:%s)\\..*)\\.(?:xlf|xliff)/', $normalizedLocale, $normalizedLocale); + if (0 === \preg_match($expectedFilenamePattern, \basename($file))) { + $errors[] = ['line' => -1, 'column' => -1, 'message' => \sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', \basename($file), $targetLanguage)]; + } + } + foreach (\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Util\XliffUtils::validateSchema($document) as $xmlError) { + $errors[] = ['line' => $xmlError['line'], 'column' => $xmlError['column'], 'message' => $xmlError['message']]; + } + \libxml_clear_errors(); + \libxml_use_internal_errors($internal); + return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors]; + } + private function display(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Style\SymfonyStyle $io, array $files) + { + switch ($this->format) { + case 'txt': + return $this->displayTxt($io, $files); + case 'json': + return $this->displayJson($io, $files); + default: + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Command\InvalidArgumentException(\sprintf('The format "%s" is not supported.', $this->format)); + } + } + private function displayTxt(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Style\SymfonyStyle $io, array $filesInfo) + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK' . ($info['file'] ? \sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR ' . ($info['file'] ? \sprintf(' in %s', $info['file']) : '')); + $io->listing(\array_map(function ($error) { + // general document errors have a '-1' line number + return -1 === $error['line'] ? $error['message'] : \sprintf('Line %d, Column %d: %s', $error['line'], $error['column'], $error['message']); + }, $info['messages'])); + } + } + if (0 === $erroredFiles) { + $io->success(\sprintf('All %d XLIFF files contain valid syntax.', $countFiles)); + } else { + $io->warning(\sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles)); + } + return \min($erroredFiles, 1); + } + private function displayJson(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Console\Style\SymfonyStyle $io, array $filesInfo) + { + $errors = 0; + \array_walk($filesInfo, function (&$v) use(&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + }); + $io->writeln(\json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + return \min($errors, 1); + } + private function getFiles(string $fileOrDirectory) + { + if (\is_file($fileOrDirectory)) { + (yield new \SplFileInfo($fileOrDirectory)); + return; + } + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) { + continue; + } + (yield $file); + } + } + private function getDirectoryIterator(string $directory) + { + $default = function ($directory) { + return new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), \RecursiveIteratorIterator::LEAVES_ONLY); + }; + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + return $default($directory); + } + private function isReadable(string $fileOrDirectory) + { + $default = function ($fileOrDirectory) { + return \is_readable($fileOrDirectory); + }; + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + return $default($fileOrDirectory); + } + private function getTargetLanguageFromFile(\DOMDocument $xliffContents) : ?string + { + foreach ($xliffContents->getElementsByTagName('file')[0]->attributes ?? [] as $attribute) { + if ('target-language' === $attribute->nodeName) { + return $attribute->nodeValue; + } + } + return null; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php b/classes/Utilities/Misc/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php new file mode 100755 index 00000000..5eef07fb --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollector; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpFoundation\Request; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpFoundation\Response; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpKernel\DataCollector\DataCollector; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator; +/** + * @author Abdellatif Ait boudad + * + * @final since Symfony 4.4 + */ +class TranslationDataCollector extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpKernel\DataCollector\DataCollector implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface +{ + private $translator; + public function __construct(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator $translator) + { + $this->translator = $translator; + } + /** + * {@inheritdoc} + */ + public function lateCollect() + { + $messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages()); + $this->data += $this->computeCount($messages); + $this->data['messages'] = $messages; + $this->data = $this->cloneVar($this->data); + } + /** + * {@inheritdoc} + * + * @param \Throwable|null $exception + */ + public function collect(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpFoundation\Request $request, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpFoundation\Response $response) + { + $this->data['locale'] = $this->translator->getLocale(); + $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); + } + /** + * {@inheritdoc} + */ + public function reset() + { + $this->data = []; + } + /** + * @return array|Data + */ + public function getMessages() + { + return isset($this->data['messages']) ? $this->data['messages'] : []; + } + /** + * @return int + */ + public function getCountMissings() + { + return isset($this->data[\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator::MESSAGE_MISSING]) ? $this->data[\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator::MESSAGE_MISSING] : 0; + } + /** + * @return int + */ + public function getCountFallbacks() + { + return isset($this->data[\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK]) ? $this->data[\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] : 0; + } + /** + * @return int + */ + public function getCountDefines() + { + return isset($this->data[\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator::MESSAGE_DEFINED]) ? $this->data[\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator::MESSAGE_DEFINED] : 0; + } + public function getLocale() + { + return !empty($this->data['locale']) ? $this->data['locale'] : null; + } + /** + * @internal since Symfony 4.2 + */ + public function getFallbackLocales() + { + return isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0 ? $this->data['fallback_locales'] : []; + } + /** + * {@inheritdoc} + */ + public function getName() + { + return 'translation'; + } + private function sanitizeCollectedMessages(array $messages) + { + $result = []; + foreach ($messages as $key => $message) { + $messageId = $message['locale'] . $message['domain'] . $message['id']; + if (!isset($result[$messageId])) { + $message['count'] = 1; + $message['parameters'] = !empty($message['parameters']) ? [$message['parameters']] : []; + $messages[$key]['translation'] = $this->sanitizeString($message['translation']); + $result[$messageId] = $message; + } else { + if (!empty($message['parameters'])) { + $result[$messageId]['parameters'][] = $message['parameters']; + } + ++$result[$messageId]['count']; + } + unset($messages[$key]); + } + return $result; + } + private function computeCount(array $messages) + { + $count = [\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator::MESSAGE_DEFINED => 0, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator::MESSAGE_MISSING => 0, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK => 0]; + foreach ($messages as $message) { + ++$count[$message['state']]; + } + return $count; + } + private function sanitizeString(string $string, int $length = 80) + { + $string = \trim(\preg_replace('/\\s+/', ' ', $string)); + if (\false !== ($encoding = \mb_detect_encoding($string, null, \true))) { + if (\mb_strlen($string, $encoding) > $length) { + return \mb_substr($string, 0, $length - 3, $encoding) . '...'; + } + } elseif (\strlen($string) > $length) { + return \substr($string, 0, $length - 3) . '...'; + } + return $string; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/DataCollectorTranslator.php b/classes/Utilities/Misc/Symfony/Component/Translation/DataCollectorTranslator.php new file mode 100755 index 00000000..a3792543 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/DataCollectorTranslator.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\LocaleAwareInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface; +/** + * @author Abdellatif Ait boudad + */ +class DataCollectorTranslator implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorBagInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface +{ + const MESSAGE_DEFINED = 0; + const MESSAGE_MISSING = 1; + const MESSAGE_EQUALS_FALLBACK = 2; + /** + * @var TranslatorInterface|TranslatorBagInterface + */ + private $translator; + private $messages = []; + /** + * @param TranslatorInterface $translator The translator must implement TranslatorBagInterface + */ + public function __construct($translator) + { + if (!$translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface && !$translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface) { + throw new \TypeError(\sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); + } + if (!$translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorBagInterface || !$translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\LocaleAwareInterface) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', \get_class($translator))); + } + $this->translator = $translator; + } + /** + * {@inheritdoc} + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null) + { + $trans = $this->translator->trans($id, $parameters, $domain, $locale); + $this->collectMessage($locale, $domain, $id, $trans, $parameters); + return $trans; + } + /** + * {@inheritdoc} + * + * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter + */ + public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null) + { + if ($this->translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface) { + $trans = $this->translator->trans($id, ['%count%' => $number] + $parameters, $domain, $locale); + } else { + $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); + } + $this->collectMessage($locale, $domain, $id, $trans, ['%count%' => $number] + $parameters); + return $trans; + } + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->translator->setLocale($locale); + } + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->translator->getLocale(); + } + /** + * {@inheritdoc} + */ + public function getCatalogue($locale = null) + { + return $this->translator->getCatalogue($locale); + } + /** + * {@inheritdoc} + */ + public function warmUp($cacheDir) + { + if ($this->translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface) { + $this->translator->warmUp($cacheDir); + } + } + /** + * Gets the fallback locales. + * + * @return array The fallback locales + */ + public function getFallbackLocales() + { + if ($this->translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Translator || \method_exists($this->translator, 'getFallbackLocales')) { + return $this->translator->getFallbackLocales(); + } + return []; + } + /** + * Passes through all unknown calls onto the translator object. + */ + public function __call($method, $args) + { + return $this->translator->{$method}(...$args); + } + /** + * @return array + */ + public function getCollectedMessages() + { + return $this->messages; + } + private function collectMessage(?string $locale, ?string $domain, ?string $id, string $translation, ?array $parameters = []) + { + if (null === $domain) { + $domain = 'messages'; + } + $id = (string) $id; + $catalogue = $this->translator->getCatalogue($locale); + $locale = $catalogue->getLocale(); + $fallbackLocale = null; + if ($catalogue->defines($id, $domain)) { + $state = self::MESSAGE_DEFINED; + } elseif ($catalogue->has($id, $domain)) { + $state = self::MESSAGE_EQUALS_FALLBACK; + $fallbackCatalogue = $catalogue->getFallbackCatalogue(); + while ($fallbackCatalogue) { + if ($fallbackCatalogue->defines($id, $domain)) { + $fallbackLocale = $fallbackCatalogue->getLocale(); + break; + } + $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); + } + } else { + $state = self::MESSAGE_MISSING; + } + $this->messages[] = ['locale' => $locale, 'fallbackLocale' => $fallbackLocale, 'domain' => $domain, 'id' => $id, 'translation' => $translation, 'parameters' => $parameters, 'state' => $state, 'transChoiceNumber' => isset($parameters['%count%']) && \is_numeric($parameters['%count%']) ? $parameters['%count%'] : null]; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslationDumperPass.php b/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslationDumperPass.php new file mode 100755 index 00000000..32533e27 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslationDumperPass.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DependencyInjection; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ContainerBuilder; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference; +/** + * Adds tagged translation.formatter services to translation writer. + */ +class TranslationDumperPass implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface +{ + private $writerServiceId; + private $dumperTag; + public function __construct(string $writerServiceId = 'translation.writer', string $dumperTag = 'translation.dumper') + { + $this->writerServiceId = $writerServiceId; + $this->dumperTag = $dumperTag; + } + public function process(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ContainerBuilder $container) + { + if (!$container->hasDefinition($this->writerServiceId)) { + return; + } + $definition = $container->getDefinition($this->writerServiceId); + foreach ($container->findTaggedServiceIds($this->dumperTag, \true) as $id => $attributes) { + $definition->addMethodCall('addDumper', [$attributes[0]['alias'], new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference($id)]); + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslationExtractorPass.php b/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslationExtractorPass.php new file mode 100755 index 00000000..2504c5a5 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslationExtractorPass.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DependencyInjection; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ContainerBuilder; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Exception\RuntimeException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference; +/** + * Adds tagged translation.extractor services to translation extractor. + */ +class TranslationExtractorPass implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface +{ + private $extractorServiceId; + private $extractorTag; + public function __construct(string $extractorServiceId = 'translation.extractor', string $extractorTag = 'translation.extractor') + { + $this->extractorServiceId = $extractorServiceId; + $this->extractorTag = $extractorTag; + } + public function process(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ContainerBuilder $container) + { + if (!$container->hasDefinition($this->extractorServiceId)) { + return; + } + $definition = $container->getDefinition($this->extractorServiceId); + foreach ($container->findTaggedServiceIds($this->extractorTag, \true) as $id => $attributes) { + if (!isset($attributes[0]['alias'])) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Exception\RuntimeException(\sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id)); + } + $definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference($id)]); + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php b/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php new file mode 100755 index 00000000..ad641516 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DependencyInjection; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ContainerBuilder; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference; +class TranslatorPass implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface +{ + private $translatorServiceId; + private $readerServiceId; + private $loaderTag; + private $debugCommandServiceId; + private $updateCommandServiceId; + public function __construct(string $translatorServiceId = 'translator.default', string $readerServiceId = 'translation.reader', string $loaderTag = 'translation.loader', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update') + { + $this->translatorServiceId = $translatorServiceId; + $this->readerServiceId = $readerServiceId; + $this->loaderTag = $loaderTag; + $this->debugCommandServiceId = $debugCommandServiceId; + $this->updateCommandServiceId = $updateCommandServiceId; + } + public function process(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ContainerBuilder $container) + { + if (!$container->hasDefinition($this->translatorServiceId)) { + return; + } + $loaders = []; + $loaderRefs = []; + foreach ($container->findTaggedServiceIds($this->loaderTag, \true) as $id => $attributes) { + $loaderRefs[$id] = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference($id); + $loaders[$id][] = $attributes[0]['alias']; + if (isset($attributes[0]['legacy-alias'])) { + $loaders[$id][] = $attributes[0]['legacy-alias']; + } + } + if ($container->hasDefinition($this->readerServiceId)) { + $definition = $container->getDefinition($this->readerServiceId); + foreach ($loaders as $id => $formats) { + foreach ($formats as $format) { + $definition->addMethodCall('addLoader', [$format, $loaderRefs[$id]]); + } + } + } + $container->findDefinition($this->translatorServiceId)->replaceArgument(0, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass::register($container, $loaderRefs))->replaceArgument(3, $loaders); + if (!$container->hasParameter('twig.default_path')) { + return; + } + $paths = \array_keys($container->getDefinition('twig.template_iterator')->getArgument(2)); + if ($container->hasDefinition($this->debugCommandServiceId)) { + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(4, $container->getParameter('twig.default_path')); + if (\count($definition->getArguments()) > 6) { + $definition->replaceArgument(6, $paths); + } + } + if ($container->hasDefinition($this->updateCommandServiceId)) { + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(5, $container->getParameter('twig.default_path')); + if (\count($definition->getArguments()) > 7) { + $definition->replaceArgument(7, $paths); + } + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslatorPathsPass.php b/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslatorPathsPass.php new file mode 100755 index 00000000..38ddbf4f --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/DependencyInjection/TranslatorPathsPass.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\DependencyInjection; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ContainerBuilder; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Definition; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ServiceLocator; +/** + * @author Yonel Ceruto + */ +class TranslatorPathsPass extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass +{ + private $translatorServiceId; + private $debugCommandServiceId; + private $updateCommandServiceId; + private $resolverServiceId; + private $level = 0; + private $paths = []; + private $definitions = []; + private $controllers = []; + public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update', string $resolverServiceId = 'argument_resolver.service') + { + $this->translatorServiceId = $translatorServiceId; + $this->debugCommandServiceId = $debugCommandServiceId; + $this->updateCommandServiceId = $updateCommandServiceId; + $this->resolverServiceId = $resolverServiceId; + } + public function process(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ContainerBuilder $container) + { + if (!$container->hasDefinition($this->translatorServiceId)) { + return; + } + foreach ($this->findControllerArguments($container) as $controller => $argument) { + $id = \substr($controller, 0, \strpos($controller, ':') ?: \strlen($controller)); + if ($container->hasDefinition($id)) { + list($locatorRef) = $argument->getValues(); + $this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = \true; + } + } + try { + parent::process($container); + $paths = []; + foreach ($this->paths as $class => $_) { + if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) { + $paths[] = $r->getFileName(); + } + } + if ($paths) { + if ($container->hasDefinition($this->debugCommandServiceId)) { + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(6, \array_merge($definition->getArgument(6), $paths)); + } + if ($container->hasDefinition($this->updateCommandServiceId)) { + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(7, \array_merge($definition->getArgument(7), $paths)); + } + } + } finally { + $this->level = 0; + $this->paths = []; + $this->definitions = []; + } + } + protected function processValue($value, $isRoot = \false) + { + if ($value instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference) { + if ((string) $value === $this->translatorServiceId) { + for ($i = $this->level - 1; $i >= 0; --$i) { + $class = $this->definitions[$i]->getClass(); + if (\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ServiceLocator::class === $class) { + if (!isset($this->controllers[$this->currentId])) { + continue; + } + foreach ($this->controllers[$this->currentId] as $class => $_) { + $this->paths[$class] = \true; + } + } else { + $this->paths[$class] = \true; + } + break; + } + } + return $value; + } + if ($value instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Definition) { + $this->definitions[$this->level++] = $value; + $value = parent::processValue($value, $isRoot); + unset($this->definitions[--$this->level]); + return $value; + } + return parent::processValue($value, $isRoot); + } + private function findControllerArguments(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\ContainerBuilder $container) : array + { + if ($container->hasDefinition($this->resolverServiceId)) { + $argument = $container->getDefinition($this->resolverServiceId)->getArgument(0); + if ($argument instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference) { + $argument = $container->getDefinition($argument); + } + return $argument->getArgument(0); + } + if ($container->hasDefinition('debug.' . $this->resolverServiceId)) { + $argument = $container->getDefinition('debug.' . $this->resolverServiceId)->getArgument(0); + if ($argument instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference) { + $argument = $container->getDefinition($argument); + } + $argument = $argument->getArgument(0); + if ($argument instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\DependencyInjection\Reference) { + $argument = $container->getDefinition($argument); + } + return $argument->getArgument(0); + } + return []; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/CsvFileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/CsvFileDumper.php new file mode 100755 index 00000000..cf4e9c39 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/CsvFileDumper.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * CsvFileDumper generates a csv formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class CsvFileDumper extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\FileDumper +{ + private $delimiter = ';'; + private $enclosure = '"'; + /** + * {@inheritdoc} + */ + public function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []) + { + $handle = \fopen('php://memory', 'r+b'); + foreach ($messages->all($domain) as $source => $target) { + \fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure); + } + \rewind($handle); + $output = \stream_get_contents($handle); + \fclose($handle); + return $output; + } + /** + * Sets the delimiter and escape character for CSV. + * + * @param string $delimiter Delimiter character + * @param string $enclosure Enclosure character + */ + public function setCsvControl($delimiter = ';', $enclosure = '"') + { + $this->delimiter = $delimiter; + $this->enclosure = $enclosure; + } + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'csv'; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/DumperInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/DumperInterface.php new file mode 100755 index 00000000..441b6435 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/DumperInterface.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * DumperInterface is the interface implemented by all translation dumpers. + * There is no common option. + * + * @author Michel Salib + */ +interface DumperInterface +{ + /** + * Dumps the message catalogue. + * + * @param array $options Options that are used by the dumper + */ + public function dump(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $options = []); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/FileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/FileDumper.php new file mode 100755 index 00000000..9c944e91 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/FileDumper.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\RuntimeException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s). + * + * Options: + * - path (mandatory): the directory where the files should be saved + * + * @author Michel Salib + */ +abstract class FileDumper implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\DumperInterface +{ + /** + * A template for the relative paths to files. + * + * @var string + */ + protected $relativePathTemplate = '%domain%.%locale%.%extension%'; + /** + * Sets the template for the relative paths to files. + * + * @param string $relativePathTemplate A template for the relative paths to files + */ + public function setRelativePathTemplate($relativePathTemplate) + { + $this->relativePathTemplate = $relativePathTemplate; + } + /** + * Sets backup flag. + * + * @param bool $backup + * + * @deprecated since Symfony 4.1 + */ + public function setBackup($backup) + { + @\trigger_error(\sprintf('The "%s()" method is deprecated since Symfony 4.1.', __METHOD__), \E_USER_DEPRECATED); + if (\false !== $backup) { + throw new \LogicException('The backup feature is no longer supported.'); + } + } + /** + * {@inheritdoc} + */ + public function dump(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $options = []) + { + if (!\array_key_exists('path', $options)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException('The file dumper needs a path option.'); + } + // save a file for each domain + foreach ($messages->getDomains() as $domain) { + $fullpath = $options['path'] . '/' . $this->getRelativePath($domain, $messages->getLocale()); + if (!\file_exists($fullpath)) { + $directory = \dirname($fullpath); + if (!\file_exists($directory) && !@\mkdir($directory, 0777, \true)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\RuntimeException(\sprintf('Unable to create directory "%s".', $directory)); + } + } + $intlDomain = $domain . \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue::INTL_DOMAIN_SUFFIX; + $intlMessages = $messages->all($intlDomain); + if ($intlMessages) { + $intlPath = $options['path'] . '/' . $this->getRelativePath($intlDomain, $messages->getLocale()); + \file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options)); + $messages->replace([], $intlDomain); + try { + if ($messages->all($domain)) { + \file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } + continue; + } finally { + $messages->replace($intlMessages, $intlDomain); + } + } + \file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } + } + /** + * Transforms a domain of a message catalogue to its string representation. + * + * @param string $domain + * + * @return string representation + */ + public abstract function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []); + /** + * Gets the file extension of the dumper. + * + * @return string file extension + */ + protected abstract function getExtension(); + /** + * Gets the relative file path using the template. + */ + private function getRelativePath(string $domain, string $locale) : string + { + return \strtr($this->relativePathTemplate, ['%domain%' => $domain, '%locale%' => $locale, '%extension%' => $this->getExtension()]); + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/IcuResFileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/IcuResFileDumper.php new file mode 100755 index 00000000..ce1e9d54 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/IcuResFileDumper.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * IcuResDumper generates an ICU ResourceBundle formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class IcuResFileDumper extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\FileDumper +{ + /** + * {@inheritdoc} + */ + protected $relativePathTemplate = '%domain%/%locale%.%extension%'; + /** + * {@inheritdoc} + */ + public function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []) + { + $data = $indexes = $resources = ''; + foreach ($messages->all($domain) as $source => $target) { + $indexes .= \pack('v', \strlen($data) + 28); + $data .= $source . "\0"; + } + $data .= $this->writePadding($data); + $keyTop = $this->getPosition($data); + foreach ($messages->all($domain) as $source => $target) { + $resources .= \pack('V', $this->getPosition($data)); + $data .= \pack('V', \strlen($target)) . \mb_convert_encoding($target . "\0", 'UTF-16LE', 'UTF-8') . $this->writePadding($data); + } + $resOffset = $this->getPosition($data); + $data .= \pack('v', \count($messages->all($domain))) . $indexes . $this->writePadding($data) . $resources; + $bundleTop = $this->getPosition($data); + $root = \pack( + 'V7', + $resOffset + (2 << 28), + // Resource Offset + Resource Type + 6, + // Index length + $keyTop, + // Index keys top + $bundleTop, + // Index resources top + $bundleTop, + // Index bundle top + \count($messages->all($domain)), + // Index max table length + 0 + ); + $header = \pack( + 'vC2v4C12@32', + 32, + // Header size + 0xda, + 0x27, + // Magic number 1 and 2 + 20, + 0, + 0, + 2, + // Rest of the header, ..., Size of a char + 0x52, + 0x65, + 0x73, + 0x42, + // Data format identifier + 1, + 2, + 0, + 0, + // Data version + 1, + 4, + 0, + 0 + ); + return $header . $root . $data; + } + private function writePadding(string $data) : ?string + { + $padding = \strlen($data) % 4; + return $padding ? \str_repeat("ª", 4 - $padding) : null; + } + private function getPosition(string $data) + { + return (\strlen($data) + 28) / 4; + } + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'res'; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/IniFileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/IniFileDumper.php new file mode 100755 index 00000000..5781c267 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/IniFileDumper.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * IniFileDumper generates an ini formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class IniFileDumper extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []) + { + $output = ''; + foreach ($messages->all($domain) as $source => $target) { + $escapeTarget = \str_replace('"', '\\"', $target); + $output .= $source . '="' . $escapeTarget . "\"\n"; + } + return $output; + } + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'ini'; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/JsonFileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/JsonFileDumper.php new file mode 100755 index 00000000..f6b248bf --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/JsonFileDumper.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * JsonFileDumper generates an json formatted string representation of a message catalogue. + * + * @author singles + */ +class JsonFileDumper extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []) + { + $flags = $options['json_encoding'] ?? \JSON_PRETTY_PRINT; + return \json_encode($messages->all($domain), $flags); + } + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'json'; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/MoFileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/MoFileDumper.php new file mode 100755 index 00000000..9ac6d82c --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/MoFileDumper.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\MoFileLoader; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * MoFileDumper generates a gettext formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class MoFileDumper extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []) + { + $sources = $targets = $sourceOffsets = $targetOffsets = ''; + $offsets = []; + $size = 0; + foreach ($messages->all($domain) as $source => $target) { + $offsets[] = \array_map('strlen', [$sources, $source, $targets, $target]); + $sources .= "\0" . $source; + $targets .= "\0" . $target; + ++$size; + } + $header = ['magicNumber' => \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\MoFileLoader::MO_LITTLE_ENDIAN_MAGIC, 'formatRevision' => 0, 'count' => $size, 'offsetId' => \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\MoFileLoader::MO_HEADER_SIZE, 'offsetTranslated' => \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\MoFileLoader::MO_HEADER_SIZE + 8 * $size, 'sizeHashes' => 0, 'offsetHashes' => \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\MoFileLoader::MO_HEADER_SIZE + 16 * $size]; + $sourcesSize = \strlen($sources); + $sourcesStart = $header['offsetHashes'] + 1; + foreach ($offsets as $offset) { + $sourceOffsets .= $this->writeLong($offset[1]) . $this->writeLong($offset[0] + $sourcesStart); + $targetOffsets .= $this->writeLong($offset[3]) . $this->writeLong($offset[2] + $sourcesStart + $sourcesSize); + } + $output = \implode('', \array_map([$this, 'writeLong'], $header)) . $sourceOffsets . $targetOffsets . $sources . $targets; + return $output; + } + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'mo'; + } + private function writeLong($str) : string + { + return \pack('V*', $str); + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/PhpFileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/PhpFileDumper.php new file mode 100755 index 00000000..67bc119f --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/PhpFileDumper.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * PhpFileDumper generates PHP files from a message catalogue. + * + * @author Michel Salib + */ +class PhpFileDumper extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []) + { + return "all($domain), \true) . ";\n"; + } + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'php'; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/PoFileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/PoFileDumper.php new file mode 100755 index 00000000..203408c3 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/PoFileDumper.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * PoFileDumper generates a gettext formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class PoFileDumper extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []) + { + $output = 'msgid ""' . "\n"; + $output .= 'msgstr ""' . "\n"; + $output .= '"Content-Type: text/plain; charset=UTF-8\\n"' . "\n"; + $output .= '"Content-Transfer-Encoding: 8bit\\n"' . "\n"; + $output .= '"Language: ' . $messages->getLocale() . '\\n"' . "\n"; + $output .= "\n"; + $newLine = \false; + foreach ($messages->all($domain) as $source => $target) { + if ($newLine) { + $output .= "\n"; + } else { + $newLine = \true; + } + $metadata = $messages->getMetadata($source, $domain); + if (isset($metadata['comments'])) { + $output .= $this->formatComments($metadata['comments']); + } + if (isset($metadata['flags'])) { + $output .= $this->formatComments(\implode(',', (array) $metadata['flags']), ','); + } + if (isset($metadata['sources'])) { + $output .= $this->formatComments(\implode(' ', (array) $metadata['sources']), ':'); + } + $sourceRules = $this->getStandardRules($source); + $targetRules = $this->getStandardRules($target); + if (2 == \count($sourceRules) && $targetRules !== []) { + $output .= \sprintf('msgid "%s"' . "\n", $this->escape($sourceRules[0])); + $output .= \sprintf('msgid_plural "%s"' . "\n", $this->escape($sourceRules[1])); + foreach ($targetRules as $i => $targetRule) { + $output .= \sprintf('msgstr[%d] "%s"' . "\n", $i, $this->escape($targetRule)); + } + } else { + $output .= \sprintf('msgid "%s"' . "\n", $this->escape($source)); + $output .= \sprintf('msgstr "%s"' . "\n", $this->escape($target)); + } + } + return $output; + } + private function getStandardRules(string $id) + { + // Partly copied from TranslatorTrait::trans. + $parts = []; + if (\preg_match('/^\\|++$/', $id)) { + $parts = \explode('|', $id); + } elseif (\preg_match_all('/(?:\\|\\||[^\\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + $standardRules = []; + foreach ($parts as $part) { + $part = \trim(\str_replace('||', '|', $part)); + if (\preg_match($intervalRegexp, $part)) { + // Explicit rule is not a standard rule. + return []; + } else { + $standardRules[] = $part; + } + } + return $standardRules; + } + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'po'; + } + private function escape(string $str) : string + { + return \addcslashes($str, "\0..\37\"\\"); + } + private function formatComments($comments, string $prefix = '') : ?string + { + $output = null; + foreach ((array) $comments as $comment) { + $output .= \sprintf('#%s %s' . "\n", $prefix, $comment); + } + return $output; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/QtFileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/QtFileDumper.php new file mode 100755 index 00000000..9cdd207e --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/QtFileDumper.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * QtFileDumper generates ts files from a message catalogue. + * + * @author Benjamin Eberlei + */ +class QtFileDumper extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []) + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = \true; + $ts = $dom->appendChild($dom->createElement('TS')); + $context = $ts->appendChild($dom->createElement('context')); + $context->appendChild($dom->createElement('name', $domain)); + foreach ($messages->all($domain) as $source => $target) { + $message = $context->appendChild($dom->createElement('message')); + $metadata = $messages->getMetadata($source, $domain); + if (isset($metadata['sources'])) { + foreach ((array) $metadata['sources'] as $location) { + $loc = \explode(':', $location, 2); + $location = $message->appendChild($dom->createElement('location')); + $location->setAttribute('filename', $loc[0]); + if (isset($loc[1])) { + $location->setAttribute('line', $loc[1]); + } + } + } + $message->appendChild($dom->createElement('source', $source)); + $message->appendChild($dom->createElement('translation', $target)); + } + return $dom->saveXML(); + } + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'ts'; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/XliffFileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/XliffFileDumper.php new file mode 100755 index 00000000..82c535bb --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/XliffFileDumper.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * XliffFileDumper generates xliff files from a message catalogue. + * + * @author Michel Salib + */ +class XliffFileDumper extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []) + { + $xliffVersion = '1.2'; + if (\array_key_exists('xliff_version', $options)) { + $xliffVersion = $options['xliff_version']; + } + if (\array_key_exists('default_locale', $options)) { + $defaultLocale = $options['default_locale']; + } else { + $defaultLocale = \Locale::getDefault(); + } + if ('1.2' === $xliffVersion) { + return $this->dumpXliff1($defaultLocale, $messages, $domain, $options); + } + if ('2.0' === $xliffVersion) { + return $this->dumpXliff2($defaultLocale, $messages, $domain); + } + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion)); + } + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'xlf'; + } + private function dumpXliff1(string $defaultLocale, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, ?string $domain, array $options = []) + { + $toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony']; + if (\array_key_exists('tool_info', $options)) { + $toolInfo = \array_merge($toolInfo, $options['tool_info']); + } + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = \true; + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('version', '1.2'); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2'); + $xliffFile = $xliff->appendChild($dom->createElement('file')); + $xliffFile->setAttribute('source-language', \str_replace('_', '-', $defaultLocale)); + $xliffFile->setAttribute('target-language', \str_replace('_', '-', $messages->getLocale())); + $xliffFile->setAttribute('datatype', 'plaintext'); + $xliffFile->setAttribute('original', 'file.ext'); + $xliffHead = $xliffFile->appendChild($dom->createElement('header')); + $xliffTool = $xliffHead->appendChild($dom->createElement('tool')); + foreach ($toolInfo as $id => $value) { + $xliffTool->setAttribute($id, $value); + } + $xliffBody = $xliffFile->appendChild($dom->createElement('body')); + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('trans-unit'); + $translation->setAttribute('id', \strtr(\substr(\base64_encode(\hash('sha256', $source, \true)), 0, 7), '/+', '._')); + $translation->setAttribute('resname', $source); + $s = $translation->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + // Does the target contain characters requiring a CDATA section? + $text = 1 === \preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + $targetElement = $dom->createElement('target'); + $metadata = $messages->getMetadata($source, $domain); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $translation->appendChild($targetElement); + $t->appendChild($text); + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + foreach ($metadata['notes'] as $note) { + if (!isset($note['content'])) { + continue; + } + $n = $translation->appendChild($dom->createElement('note')); + $n->appendChild($dom->createTextNode($note['content'])); + if (isset($note['priority'])) { + $n->setAttribute('priority', $note['priority']); + } + if (isset($note['from'])) { + $n->setAttribute('from', $note['from']); + } + } + } + $xliffBody->appendChild($translation); + } + return $dom->saveXML(); + } + private function dumpXliff2(string $defaultLocale, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, ?string $domain) + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = \true; + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0'); + $xliff->setAttribute('version', '2.0'); + $xliff->setAttribute('srcLang', \str_replace('_', '-', $defaultLocale)); + $xliff->setAttribute('trgLang', \str_replace('_', '-', $messages->getLocale())); + $xliffFile = $xliff->appendChild($dom->createElement('file')); + if (\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue::INTL_DOMAIN_SUFFIX === \substr($domain, -($suffixLength = \strlen(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue::INTL_DOMAIN_SUFFIX)))) { + $xliffFile->setAttribute('id', \substr($domain, 0, -$suffixLength) . '.' . $messages->getLocale()); + } else { + $xliffFile->setAttribute('id', $domain . '.' . $messages->getLocale()); + } + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('unit'); + $translation->setAttribute('id', \strtr(\substr(\base64_encode(\hash('sha256', $source, \true)), 0, 7), '/+', '._')); + $name = $source; + if (\strlen($source) > 80) { + $name = \substr(\md5($source), -7); + } + $translation->setAttribute('name', $name); + $metadata = $messages->getMetadata($source, $domain); + // Add notes section + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + $notesElement = $dom->createElement('notes'); + foreach ($metadata['notes'] as $note) { + $n = $dom->createElement('note'); + $n->appendChild($dom->createTextNode(isset($note['content']) ? $note['content'] : '')); + unset($note['content']); + foreach ($note as $name => $value) { + $n->setAttribute($name, $value); + } + $notesElement->appendChild($n); + } + $translation->appendChild($notesElement); + } + $segment = $translation->appendChild($dom->createElement('segment')); + $s = $segment->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + // Does the target contain characters requiring a CDATA section? + $text = 1 === \preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + $targetElement = $dom->createElement('target'); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $segment->appendChild($targetElement); + $t->appendChild($text); + $xliffFile->appendChild($translation); + } + return $dom->saveXML(); + } + private function hasMetadataArrayInfo(string $key, array $metadata = null) : bool + { + return null !== $metadata && \array_key_exists($key, $metadata) && ($metadata[$key] instanceof \Traversable || \is_array($metadata[$key])); + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/YamlFileDumper.php b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/YamlFileDumper.php new file mode 100755 index 00000000..424ae11d --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Dumper/YamlFileDumper.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Util\ArrayConverter; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Yaml\Yaml; +/** + * YamlFileDumper generates yaml files from a message catalogue. + * + * @author Michel Salib + */ +class YamlFileDumper extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\FileDumper +{ + private $extension; + public function __construct(string $extension = 'yml') + { + $this->extension = $extension; + } + /** + * {@inheritdoc} + */ + public function formatCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $messages, $domain, array $options = []) + { + if (!\class_exists('ILAB\\MediaCloud\\Utilities\\Misc\\Symfony\\Component\\Yaml\\Yaml')) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.'); + } + $data = $messages->all($domain); + if (isset($options['as_tree']) && $options['as_tree']) { + $data = \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Util\ArrayConverter::expandToTree($data); + } + if (isset($options['inline']) && ($inline = (int) $options['inline']) > 0) { + return \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Yaml\Yaml::dump($data, $inline); + } + return \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Yaml\Yaml::dump($data); + } + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return $this->extension; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Exception/ExceptionInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/ExceptionInterface.php new file mode 100755 index 00000000..40b0d20b --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/ExceptionInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Exception/InvalidArgumentException.php b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/InvalidArgumentException.php new file mode 100755 index 00000000..9b3ba98b --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/InvalidArgumentException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception; + +/** + * Base InvalidArgumentException for the Translation component. + * + * @author Abdellatif Ait boudad + */ +class InvalidArgumentException extends \InvalidArgumentException implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\ExceptionInterface +{ +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Exception/InvalidResourceException.php b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/InvalidResourceException.php new file mode 100755 index 00000000..a55e246b --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/InvalidResourceException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception; + +/** + * Thrown when a resource cannot be loaded. + * + * @author Fabien Potencier + */ +class InvalidResourceException extends \InvalidArgumentException implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\ExceptionInterface +{ +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Exception/LogicException.php b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/LogicException.php new file mode 100755 index 00000000..9e3dd5cf --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/LogicException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception; + +/** + * Base LogicException for Translation component. + * + * @author Abdellatif Ait boudad + */ +class LogicException extends \LogicException implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\ExceptionInterface +{ +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Exception/NotFoundResourceException.php b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/NotFoundResourceException.php new file mode 100755 index 00000000..1c3a3148 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/NotFoundResourceException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception; + +/** + * Thrown when a resource does not exist. + * + * @author Fabien Potencier + */ +class NotFoundResourceException extends \InvalidArgumentException implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\ExceptionInterface +{ +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Exception/RuntimeException.php b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/RuntimeException.php new file mode 100755 index 00000000..1d624133 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Exception/RuntimeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception; + +/** + * Base RuntimeException for the Translation component. + * + * @author Abdellatif Ait boudad + */ +class RuntimeException extends \RuntimeException implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\ExceptionInterface +{ +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/AbstractFileExtractor.php b/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/AbstractFileExtractor.php new file mode 100755 index 00000000..8169e7cb --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/AbstractFileExtractor.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +/** + * Base class used by classes that extract translation messages from files. + * + * @author Marcos D. Sánchez + */ +abstract class AbstractFileExtractor +{ + /** + * @param string|iterable $resource Files, a file or a directory + * + * @return iterable + */ + protected function extractFiles($resource) + { + if (\is_iterable($resource)) { + $files = []; + foreach ($resource as $file) { + if ($this->canBeExtracted($file)) { + $files[] = $this->toSplFileInfo($file); + } + } + } elseif (\is_file($resource)) { + $files = $this->canBeExtracted($resource) ? [$this->toSplFileInfo($resource)] : []; + } else { + $files = $this->extractFromDirectory($resource); + } + return $files; + } + private function toSplFileInfo(string $file) : \SplFileInfo + { + return new \SplFileInfo($file); + } + /** + * @param string $file + * + * @return bool + * + * @throws InvalidArgumentException + */ + protected function isFile($file) + { + if (!\is_file($file)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('The "%s" file does not exist.', $file)); + } + return \true; + } + /** + * @param string $file + * + * @return bool + */ + protected abstract function canBeExtracted($file); + /** + * @param string|array $resource Files, a file or a directory + * + * @return iterable files to be extracted + */ + protected abstract function extractFromDirectory($resource); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/ChainExtractor.php b/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/ChainExtractor.php new file mode 100755 index 00000000..c1d32333 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/ChainExtractor.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * ChainExtractor extracts translation messages from template files. + * + * @author Michel Salib + */ +class ChainExtractor implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor\ExtractorInterface +{ + /** + * The extractors. + * + * @var ExtractorInterface[] + */ + private $extractors = []; + /** + * Adds a loader to the translation extractor. + * + * @param string $format The format of the loader + */ + public function addExtractor($format, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor\ExtractorInterface $extractor) + { + $this->extractors[$format] = $extractor; + } + /** + * {@inheritdoc} + */ + public function setPrefix($prefix) + { + foreach ($this->extractors as $extractor) { + $extractor->setPrefix($prefix); + } + } + /** + * {@inheritdoc} + */ + public function extract($directory, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalogue) + { + foreach ($this->extractors as $extractor) { + $extractor->extract($directory, $catalogue); + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/ExtractorInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/ExtractorInterface.php new file mode 100755 index 00000000..0ad4272c --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/ExtractorInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * Extracts translation messages from a directory or files to the catalogue. + * New found messages are injected to the catalogue using the prefix. + * + * @author Michel Salib + */ +interface ExtractorInterface +{ + /** + * Extracts translation messages from files, a file or a directory to the catalogue. + * + * @param string|array $resource Files, a file or a directory + */ + public function extract($resource, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalogue); + /** + * Sets the prefix that should be used for new found messages. + * + * @param string $prefix The prefix + */ + public function setPrefix($prefix); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/PhpExtractor.php b/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/PhpExtractor.php new file mode 100755 index 00000000..a62361a2 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/PhpExtractor.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Finder\Finder; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * PhpExtractor extracts translation messages from a PHP template. + * + * @author Michel Salib + */ +class PhpExtractor extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor\AbstractFileExtractor implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor\ExtractorInterface +{ + const MESSAGE_TOKEN = 300; + const METHOD_ARGUMENTS_TOKEN = 1000; + const DOMAIN_TOKEN = 1001; + /** + * Prefix for new found message. + * + * @var string + */ + private $prefix = ''; + /** + * The sequence that captures translation messages. + * + * @var array + */ + protected $sequences = [['->', 'trans', '(', self::MESSAGE_TOKEN, ',', self::METHOD_ARGUMENTS_TOKEN, ',', self::DOMAIN_TOKEN], ['->', 'transChoice', '(', self::MESSAGE_TOKEN, ',', self::METHOD_ARGUMENTS_TOKEN, ',', self::METHOD_ARGUMENTS_TOKEN, ',', self::DOMAIN_TOKEN], ['->', 'trans', '(', self::MESSAGE_TOKEN], ['->', 'transChoice', '(', self::MESSAGE_TOKEN]]; + /** + * {@inheritdoc} + */ + public function extract($resource, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalog) + { + $files = $this->extractFiles($resource); + foreach ($files as $file) { + $this->parseTokens(\token_get_all(\file_get_contents($file)), $catalog, $file); + \gc_mem_caches(); + } + } + /** + * {@inheritdoc} + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + /** + * Normalizes a token. + * + * @param mixed $token + * + * @return string|null + */ + protected function normalizeToken($token) + { + if (isset($token[1]) && 'b"' !== $token) { + return $token[1]; + } + return $token; + } + /** + * Seeks to a non-whitespace token. + */ + private function seekToNextRelevantToken(\Iterator $tokenIterator) + { + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if (\T_WHITESPACE !== $t[0]) { + break; + } + } + } + private function skipMethodArgument(\Iterator $tokenIterator) + { + $openBraces = 0; + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if ('[' === $t[0] || '(' === $t[0]) { + ++$openBraces; + } + if (']' === $t[0] || ')' === $t[0]) { + --$openBraces; + } + if (0 === $openBraces && ',' === $t[0] || -1 === $openBraces && ')' === $t[0]) { + break; + } + } + } + /** + * Extracts the message from the iterator while the tokens + * match allowed message tokens. + */ + private function getValue(\Iterator $tokenIterator) + { + $message = ''; + $docToken = ''; + $docPart = ''; + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if ('.' === $t) { + // Concatenate with next token + continue; + } + if (!isset($t[1])) { + break; + } + switch ($t[0]) { + case \T_START_HEREDOC: + $docToken = $t[1]; + break; + case \T_ENCAPSED_AND_WHITESPACE: + case \T_CONSTANT_ENCAPSED_STRING: + if ('' === $docToken) { + $message .= \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor\PhpStringTokenParser::parse($t[1]); + } else { + $docPart = $t[1]; + } + break; + case \T_END_HEREDOC: + $message .= \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor\PhpStringTokenParser::parseDocString($docToken, $docPart); + $docToken = ''; + $docPart = ''; + break; + case \T_WHITESPACE: + break; + default: + break 2; + } + } + return $message; + } + /** + * Extracts trans message from PHP tokens. + * + * @param array $tokens + * @param string $filename + */ + protected function parseTokens($tokens, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalog) + { + if (\func_num_args() < 3 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \ILAB\MediaCloud\Utilities\Misc\PHPUnit\Framework\MockObject\MockObject && !$this instanceof \ILAB\MediaCloud\Utilities\Misc\Prophecy\Prophecy\ProphecySubjectInterface) { + @\trigger_error(\sprintf('The "%s()" method will have a new "string $filename" argument in version 5.0, not defining it is deprecated since Symfony 4.3.', __METHOD__), \E_USER_DEPRECATED); + } + $filename = 2 < \func_num_args() ? \func_get_arg(2) : ''; + $tokenIterator = new \ArrayIterator($tokens); + for ($key = 0; $key < $tokenIterator->count(); ++$key) { + foreach ($this->sequences as $sequence) { + $message = ''; + $domain = 'messages'; + $tokenIterator->seek($key); + foreach ($sequence as $sequenceKey => $item) { + $this->seekToNextRelevantToken($tokenIterator); + if ($this->normalizeToken($tokenIterator->current()) === $item) { + $tokenIterator->next(); + continue; + } elseif (self::MESSAGE_TOKEN === $item) { + $message = $this->getValue($tokenIterator); + if (\count($sequence) === $sequenceKey + 1) { + break; + } + } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) { + $this->skipMethodArgument($tokenIterator); + } elseif (self::DOMAIN_TOKEN === $item) { + $domainToken = $this->getValue($tokenIterator); + if ('' !== $domainToken) { + $domain = $domainToken; + } + break; + } else { + break; + } + } + if ($message) { + $catalog->set($message, $this->prefix . $message, $domain); + $metadata = $catalog->getMetadata($message, $domain) ?? []; + $normalizedFilename = \preg_replace('{[\\\\/]+}', '/', $filename); + $metadata['sources'][] = $normalizedFilename . ':' . $tokens[$key][2]; + $catalog->setMetadata($message, $metadata, $domain); + break; + } + } + } + } + /** + * @param string $file + * + * @return bool + * + * @throws \InvalidArgumentException + */ + protected function canBeExtracted($file) + { + return $this->isFile($file) && 'php' === \pathinfo($file, \PATHINFO_EXTENSION); + } + /** + * {@inheritdoc} + */ + protected function extractFromDirectory($directory) + { + $finder = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Finder\Finder(); + return $finder->files()->name('*.php')->in($directory); + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/PhpStringTokenParser.php b/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/PhpStringTokenParser.php new file mode 100755 index 00000000..5ea95d2b --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Extractor/PhpStringTokenParser.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Extractor; + +/* + * The following is derived from code at http://github.com/nikic/PHP-Parser + * + * Copyright (c) 2011 by Nikita Popov + * + * Some rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * * The names of the contributors may not be used to endorse or + * promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +class PhpStringTokenParser +{ + protected static $replacements = ['\\' => '\\', '$' => '$', 'n' => "\n", 'r' => "\r", 't' => "\t", 'f' => "\f", 'v' => "\v", 'e' => "\33"]; + /** + * Parses a string token. + * + * @param string $str String token content + * + * @return string The parsed string + */ + public static function parse($str) + { + $bLength = 0; + if ('b' === $str[0]) { + $bLength = 1; + } + if ('\'' === $str[$bLength]) { + return \str_replace(['\\\\', '\\\''], ['\\', '\''], \substr($str, $bLength + 1, -1)); + } else { + return self::parseEscapeSequences(\substr($str, $bLength + 1, -1), '"'); + } + } + /** + * Parses escape sequences in strings (all string types apart from single quoted). + * + * @param string $str String without quotes + * @param string|null $quote Quote type + * + * @return string String with escape sequences parsed + */ + public static function parseEscapeSequences($str, $quote) + { + if (null !== $quote) { + $str = \str_replace('\\' . $quote, $quote, $str); + } + return \preg_replace_callback('~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~', [__CLASS__, 'parseCallback'], $str); + } + private static function parseCallback(array $matches) : string + { + $str = $matches[1]; + if (isset(self::$replacements[$str])) { + return self::$replacements[$str]; + } elseif ('x' === $str[0] || 'X' === $str[0]) { + return \chr(\hexdec($str)); + } else { + return \chr(\octdec($str)); + } + } + /** + * Parses a constant doc string. + * + * @param string $startToken Doc string start token content (<< + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter; + +/** + * @author Abdellatif Ait boudad + * + * @deprecated since Symfony 4.2, use MessageFormatterInterface::format() with a %count% parameter instead + */ +interface ChoiceMessageFormatterInterface +{ + /** + * Formats a localized message pattern with given arguments. + * + * @param string $message The message (may also be an object that can be cast to string) + * @param int $number The number to use to find the indice of the message + * @param string $locale The message locale + * @param array $parameters An array of parameters for the message + * + * @return string + */ + public function choiceFormat($message, $number, $locale, array $parameters = []); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/IntlFormatter.php b/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/IntlFormatter.php new file mode 100755 index 00000000..47458210 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/IntlFormatter.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException; +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +class IntlFormatter implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\IntlFormatterInterface +{ + private $hasMessageFormatter; + private $cache = []; + /** + * {@inheritdoc} + */ + public function formatIntl(string $message, string $locale, array $parameters = []) : string + { + // MessageFormatter constructor throws an exception if the message is empty + if ('' === $message) { + return ''; + } + if (!($formatter = $this->cache[$locale][$message] ?? null)) { + if (!($this->hasMessageFormatter ?? ($this->hasMessageFormatter = \class_exists(\MessageFormatter::class)))) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException('Cannot parse message translation: please install the "intl" PHP extension or the "symfony/polyfill-intl-messageformatter" package.'); + } + try { + $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message); + } catch (\IntlException $e) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('Invalid message format (error #%d): %s.', \intl_get_error_code(), \intl_get_error_message()), 0, $e); + } + } + foreach ($parameters as $key => $value) { + if (\in_array($key[0] ?? null, ['%', '{'], \true)) { + unset($parameters[$key]); + $parameters[\trim($key, '%{ }')] = $value; + } + } + if (\false === ($message = $formatter->format($parameters))) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('Unable to format message (error #%s): %s.', $formatter->getErrorCode(), $formatter->getErrorMessage())); + } + return $message; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/IntlFormatterInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/IntlFormatterInterface.php new file mode 100755 index 00000000..76a27da3 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/IntlFormatterInterface.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter; + +/** + * Formats ICU message patterns. + * + * @author Nicolas Grekas + */ +interface IntlFormatterInterface +{ + /** + * Formats a localized message using rules defined by ICU MessageFormat. + * + * @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details + */ + public function formatIntl(string $message, string $locale, array $parameters = []) : string; +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/MessageFormatter.php b/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/MessageFormatter.php new file mode 100755 index 00000000..3595236c --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/MessageFormatter.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\IdentityTranslator; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageSelector; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface; +/** + * @author Abdellatif Ait boudad + */ +class MessageFormatter implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\MessageFormatterInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\IntlFormatterInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface +{ + private $translator; + private $intlFormatter; + /** + * @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization + */ + public function __construct($translator = null, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\IntlFormatterInterface $intlFormatter = null) + { + if ($translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageSelector) { + $translator = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\IdentityTranslator($translator); + } elseif (null !== $translator && !$translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface && !$translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface) { + throw new \TypeError(\sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); + } + $this->translator = $translator ?? new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\IdentityTranslator(); + $this->intlFormatter = $intlFormatter ?? new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\IntlFormatter(); + } + /** + * {@inheritdoc} + */ + public function format($message, $locale, array $parameters = []) + { + if ($this->translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface) { + return $this->translator->trans($message, $parameters, null, $locale); + } + return \strtr($message, $parameters); + } + /** + * {@inheritdoc} + */ + public function formatIntl(string $message, string $locale, array $parameters = []) : string + { + return $this->intlFormatter->formatIntl($message, $locale, $parameters); + } + /** + * {@inheritdoc} + * + * @deprecated since Symfony 4.2, use format() with a %count% parameter instead + */ + public function choiceFormat($message, $number, $locale, array $parameters = []) + { + @\trigger_error(\sprintf('The "%s()" method is deprecated since Symfony 4.2, use the format() one instead with a %%count%% parameter.', __METHOD__), \E_USER_DEPRECATED); + $parameters = ['%count%' => $number] + $parameters; + if ($this->translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface) { + return $this->format($message, $locale, $parameters); + } + return $this->format($this->translator->transChoice($message, $number, [], null, $locale), $locale, $parameters); + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/MessageFormatterInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/MessageFormatterInterface.php new file mode 100755 index 00000000..3c82ae2e --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Formatter/MessageFormatterInterface.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter; + +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +interface MessageFormatterInterface +{ + /** + * Formats a localized message pattern with given arguments. + * + * @param string $message The message (may also be an object that can be cast to string) + * @param string $locale The message locale + * @param array $parameters An array of parameters for the message + * + * @return string + */ + public function format($message, $locale, array $parameters = []); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/IdentityTranslator.php b/classes/Utilities/Misc/Symfony/Component/Translation/IdentityTranslator.php new file mode 100755 index 00000000..f4c28e29 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/IdentityTranslator.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorTrait; +/** + * IdentityTranslator does not translate anything. + * + * @author Fabien Potencier + */ +class IdentityTranslator implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface +{ + use TranslatorTrait { + trans as private doTrans; + setLocale as private doSetLocale; + } + private $selector; + public function __construct(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageSelector $selector = null) + { + $this->selector = $selector; + if (__CLASS__ !== \get_class($this)) { + @\trigger_error(\sprintf('Calling "%s()" is deprecated since Symfony 4.2.', __METHOD__), \E_USER_DEPRECATED); + } + } + /** + * {@inheritdoc} + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null) + { + return $this->doTrans($id, $parameters, $domain, $locale); + } + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->doSetLocale($locale); + } + /** + * {@inheritdoc} + * + * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter + */ + public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null) + { + @\trigger_error(\sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%%count%%" parameter.', __METHOD__), \E_USER_DEPRECATED); + if ($this->selector) { + return \strtr($this->selector->choose((string) $id, $number, $locale ?: $this->getLocale()), $parameters); + } + return $this->trans($id, ['%count%' => $number] + $parameters, $domain, $locale); + } + private function getPluralizationRule(int $number, string $locale) : int + { + return \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\PluralizationRules::get($number, $locale, \false); + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Interval.php b/classes/Utilities/Misc/Symfony/Component/Translation/Interval.php new file mode 100755 index 00000000..1e712dee --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Interval.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +@\trigger_error(\sprintf('The "%s" class is deprecated since Symfony 4.2, use IdentityTranslator instead.', \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Interval::class), \E_USER_DEPRECATED); +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +/** + * Tests if a given number belongs to a given math interval. + * + * An interval can represent a finite set of numbers: + * + * {1,2,3,4} + * + * An interval can represent numbers between two numbers: + * + * [1, +Inf] + * ]-1,2[ + * + * The left delimiter can be [ (inclusive) or ] (exclusive). + * The right delimiter can be [ (exclusive) or ] (inclusive). + * Beside numbers, you can use -Inf and +Inf for the infinite. + * + * @author Fabien Potencier + * + * @see http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation + * @deprecated since Symfony 4.2, use IdentityTranslator instead + */ +class Interval +{ + /** + * Tests if the given number is in the math interval. + * + * @param int $number A number + * @param string $interval An interval + * + * @return bool + * + * @throws InvalidArgumentException + */ + public static function test($number, $interval) + { + $interval = \trim($interval); + if (!\preg_match('/^' . self::getIntervalRegexp() . '$/x', $interval, $matches)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('"%s" is not a valid interval.', $interval)); + } + if ($matches[1]) { + foreach (\explode(',', $matches[2]) as $n) { + if ($number == $n) { + return \true; + } + } + } else { + $leftNumber = self::convertNumber($matches['left']); + $rightNumber = self::convertNumber($matches['right']); + return ('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber); + } + return \false; + } + /** + * Returns a Regexp that matches valid intervals. + * + * @return string A Regexp (without the delimiters) + */ + public static function getIntervalRegexp() + { + return <<[\\[\\]]) + \\s* + (?P-Inf|\\-?\\d+(\\.\\d+)?) + \\s*,\\s* + (?P\\+?Inf|\\-?\\d+(\\.\\d+)?) + \\s* + (?P[\\[\\]]) +EOF; + } + private static function convertNumber(string $number) : float + { + if ('-Inf' === $number) { + return \log(0); + } elseif ('+Inf' === $number || 'Inf' === $number) { + return -\log(0); + } + return (float) $number; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/ArrayLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/ArrayLoader.php new file mode 100755 index 00000000..645c3d83 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/ArrayLoader.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * ArrayLoader loads translations from a PHP array. + * + * @author Fabien Potencier + */ +class ArrayLoader implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, $locale, $domain = 'messages') + { + $resource = $this->flatten($resource); + $catalogue = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue($locale); + $catalogue->add($resource, $domain); + return $catalogue; + } + /** + * Flattens an nested array of translations. + * + * The scheme used is: + * 'key' => ['key2' => ['key3' => 'value']] + * Becomes: + * 'key.key2.key3' => 'value' + */ + private function flatten(array $messages) : array + { + $result = []; + foreach ($messages as $key => $value) { + if (\is_array($value)) { + foreach ($this->flatten($value) as $k => $v) { + $result[$key . '.' . $k] = $v; + } + } else { + $result[$key] = $value; + } + } + return $result; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/CsvFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/CsvFileLoader.php new file mode 100755 index 00000000..7ad278c0 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/CsvFileLoader.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException; +/** + * CsvFileLoader loads translations from CSV files. + * + * @author SaÅ¡a Stamenković + */ +class CsvFileLoader extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\FileLoader +{ + private $delimiter = ';'; + private $enclosure = '"'; + private $escape = '\\'; + /** + * {@inheritdoc} + */ + protected function loadResource($resource) + { + $messages = []; + try { + $file = new \SplFileObject($resource, 'rb'); + } catch (\RuntimeException $e) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException(\sprintf('Error opening file "%s".', $resource), 0, $e); + } + $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY); + $file->setCsvControl($this->delimiter, $this->enclosure, $this->escape); + foreach ($file as $data) { + if (\false === $data) { + continue; + } + if ('#' !== \substr($data[0], 0, 1) && isset($data[1]) && 2 === \count($data)) { + $messages[$data[0]] = $data[1]; + } + } + return $messages; + } + /** + * Sets the delimiter, enclosure, and escape character for CSV. + * + * @param string $delimiter Delimiter character + * @param string $enclosure Enclosure character + * @param string $escape Escape character + */ + public function setCsvControl($delimiter = ';', $enclosure = '"', $escape = '\\') + { + $this->delimiter = $delimiter; + $this->enclosure = $enclosure; + $this->escape = $escape; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/FileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/FileLoader.php new file mode 100755 index 00000000..bd599edf --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/FileLoader.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\FileResource; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException; +/** + * @author Abdellatif Ait boudad + */ +abstract class FileLoader extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\ArrayLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!\stream_is_local($resource)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); + } + if (!\file_exists($resource)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); + } + $messages = $this->loadResource($resource); + // empty resource + if (null === $messages) { + $messages = []; + } + // not an array + if (!\is_array($messages)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('Unable to load file "%s".', $resource)); + } + $catalogue = parent::load($messages, $locale, $domain); + if (\class_exists('ILAB\\MediaCloud\\Utilities\\Misc\\Symfony\\Component\\Config\\Resource\\FileResource')) { + $catalogue->addResource(new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\FileResource($resource)); + } + return $catalogue; + } + /** + * @param string $resource + * + * @return array + * + * @throws InvalidResourceException if stream content has an invalid format + */ + protected abstract function loadResource($resource); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/IcuDatFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/IcuDatFileLoader.php new file mode 100755 index 00000000..f83c9443 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/IcuDatFileLoader.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\FileResource; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * IcuResFileLoader loads translations from a resource bundle. + * + * @author stealth35 + */ +class IcuDatFileLoader extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\IcuResFileLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!\stream_is_local($resource . '.dat')) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); + } + if (!\file_exists($resource . '.dat')) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); + } + try { + $rb = new \ResourceBundle($locale, $resource); + } catch (\Exception $e) { + $rb = null; + } + if (!$rb) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('Cannot load resource "%s"', $resource)); + } elseif (\intl_is_failure($rb->getErrorCode())) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); + } + $messages = $this->flatten($rb); + $catalogue = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue($locale); + $catalogue->add($messages, $domain); + if (\class_exists('ILAB\\MediaCloud\\Utilities\\Misc\\Symfony\\Component\\Config\\Resource\\FileResource')) { + $catalogue->addResource(new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\FileResource($resource . '.dat')); + } + return $catalogue; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/IcuResFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/IcuResFileLoader.php new file mode 100755 index 00000000..5410136c --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/IcuResFileLoader.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\DirectoryResource; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * IcuResFileLoader loads translations from a resource bundle. + * + * @author stealth35 + */ +class IcuResFileLoader implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!\stream_is_local($resource)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); + } + if (!\is_dir($resource)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); + } + try { + $rb = new \ResourceBundle($locale, $resource); + } catch (\Exception $e) { + $rb = null; + } + if (!$rb) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('Cannot load resource "%s"', $resource)); + } elseif (\intl_is_failure($rb->getErrorCode())) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); + } + $messages = $this->flatten($rb); + $catalogue = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue($locale); + $catalogue->add($messages, $domain); + if (\class_exists('ILAB\\MediaCloud\\Utilities\\Misc\\Symfony\\Component\\Config\\Resource\\DirectoryResource')) { + $catalogue->addResource(new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\DirectoryResource($resource)); + } + return $catalogue; + } + /** + * Flattens an ResourceBundle. + * + * The scheme used is: + * key { key2 { key3 { "value" } } } + * Becomes: + * 'key.key2.key3' => 'value' + * + * This function takes an array by reference and will modify it + * + * @param \ResourceBundle $rb The ResourceBundle that will be flattened + * @param array $messages Used internally for recursive calls + * @param string $path Current path being parsed, used internally for recursive calls + * + * @return array the flattened ResourceBundle + */ + protected function flatten(\ResourceBundle $rb, array &$messages = [], $path = null) + { + foreach ($rb as $key => $value) { + $nodePath = $path ? $path . '.' . $key : $key; + if ($value instanceof \ResourceBundle) { + $this->flatten($value, $messages, $nodePath); + } else { + $messages[$nodePath] = $value; + } + } + return $messages; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/IniFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/IniFileLoader.php new file mode 100755 index 00000000..ee6b8aa4 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/IniFileLoader.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +/** + * IniFileLoader loads translations from an ini file. + * + * @author stealth35 + */ +class IniFileLoader extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\FileLoader +{ + /** + * {@inheritdoc} + */ + protected function loadResource($resource) + { + return \parse_ini_file($resource, \true); + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/JsonFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/JsonFileLoader.php new file mode 100755 index 00000000..48724d82 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/JsonFileLoader.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException; +/** + * JsonFileLoader loads translations from an json file. + * + * @author singles + */ +class JsonFileLoader extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\FileLoader +{ + /** + * {@inheritdoc} + */ + protected function loadResource($resource) + { + $messages = []; + if ($data = \file_get_contents($resource)) { + $messages = \json_decode($data, \true); + if (0 < ($errorCode = \json_last_error())) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('Error parsing JSON - %s', $this->getJSONErrorMessage($errorCode))); + } + } + return $messages; + } + /** + * Translates JSON_ERROR_* constant into meaningful message. + */ + private function getJSONErrorMessage(int $errorCode) : string + { + switch ($errorCode) { + case \JSON_ERROR_DEPTH: + return 'Maximum stack depth exceeded'; + case \JSON_ERROR_STATE_MISMATCH: + return 'Underflow or the modes mismatch'; + case \JSON_ERROR_CTRL_CHAR: + return 'Unexpected control character found'; + case \JSON_ERROR_SYNTAX: + return 'Syntax error, malformed JSON'; + case \JSON_ERROR_UTF8: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + default: + return 'Unknown error'; + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/LoaderInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/LoaderInterface.php new file mode 100755 index 00000000..3ca3d098 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/LoaderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * LoaderInterface is the interface implemented by all translation loaders. + * + * @author Fabien Potencier + */ +interface LoaderInterface +{ + /** + * Loads a locale. + * + * @param mixed $resource A resource + * @param string $locale A locale + * @param string $domain The domain + * + * @return MessageCatalogue A MessageCatalogue instance + * + * @throws NotFoundResourceException when the resource cannot be found + * @throws InvalidResourceException when the resource cannot be loaded + */ + public function load($resource, $locale, $domain = 'messages'); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/MoFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/MoFileLoader.php new file mode 100755 index 00000000..8b890991 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/MoFileLoader.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException; +/** + * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) + */ +class MoFileLoader extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\FileLoader +{ + /** + * Magic used for validating the format of a MO file as well as + * detecting if the machine used to create that file was little endian. + */ + const MO_LITTLE_ENDIAN_MAGIC = 0x950412de; + /** + * Magic used for validating the format of a MO file as well as + * detecting if the machine used to create that file was big endian. + */ + const MO_BIG_ENDIAN_MAGIC = 0xde120495; + /** + * The size of the header of a MO file in bytes. + */ + const MO_HEADER_SIZE = 28; + /** + * Parses machine object (MO) format, independent of the machine's endian it + * was created on. Both 32bit and 64bit systems are supported. + * + * {@inheritdoc} + */ + protected function loadResource($resource) + { + $stream = \fopen($resource, 'r'); + $stat = \fstat($stream); + if ($stat['size'] < self::MO_HEADER_SIZE) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException('MO stream content has an invalid format.'); + } + $magic = \unpack('V1', \fread($stream, 4)); + $magic = \hexdec(\substr(\dechex(\current($magic)), -8)); + if (self::MO_LITTLE_ENDIAN_MAGIC == $magic) { + $isBigEndian = \false; + } elseif (self::MO_BIG_ENDIAN_MAGIC == $magic) { + $isBigEndian = \true; + } else { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException('MO stream content has an invalid format.'); + } + // formatRevision + $this->readLong($stream, $isBigEndian); + $count = $this->readLong($stream, $isBigEndian); + $offsetId = $this->readLong($stream, $isBigEndian); + $offsetTranslated = $this->readLong($stream, $isBigEndian); + // sizeHashes + $this->readLong($stream, $isBigEndian); + // offsetHashes + $this->readLong($stream, $isBigEndian); + $messages = []; + for ($i = 0; $i < $count; ++$i) { + $pluralId = null; + $translated = null; + \fseek($stream, $offsetId + $i * 8); + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + if ($length < 1) { + continue; + } + \fseek($stream, $offset); + $singularId = \fread($stream, $length); + if (\false !== \strpos($singularId, "\0")) { + list($singularId, $pluralId) = \explode("\0", $singularId); + } + \fseek($stream, $offsetTranslated + $i * 8); + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + if ($length < 1) { + continue; + } + \fseek($stream, $offset); + $translated = \fread($stream, $length); + if (\false !== \strpos($translated, "\0")) { + $translated = \explode("\0", $translated); + } + $ids = ['singular' => $singularId, 'plural' => $pluralId]; + $item = \compact('ids', 'translated'); + if (!empty($item['ids']['singular'])) { + $id = $item['ids']['singular']; + if (isset($item['ids']['plural'])) { + $id .= '|' . $item['ids']['plural']; + } + $messages[$id] = \stripcslashes(\implode('|', (array) $item['translated'])); + } + } + \fclose($stream); + return \array_filter($messages); + } + /** + * Reads an unsigned long from stream respecting endianness. + * + * @param resource $stream + */ + private function readLong($stream, bool $isBigEndian) : int + { + $result = \unpack($isBigEndian ? 'N1' : 'V1', \fread($stream, 4)); + $result = \current($result); + return (int) \substr($result, -8); + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/PhpFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/PhpFileLoader.php new file mode 100755 index 00000000..104889f0 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/PhpFileLoader.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +/** + * PhpFileLoader loads translations from PHP files returning an array of translations. + * + * @author Fabien Potencier + */ +class PhpFileLoader extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\FileLoader +{ + /** + * {@inheritdoc} + */ + protected function loadResource($resource) + { + return require $resource; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/PoFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/PoFileLoader.php new file mode 100755 index 00000000..a69e043a --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/PoFileLoader.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +/** + * @copyright Copyright (c) 2010, Union of RAD https://github.com/UnionOfRAD/lithium + * @copyright Copyright (c) 2012, Clemens Tolboom + */ +class PoFileLoader extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\FileLoader +{ + /** + * Parses portable object (PO) format. + * + * From https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files + * we should be able to parse files having: + * + * white-space + * # translator-comments + * #. extracted-comments + * #: reference... + * #, flag... + * #| msgid previous-untranslated-string + * msgid untranslated-string + * msgstr translated-string + * + * extra or different lines are: + * + * #| msgctxt previous-context + * #| msgid previous-untranslated-string + * msgctxt context + * + * #| msgid previous-untranslated-string-singular + * #| msgid_plural previous-untranslated-string-plural + * msgid untranslated-string-singular + * msgid_plural untranslated-string-plural + * msgstr[0] translated-string-case-0 + * ... + * msgstr[N] translated-string-case-n + * + * The definition states: + * - white-space and comments are optional. + * - msgid "" that an empty singleline defines a header. + * + * This parser sacrifices some features of the reference implementation the + * differences to that implementation are as follows. + * - No support for comments spanning multiple lines. + * - Translator and extracted comments are treated as being the same type. + * - Message IDs are allowed to have other encodings as just US-ASCII. + * + * Items with an empty id are ignored. + * + * {@inheritdoc} + */ + protected function loadResource($resource) + { + $stream = \fopen($resource, 'r'); + $defaults = ['ids' => [], 'translated' => null]; + $messages = []; + $item = $defaults; + $flags = []; + while ($line = \fgets($stream)) { + $line = \trim($line); + if ('' === $line) { + // Whitespace indicated current item is done + if (!\in_array('fuzzy', $flags)) { + $this->addMessage($messages, $item); + } + $item = $defaults; + $flags = []; + } elseif ('#,' === \substr($line, 0, 2)) { + $flags = \array_map('trim', \explode(',', \substr($line, 2))); + } elseif ('msgid "' === \substr($line, 0, 7)) { + // We start a new msg so save previous + // TODO: this fails when comments or contexts are added + $this->addMessage($messages, $item); + $item = $defaults; + $item['ids']['singular'] = \substr($line, 7, -1); + } elseif ('msgstr "' === \substr($line, 0, 8)) { + $item['translated'] = \substr($line, 8, -1); + } elseif ('"' === $line[0]) { + $continues = isset($item['translated']) ? 'translated' : 'ids'; + if (\is_array($item[$continues])) { + \end($item[$continues]); + $item[$continues][\key($item[$continues])] .= \substr($line, 1, -1); + } else { + $item[$continues] .= \substr($line, 1, -1); + } + } elseif ('msgid_plural "' === \substr($line, 0, 14)) { + $item['ids']['plural'] = \substr($line, 14, -1); + } elseif ('msgstr[' === \substr($line, 0, 7)) { + $size = \strpos($line, ']'); + $item['translated'][(int) \substr($line, 7, 1)] = \substr($line, $size + 3, -1); + } + } + // save last item + if (!\in_array('fuzzy', $flags)) { + $this->addMessage($messages, $item); + } + \fclose($stream); + return $messages; + } + /** + * Save a translation item to the messages. + * + * A .po file could contain by error missing plural indexes. We need to + * fix these before saving them. + */ + private function addMessage(array &$messages, array $item) + { + if (!empty($item['ids']['singular'])) { + $id = \stripcslashes($item['ids']['singular']); + if (isset($item['ids']['plural'])) { + $id .= '|' . \stripcslashes($item['ids']['plural']); + } + $translated = (array) $item['translated']; + // PO are by definition indexed so sort by index. + \ksort($translated); + // Make sure every index is filled. + \end($translated); + $count = \key($translated); + // Fill missing spots with '-'. + $empties = \array_fill(0, $count + 1, '-'); + $translated += $empties; + \ksort($translated); + $messages[$id] = \stripcslashes(\implode('|', $translated)); + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/QtFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/QtFileLoader.php new file mode 100755 index 00000000..2866755a --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/QtFileLoader.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\FileResource; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Util\XmlUtils; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * QtFileLoader loads translations from QT Translations XML files. + * + * @author Benjamin Eberlei + */ +class QtFileLoader implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!\stream_is_local($resource)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); + } + if (!\file_exists($resource)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); + } + try { + $dom = \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Util\XmlUtils::loadFile($resource); + } catch (\InvalidArgumentException $e) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('Unable to load "%s".', $resource), $e->getCode(), $e); + } + $internalErrors = \libxml_use_internal_errors(\true); + \libxml_clear_errors(); + $xpath = new \DOMXPath($dom); + $nodes = $xpath->evaluate('//TS/context/name[text()="' . $domain . '"]'); + $catalogue = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue($locale); + if (1 == $nodes->length) { + $translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message'); + foreach ($translations as $translation) { + $translationValue = (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue; + if (!empty($translationValue)) { + $catalogue->set((string) $translation->getElementsByTagName('source')->item(0)->nodeValue, $translationValue, $domain); + } + $translation = $translation->nextSibling; + } + if (\class_exists('ILAB\\MediaCloud\\Utilities\\Misc\\Symfony\\Component\\Config\\Resource\\FileResource')) { + $catalogue->addResource(new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\FileResource($resource)); + } + } + \libxml_use_internal_errors($internalErrors); + return $catalogue; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/XliffFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/XliffFileLoader.php new file mode 100755 index 00000000..7646a26b --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\FileResource; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Util\XmlUtils; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Util\XliffUtils; +/** + * XliffFileLoader loads translations from XLIFF files. + * + * @author Fabien Potencier + */ +class XliffFileLoader implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!\stream_is_local($resource)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); + } + if (!\file_exists($resource)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); + } + $catalogue = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue($locale); + $this->extract($resource, $catalogue, $domain); + if (\class_exists('ILAB\\MediaCloud\\Utilities\\Misc\\Symfony\\Component\\Config\\Resource\\FileResource')) { + $catalogue->addResource(new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\FileResource($resource)); + } + return $catalogue; + } + private function extract($resource, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalogue, string $domain) + { + try { + $dom = \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Util\XmlUtils::loadFile($resource); + } catch (\InvalidArgumentException $e) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('Unable to load "%s": %s', $resource, $e->getMessage()), $e->getCode(), $e); + } + $xliffVersion = \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Util\XliffUtils::getVersionNumber($dom); + if ($errors = \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Util\XliffUtils::validateSchema($dom)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('Invalid resource provided: "%s"; Errors: %s', $resource, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Util\XliffUtils::getErrorsAsString($errors))); + } + if ('1.2' === $xliffVersion) { + $this->extractXliff1($dom, $catalogue, $domain); + } + if ('2.0' === $xliffVersion) { + $this->extractXliff2($dom, $catalogue, $domain); + } + } + /** + * Extract messages and metadata from DOMDocument into a MessageCatalogue. + */ + private function extractXliff1(\DOMDocument $dom, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalogue, string $domain) + { + $xml = \simplexml_import_dom($dom); + $encoding = \strtoupper($dom->encoding); + $namespace = 'urn:oasis:names:tc:xliff:document:1.2'; + $xml->registerXPathNamespace('xliff', $namespace); + foreach ($xml->xpath('//xliff:file') as $file) { + $fileAttributes = $file->attributes(); + $file->registerXPathNamespace('xliff', $namespace); + foreach ($file->xpath('.//xliff:trans-unit') as $translation) { + $attributes = $translation->attributes(); + if (!(isset($attributes['resname']) || isset($translation->source))) { + continue; + } + $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding); + $catalogue->set((string) $source, $target, $domain); + $metadata = ['source' => (string) $translation->source, 'file' => ['original' => (string) $fileAttributes['original']]]; + if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { + $metadata['notes'] = $notes; + } + if (isset($translation->target) && $translation->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($translation->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + if (isset($attributes['id'])) { + $metadata['id'] = (string) $attributes['id']; + } + $catalogue->setMetadata((string) $source, $metadata, $domain); + } + } + } + private function extractXliff2(\DOMDocument $dom, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalogue, string $domain) + { + $xml = \simplexml_import_dom($dom); + $encoding = \strtoupper($dom->encoding); + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); + foreach ($xml->xpath('//xliff:unit') as $unit) { + foreach ($unit->segment as $segment) { + $source = $segment->source; + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding); + $catalogue->set((string) $source, $target, $domain); + $metadata = []; + if (isset($segment->target) && $segment->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($segment->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + if (isset($unit->notes)) { + $metadata['notes'] = []; + foreach ($unit->notes->note as $noteNode) { + $note = []; + foreach ($noteNode->attributes() as $key => $value) { + $note[$key] = (string) $value; + } + $note['content'] = (string) $noteNode; + $metadata['notes'][] = $note; + } + } + $catalogue->setMetadata((string) $source, $metadata, $domain); + } + } + } + /** + * Convert a UTF8 string to the specified encoding. + */ + private function utf8ToCharset(string $content, string $encoding = null) : string + { + if ('UTF-8' !== $encoding && !empty($encoding)) { + return \mb_convert_encoding($content, $encoding, 'UTF-8'); + } + return $content; + } + private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null) : array + { + $notes = []; + if (null === $noteElement) { + return $notes; + } + /** @var \SimpleXMLElement $xmlNote */ + foreach ($noteElement as $xmlNote) { + $noteAttributes = $xmlNote->attributes(); + $note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)]; + if (isset($noteAttributes['priority'])) { + $note['priority'] = (int) $noteAttributes['priority']; + } + if (isset($noteAttributes['from'])) { + $note['from'] = (string) $noteAttributes['from']; + } + $notes[] = $note; + } + return $notes; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Loader/YamlFileLoader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/YamlFileLoader.php new file mode 100755 index 00000000..9081edfb --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Loader/YamlFileLoader.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Yaml\Exception\ParseException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Yaml\Parser as YamlParser; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Yaml\Yaml; +/** + * YamlFileLoader loads translations from Yaml files. + * + * @author Fabien Potencier + */ +class YamlFileLoader extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\FileLoader +{ + private $yamlParser; + /** + * {@inheritdoc} + */ + protected function loadResource($resource) + { + if (null === $this->yamlParser) { + if (!\class_exists('ILAB\\MediaCloud\\Utilities\\Misc\\Symfony\\Component\\Yaml\\Parser')) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException('Loading translations from the YAML format requires the Symfony Yaml component.'); + } + $this->yamlParser = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Yaml\Parser(); + } + try { + $messages = $this->yamlParser->parseFile($resource, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Yaml\Yaml::PARSE_CONSTANT); + } catch (\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Yaml\Exception\ParseException $e) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('Error parsing YAML, invalid file "%s"', $resource), 0, $e); + } + if (null !== $messages && !\is_array($messages)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException(\sprintf('Unable to load file "%s".', $resource)); + } + return $messages ?: []; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/LocaleAwareInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/LocaleAwareInterface.php new file mode 100755 index 00000000..cdef0129 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/LocaleAwareInterface.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation; + +interface LocaleAwareInterface +{ + /** + * Sets the current locale. + * + * @param string $locale The locale + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale(string $locale); + /** + * Returns the current locale. + * + * @return string The locale + */ + public function getLocale(); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/LoggingTranslator.php b/classes/Utilities/Misc/Symfony/Component/Translation/LoggingTranslator.php new file mode 100755 index 00000000..55cc7e66 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/LoggingTranslator.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +use ILAB\MediaCloud\Utilities\Misc\Psr\Log\LoggerInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\LocaleAwareInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface; +/** + * @author Abdellatif Ait boudad + */ +class LoggingTranslator implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorBagInterface +{ + /** + * @var TranslatorInterface|TranslatorBagInterface + */ + private $translator; + private $logger; + /** + * @param TranslatorInterface $translator The translator must implement TranslatorBagInterface + */ + public function __construct($translator, \ILAB\MediaCloud\Utilities\Misc\Psr\Log\LoggerInterface $logger) + { + if (!$translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface && !$translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface) { + throw new \TypeError(\sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); + } + if (!$translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorBagInterface || !$translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\LocaleAwareInterface) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', \get_class($translator))); + } + $this->translator = $translator; + $this->logger = $logger; + } + /** + * {@inheritdoc} + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null) + { + $trans = $this->translator->trans($id, $parameters, $domain, $locale); + $this->log($id, $domain, $locale); + return $trans; + } + /** + * {@inheritdoc} + * + * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter + */ + public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null) + { + @\trigger_error(\sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%%count%%" parameter.', __METHOD__), \E_USER_DEPRECATED); + if ($this->translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface) { + $trans = $this->translator->trans($id, ['%count%' => $number] + $parameters, $domain, $locale); + } else { + $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); + } + $this->log($id, $domain, $locale); + return $trans; + } + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $prev = $this->translator->getLocale(); + $this->translator->setLocale($locale); + if ($prev === $locale) { + return; + } + $this->logger->debug(\sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale)); + } + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->translator->getLocale(); + } + /** + * {@inheritdoc} + */ + public function getCatalogue($locale = null) + { + return $this->translator->getCatalogue($locale); + } + /** + * Gets the fallback locales. + * + * @return array The fallback locales + */ + public function getFallbackLocales() + { + if ($this->translator instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Translator || \method_exists($this->translator, 'getFallbackLocales')) { + return $this->translator->getFallbackLocales(); + } + return []; + } + /** + * Passes through all unknown calls onto the translator object. + */ + public function __call($method, $args) + { + return $this->translator->{$method}(...$args); + } + /** + * Logs for missing translations. + */ + private function log(?string $id, ?string $domain, ?string $locale) + { + if (null === $domain) { + $domain = 'messages'; + } + $id = (string) $id; + $catalogue = $this->translator->getCatalogue($locale); + if ($catalogue->defines($id, $domain)) { + return; + } + if ($catalogue->has($id, $domain)) { + $this->logger->debug('Translation use fallback catalogue.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]); + } else { + $this->logger->warning('Translation not found.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]); + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/MessageCatalogue.php b/classes/Utilities/Misc/Symfony/Component/Translation/MessageCatalogue.php new file mode 100755 index 00000000..2827a7c2 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/MessageCatalogue.php @@ -0,0 +1,264 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\ResourceInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException; +/** + * @author Fabien Potencier + */ +class MessageCatalogue implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MetadataAwareInterface +{ + private $messages = []; + private $metadata = []; + private $resources = []; + private $locale; + private $fallbackCatalogue; + private $parent; + /** + * @param string $locale The locale + * @param array $messages An array of messages classified by domain + */ + public function __construct(?string $locale, array $messages = []) + { + if (null === $locale) { + @\trigger_error(\sprintf('Passing "null" to the first argument of the "%s" method has been deprecated since Symfony 4.4 and will throw an error in 5.0.', __METHOD__), \E_USER_DEPRECATED); + } + $this->locale = $locale; + $this->messages = $messages; + } + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale; + } + /** + * {@inheritdoc} + */ + public function getDomains() + { + $domains = []; + $suffixLength = \strlen(self::INTL_DOMAIN_SUFFIX); + foreach ($this->messages as $domain => $messages) { + if (\strlen($domain) > $suffixLength && \false !== ($i = \strpos($domain, self::INTL_DOMAIN_SUFFIX, -$suffixLength))) { + $domain = \substr($domain, 0, $i); + } + $domains[$domain] = $domain; + } + return \array_values($domains); + } + /** + * {@inheritdoc} + */ + public function all($domain = null) + { + if (null !== $domain) { + return ($this->messages[$domain . self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []); + } + $allMessages = []; + $suffixLength = \strlen(self::INTL_DOMAIN_SUFFIX); + foreach ($this->messages as $domain => $messages) { + if (\strlen($domain) > $suffixLength && \false !== ($i = \strpos($domain, self::INTL_DOMAIN_SUFFIX, -$suffixLength))) { + $domain = \substr($domain, 0, $i); + $allMessages[$domain] = $messages + ($allMessages[$domain] ?? []); + } else { + $allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages; + } + } + return $allMessages; + } + /** + * {@inheritdoc} + */ + public function set($id, $translation, $domain = 'messages') + { + $this->add([$id => $translation], $domain); + } + /** + * {@inheritdoc} + */ + public function has($id, $domain = 'messages') + { + if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain . self::INTL_DOMAIN_SUFFIX][$id])) { + return \true; + } + if (null !== $this->fallbackCatalogue) { + return $this->fallbackCatalogue->has($id, $domain); + } + return \false; + } + /** + * {@inheritdoc} + */ + public function defines($id, $domain = 'messages') + { + return isset($this->messages[$domain][$id]) || isset($this->messages[$domain . self::INTL_DOMAIN_SUFFIX][$id]); + } + /** + * {@inheritdoc} + */ + public function get($id, $domain = 'messages') + { + if (isset($this->messages[$domain . self::INTL_DOMAIN_SUFFIX][$id])) { + return $this->messages[$domain . self::INTL_DOMAIN_SUFFIX][$id]; + } + if (isset($this->messages[$domain][$id])) { + return $this->messages[$domain][$id]; + } + if (null !== $this->fallbackCatalogue) { + return $this->fallbackCatalogue->get($id, $domain); + } + return $id; + } + /** + * {@inheritdoc} + */ + public function replace($messages, $domain = 'messages') + { + unset($this->messages[$domain], $this->messages[$domain . self::INTL_DOMAIN_SUFFIX]); + $this->add($messages, $domain); + } + /** + * {@inheritdoc} + */ + public function add($messages, $domain = 'messages') + { + if (!isset($this->messages[$domain])) { + $this->messages[$domain] = $messages; + } else { + $this->messages[$domain] = \array_replace($this->messages[$domain], $messages); + } + } + /** + * {@inheritdoc} + */ + public function addCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface $catalogue) + { + if ($catalogue->getLocale() !== $this->locale) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException(\sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s"', $catalogue->getLocale(), $this->locale)); + } + foreach ($catalogue->all() as $domain => $messages) { + if ($intlMessages = $catalogue->all($domain . self::INTL_DOMAIN_SUFFIX)) { + $this->add($intlMessages, $domain . self::INTL_DOMAIN_SUFFIX); + $messages = \array_diff_key($messages, $intlMessages); + } + $this->add($messages, $domain); + } + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + if ($catalogue instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MetadataAwareInterface) { + $metadata = $catalogue->getMetadata('', ''); + $this->addMetadata($metadata); + } + } + /** + * {@inheritdoc} + */ + public function addFallbackCatalogue(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface $catalogue) + { + // detect circular references + $c = $catalogue; + while ($c = $c->getFallbackCatalogue()) { + if ($c->getLocale() === $this->getLocale()) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException(\sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + } + } + $c = $this; + do { + if ($c->getLocale() === $catalogue->getLocale()) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException(\sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + } + foreach ($catalogue->getResources() as $resource) { + $c->addResource($resource); + } + } while ($c = $c->parent); + $catalogue->parent = $this; + $this->fallbackCatalogue = $catalogue; + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + } + /** + * {@inheritdoc} + */ + public function getFallbackCatalogue() + { + return $this->fallbackCatalogue; + } + /** + * {@inheritdoc} + */ + public function getResources() + { + return \array_values($this->resources); + } + /** + * {@inheritdoc} + */ + public function addResource(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\ResourceInterface $resource) + { + $this->resources[$resource->__toString()] = $resource; + } + /** + * {@inheritdoc} + */ + public function getMetadata($key = '', $domain = 'messages') + { + if ('' == $domain) { + return $this->metadata; + } + if (isset($this->metadata[$domain])) { + if ('' == $key) { + return $this->metadata[$domain]; + } + if (isset($this->metadata[$domain][$key])) { + return $this->metadata[$domain][$key]; + } + } + return null; + } + /** + * {@inheritdoc} + */ + public function setMetadata($key, $value, $domain = 'messages') + { + $this->metadata[$domain][$key] = $value; + } + /** + * {@inheritdoc} + */ + public function deleteMetadata($key = '', $domain = 'messages') + { + if ('' == $domain) { + $this->metadata = []; + } elseif ('' == $key) { + unset($this->metadata[$domain]); + } else { + unset($this->metadata[$domain][$key]); + } + } + /** + * Adds current values with the new values. + * + * @param array $values Values to add + */ + private function addMetadata(array $values) + { + foreach ($values as $domain => $keys) { + foreach ($keys as $key => $value) { + $this->setMetadata($key, $value, $domain); + } + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/MessageCatalogueInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/MessageCatalogueInterface.php new file mode 100755 index 00000000..65f4a1e7 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/MessageCatalogueInterface.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\ResourceInterface; +/** + * MessageCatalogueInterface. + * + * @author Fabien Potencier + */ +interface MessageCatalogueInterface +{ + const INTL_DOMAIN_SUFFIX = '+intl-icu'; + /** + * Gets the catalogue locale. + * + * @return string The locale + */ + public function getLocale(); + /** + * Gets the domains. + * + * @return array An array of domains + */ + public function getDomains(); + /** + * Gets the messages within a given domain. + * + * If $domain is null, it returns all messages. + * + * @param string $domain The domain name + * + * @return array An array of messages + */ + public function all($domain = null); + /** + * Sets a message translation. + * + * @param string $id The message id + * @param string $translation The messages translation + * @param string $domain The domain name + */ + public function set($id, $translation, $domain = 'messages'); + /** + * Checks if a message has a translation. + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return bool true if the message has a translation, false otherwise + */ + public function has($id, $domain = 'messages'); + /** + * Checks if a message has a translation (it does not take into account the fallback mechanism). + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return bool true if the message has a translation, false otherwise + */ + public function defines($id, $domain = 'messages'); + /** + * Gets a message translation. + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return string The message translation + */ + public function get($id, $domain = 'messages'); + /** + * Sets translations for a given domain. + * + * @param array $messages An array of translations + * @param string $domain The domain name + */ + public function replace($messages, $domain = 'messages'); + /** + * Adds translations for a given domain. + * + * @param array $messages An array of translations + * @param string $domain The domain name + */ + public function add($messages, $domain = 'messages'); + /** + * Merges translations from the given Catalogue into the current one. + * + * The two catalogues must have the same locale. + */ + public function addCatalogue(self $catalogue); + /** + * Merges translations from the given Catalogue into the current one + * only when the translation does not exist. + * + * This is used to provide default translations when they do not exist for the current locale. + */ + public function addFallbackCatalogue(self $catalogue); + /** + * Gets the fallback catalogue. + * + * @return self|null A MessageCatalogueInterface instance or null when no fallback has been set + */ + public function getFallbackCatalogue(); + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] An array of resources + */ + public function getResources(); + /** + * Adds a resource for this collection. + */ + public function addResource(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\Resource\ResourceInterface $resource); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/MessageSelector.php b/classes/Utilities/Misc/Symfony/Component/Translation/MessageSelector.php new file mode 100755 index 00000000..28c20b19 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/MessageSelector.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +@\trigger_error(\sprintf('The "%s" class is deprecated since Symfony 4.2, use IdentityTranslator instead.', \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageSelector::class), \E_USER_DEPRECATED); +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +/** + * MessageSelector. + * + * @author Fabien Potencier + * @author Bernhard Schussek + * + * @deprecated since Symfony 4.2, use IdentityTranslator instead. + */ +class MessageSelector +{ + /** + * Given a message with different plural translations separated by a + * pipe (|), this method returns the correct portion of the message based + * on the given number, locale and the pluralization rules in the message + * itself. + * + * The message supports two different types of pluralization rules: + * + * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples + * indexed: There is one apple|There are %count% apples + * + * The indexed solution can also contain labels (e.g. one: There is one apple). + * This is purely for making the translations more clear - it does not + * affect the functionality. + * + * The two methods can also be mixed: + * {0} There are no apples|one: There is one apple|more: There are %count% apples + * + * @param string $message The message being translated + * @param int|float $number The number of items represented for the message + * @param string $locale The locale to use for choosing + * + * @return string + * + * @throws InvalidArgumentException + */ + public function choose($message, $number, $locale) + { + $parts = []; + if (\preg_match('/^\\|++$/', $message)) { + $parts = \explode('|', $message); + } elseif (\preg_match_all('/(?:\\|\\||[^\\|])++/', $message, $matches)) { + $parts = $matches[0]; + } + $explicitRules = []; + $standardRules = []; + foreach ($parts as $part) { + $part = \trim(\str_replace('||', '|', $part)); + if (\preg_match('/^(?P' . \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Interval::getIntervalRegexp() . ')\\s*(?P.*?)$/xs', $part, $matches)) { + $explicitRules[$matches['interval']] = $matches['message']; + } elseif (\preg_match('/^\\w+\\:\\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + // try to match an explicit rule, then fallback to the standard ones + foreach ($explicitRules as $interval => $m) { + if (\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Interval::test($number, $interval)) { + return $m; + } + } + $position = \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\PluralizationRules::get($number, $locale); + if (!isset($standardRules[$position])) { + // when there's exactly one rule given, and that rule is a standard + // rule, use this rule + if (1 === \count($parts) && isset($standardRules[0])) { + return $standardRules[0]; + } + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $message, $locale, $number)); + } + return $standardRules[$position]; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/MetadataAwareInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/MetadataAwareInterface.php new file mode 100755 index 00000000..ad9f4bdb --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/MetadataAwareInterface.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +/** + * MetadataAwareInterface. + * + * @author Fabien Potencier + */ +interface MetadataAwareInterface +{ + /** + * Gets metadata for the given domain and key. + * + * Passing an empty domain will return an array with all metadata indexed by + * domain and then by key. Passing an empty key will return an array with all + * metadata for the given domain. + * + * @param string $key The key + * @param string $domain The domain name + * + * @return mixed The value that was set or an array with the domains/keys or null + */ + public function getMetadata($key = '', $domain = 'messages'); + /** + * Adds metadata to a message domain. + * + * @param string $key The key + * @param mixed $value The value + * @param string $domain The domain name + */ + public function setMetadata($key, $value, $domain = 'messages'); + /** + * Deletes metadata for the given key and domain. + * + * Passing an empty domain will delete all metadata. Passing an empty key will + * delete all metadata for the given domain. + * + * @param string $key The key + * @param string $domain The domain name + */ + public function deleteMetadata($key = '', $domain = 'messages'); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/PluralizationRules.php b/classes/Utilities/Misc/Symfony/Component/Translation/PluralizationRules.php new file mode 100755 index 00000000..b71ee5c7 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/PluralizationRules.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +/** + * Returns the plural rules for a given locale. + * + * @author Fabien Potencier + * + * @deprecated since Symfony 4.2, use IdentityTranslator instead + */ +class PluralizationRules +{ + private static $rules = []; + /** + * Returns the plural position to use for the given locale and number. + * + * @param int $number The number + * @param string $locale The locale + * + * @return int The plural position + */ + public static function get($number, $locale) + { + if (3 > \func_num_args() || \func_get_arg(2)) { + @\trigger_error(\sprintf('The "%s" class is deprecated since Symfony 4.2.', __CLASS__), \E_USER_DEPRECATED); + } + if ('pt_BR' === $locale) { + // temporary set a locale for brazilian + $locale = 'xbr'; + } + if (\strlen($locale) > 3) { + $locale = \substr($locale, 0, -\strlen(\strrchr($locale, '_'))); + } + if (isset(self::$rules[$locale])) { + $return = self::$rules[$locale]($number); + if (!\is_int($return) || $return < 0) { + return 0; + } + return $return; + } + /* + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + switch ($locale) { + case 'az': + case 'bo': + case 'dz': + case 'id': + case 'ja': + case 'jv': + case 'ka': + case 'km': + case 'kn': + case 'ko': + case 'ms': + case 'th': + case 'tr': + case 'vi': + case 'zh': + return 0; + case 'af': + case 'bn': + case 'bg': + case 'ca': + case 'da': + case 'de': + case 'el': + case 'en': + case 'eo': + case 'es': + case 'et': + case 'eu': + case 'fa': + case 'fi': + case 'fo': + case 'fur': + case 'fy': + case 'gl': + case 'gu': + case 'ha': + case 'he': + case 'hu': + case 'is': + case 'it': + case 'ku': + case 'lb': + case 'ml': + case 'mn': + case 'mr': + case 'nah': + case 'nb': + case 'ne': + case 'nl': + case 'nn': + case 'no': + case 'oc': + case 'om': + case 'or': + case 'pa': + case 'pap': + case 'ps': + case 'pt': + case 'so': + case 'sq': + case 'sv': + case 'sw': + case 'ta': + case 'te': + case 'tk': + case 'ur': + case 'zu': + return 1 == $number ? 0 : 1; + case 'am': + case 'bh': + case 'fil': + case 'fr': + case 'gun': + case 'hi': + case 'hy': + case 'ln': + case 'mg': + case 'nso': + case 'xbr': + case 'ti': + case 'wa': + return 0 == $number || 1 == $number ? 0 : 1; + case 'be': + case 'bs': + case 'hr': + case 'ru': + case 'sh': + case 'sr': + case 'uk': + return 1 == $number % 10 && 11 != $number % 100 ? 0 : ($number % 10 >= 2 && $number % 10 <= 4 && ($number % 100 < 10 || $number % 100 >= 20) ? 1 : 2); + case 'cs': + case 'sk': + return 1 == $number ? 0 : ($number >= 2 && $number <= 4 ? 1 : 2); + case 'ga': + return 1 == $number ? 0 : (2 == $number ? 1 : 2); + case 'lt': + return 1 == $number % 10 && 11 != $number % 100 ? 0 : ($number % 10 >= 2 && ($number % 100 < 10 || $number % 100 >= 20) ? 1 : 2); + case 'sl': + return 1 == $number % 100 ? 0 : (2 == $number % 100 ? 1 : (3 == $number % 100 || 4 == $number % 100 ? 2 : 3)); + case 'mk': + return 1 == $number % 10 ? 0 : 1; + case 'mt': + return 1 == $number ? 0 : (0 == $number || $number % 100 > 1 && $number % 100 < 11 ? 1 : ($number % 100 > 10 && $number % 100 < 20 ? 2 : 3)); + case 'lv': + return 0 == $number ? 0 : (1 == $number % 10 && 11 != $number % 100 ? 1 : 2); + case 'pl': + return 1 == $number ? 0 : ($number % 10 >= 2 && $number % 10 <= 4 && ($number % 100 < 12 || $number % 100 > 14) ? 1 : 2); + case 'cy': + return 1 == $number ? 0 : (2 == $number ? 1 : (8 == $number || 11 == $number ? 2 : 3)); + case 'ro': + return 1 == $number ? 0 : (0 == $number || $number % 100 > 0 && $number % 100 < 20 ? 1 : 2); + case 'ar': + return 0 == $number ? 0 : (1 == $number ? 1 : (2 == $number ? 2 : ($number % 100 >= 3 && $number % 100 <= 10 ? 3 : ($number % 100 >= 11 && $number % 100 <= 99 ? 4 : 5)))); + default: + return 0; + } + } + /** + * Overrides the default plural rule for a given locale. + * + * @param callable $rule A PHP callable + * @param string $locale The locale + */ + public static function set(callable $rule, $locale) + { + @\trigger_error(\sprintf('The "%s" class is deprecated since Symfony 4.2.', __CLASS__), \E_USER_DEPRECATED); + if ('pt_BR' === $locale) { + // temporary set a locale for brazilian + $locale = 'xbr'; + } + if (\strlen($locale) > 3) { + $locale = \substr($locale, 0, -\strlen(\strrchr($locale, '_'))); + } + self::$rules[$locale] = $rule; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Reader/TranslationReader.php b/classes/Utilities/Misc/Symfony/Component/Translation/Reader/TranslationReader.php new file mode 100755 index 00000000..7012cf16 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Reader/TranslationReader.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Reader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Finder\Finder; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\LoaderInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * TranslationReader reads translation messages from translation files. + * + * @author Michel Salib + */ +class TranslationReader implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Reader\TranslationReaderInterface +{ + /** + * Loaders used for import. + * + * @var array + */ + private $loaders = []; + /** + * Adds a loader to the translation extractor. + * + * @param string $format The format of the loader + */ + public function addLoader($format, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + /** + * {@inheritdoc} + */ + public function read($directory, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalogue) + { + if (!\is_dir($directory)) { + return; + } + foreach ($this->loaders as $format => $loader) { + // load any existing translation files + $finder = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Finder\Finder(); + $extension = $catalogue->getLocale() . '.' . $format; + $files = $finder->files()->name('*.' . $extension)->in($directory); + foreach ($files as $file) { + $domain = \substr($file->getFilename(), 0, -1 * \strlen($extension) - 1); + $catalogue->addCatalogue($loader->load($file->getPathname(), $catalogue->getLocale(), $domain)); + } + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Reader/TranslationReaderInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/Reader/TranslationReaderInterface.php new file mode 100755 index 00000000..ae333fb8 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Reader/TranslationReaderInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Reader; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * TranslationReader reads translation messages from translation files. + * + * @author Tobias Nyholm + */ +interface TranslationReaderInterface +{ + /** + * Reads translation messages from a directory to the catalogue. + * + * @param string $directory + */ + public function read($directory, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalogue); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Resources/bin/translation-status.php b/classes/Utilities/Misc/Symfony/Component/Translation/Resources/bin/translation-status.php new file mode 100755 index 00000000..47b97559 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Resources/bin/translation-status.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +$usageInstructions = << \false, + // NULL = analyze all locales + 'locale_to_analyze' => null, + // the reference files all the other translations are compared to + 'original_files' => ['src/Symfony/Component/Form/Resources/translations/validators.en.xlf', 'src/Symfony/Component/Security/Core/Resources/translations/security.en.xlf', 'src/Symfony/Component/Validator/Resources/translations/validators.en.xlf'], +]; +$argc = $_SERVER['argc']; +$argv = $_SERVER['argv']; +if ($argc > 3) { + echo \str_replace('translation-status.php', $argv[0], $usageInstructions); + exit(1); +} +foreach (\array_slice($argv, 1) as $argumentOrOption) { + if (0 === \strpos($argumentOrOption, '-')) { + $config['verbose_output'] = \true; + } else { + $config['locale_to_analyze'] = $argumentOrOption; + } +} +foreach ($config['original_files'] as $originalFilePath) { + if (!\file_exists($originalFilePath)) { + echo \sprintf('The following file does not exist. Make sure that you execute this command at the root dir of the Symfony code repository.%s %s', \PHP_EOL, $originalFilePath); + exit(1); + } +} +$totalMissingTranslations = 0; +foreach ($config['original_files'] as $originalFilePath) { + $translationFilePaths = \ILAB\MediaCloud\Utilities\Misc\findTranslationFiles($originalFilePath, $config['locale_to_analyze']); + $translationStatus = \ILAB\MediaCloud\Utilities\Misc\calculateTranslationStatus($originalFilePath, $translationFilePaths); + $totalMissingTranslations += \array_sum(\array_map(function ($translation) { + return \count($translation['missingKeys']); + }, \array_values($translationStatus))); + \ILAB\MediaCloud\Utilities\Misc\printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output']); +} +exit($totalMissingTranslations > 0 ? 1 : 0); +function findTranslationFiles($originalFilePath, $localeToAnalyze) +{ + $translations = []; + $translationsDir = \dirname($originalFilePath); + $originalFileName = \basename($originalFilePath); + $translationFileNamePattern = \str_replace('.en.', '.*.', $originalFileName); + $translationFiles = \glob($translationsDir . '/' . $translationFileNamePattern, \GLOB_NOSORT); + \sort($translationFiles); + foreach ($translationFiles as $filePath) { + $locale = \ILAB\MediaCloud\Utilities\Misc\extractLocaleFromFilePath($filePath); + if (null !== $localeToAnalyze && $locale !== $localeToAnalyze) { + continue; + } + $translations[$locale] = $filePath; + } + return $translations; +} +function calculateTranslationStatus($originalFilePath, $translationFilePaths) +{ + $translationStatus = []; + $allTranslationKeys = \ILAB\MediaCloud\Utilities\Misc\extractTranslationKeys($originalFilePath); + foreach ($translationFilePaths as $locale => $translationPath) { + $translatedKeys = \ILAB\MediaCloud\Utilities\Misc\extractTranslationKeys($translationPath); + $missingKeys = \array_diff_key($allTranslationKeys, $translatedKeys); + $translationStatus[$locale] = ['total' => \count($allTranslationKeys), 'translated' => \count($translatedKeys), 'missingKeys' => $missingKeys]; + } + return $translationStatus; +} +function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput) +{ + \ILAB\MediaCloud\Utilities\Misc\printTitle($originalFilePath); + \ILAB\MediaCloud\Utilities\Misc\printTable($translationStatus, $verboseOutput); + echo \PHP_EOL . \PHP_EOL; +} +function extractLocaleFromFilePath($filePath) +{ + $parts = \explode('.', $filePath); + return $parts[\count($parts) - 2]; +} +function extractTranslationKeys($filePath) +{ + $translationKeys = []; + $contents = new \SimpleXMLElement(\file_get_contents($filePath)); + foreach ($contents->file->body->{'trans-unit'} as $translationKey) { + $translationId = (string) $translationKey['id']; + $translationKey = (string) $translationKey->source; + $translationKeys[$translationId] = $translationKey; + } + return $translationKeys; +} +function printTitle($title) +{ + echo $title . \PHP_EOL; + echo \str_repeat('=', \strlen($title)) . \PHP_EOL . \PHP_EOL; +} +function printTable($translations, $verboseOutput) +{ + if (0 === \count($translations)) { + echo 'No translations found'; + return; + } + $longestLocaleNameLength = \max(\array_map('strlen', \array_keys($translations))); + foreach ($translations as $locale => $translation) { + if ($translation['translated'] > $translation['total']) { + \ILAB\MediaCloud\Utilities\Misc\textColorRed(); + } elseif ($translation['translated'] === $translation['total']) { + \ILAB\MediaCloud\Utilities\Misc\textColorGreen(); + } + echo \sprintf('| Locale: %-' . $longestLocaleNameLength . 's | Translated: %d/%d', $locale, $translation['translated'], $translation['total']) . \PHP_EOL; + \ILAB\MediaCloud\Utilities\Misc\textColorNormal(); + if (\true === $verboseOutput && \count($translation['missingKeys']) > 0) { + echo \str_repeat('-', 80) . \PHP_EOL; + echo '| Missing Translations:' . \PHP_EOL; + foreach ($translation['missingKeys'] as $id => $content) { + echo \sprintf('| (id=%s) %s', $id, $content) . \PHP_EOL; + } + echo \str_repeat('-', 80) . \PHP_EOL; + } + } +} +function textColorGreen() +{ + echo "\33[32m"; +} +function textColorRed() +{ + echo "\33[31m"; +} +function textColorNormal() +{ + echo "\33[0m"; +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Resources/data/parents.json b/classes/Utilities/Misc/Symfony/Component/Translation/Resources/data/parents.json new file mode 100755 index 00000000..6e45d9f3 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Resources/data/parents.json @@ -0,0 +1,136 @@ +{ + "az_Cyrl": "root", + "bs_Cyrl": "root", + "en_150": "en_001", + "en_AG": "en_001", + "en_AI": "en_001", + "en_AT": "en_150", + "en_AU": "en_001", + "en_BB": "en_001", + "en_BE": "en_150", + "en_BM": "en_001", + "en_BS": "en_001", + "en_BW": "en_001", + "en_BZ": "en_001", + "en_CA": "en_001", + "en_CC": "en_001", + "en_CH": "en_150", + "en_CK": "en_001", + "en_CM": "en_001", + "en_CX": "en_001", + "en_CY": "en_001", + "en_DE": "en_150", + "en_DG": "en_001", + "en_DK": "en_150", + "en_DM": "en_001", + "en_ER": "en_001", + "en_FI": "en_150", + "en_FJ": "en_001", + "en_FK": "en_001", + "en_FM": "en_001", + "en_GB": "en_001", + "en_GD": "en_001", + "en_GG": "en_001", + "en_GH": "en_001", + "en_GI": "en_001", + "en_GM": "en_001", + "en_GY": "en_001", + "en_HK": "en_001", + "en_IE": "en_001", + "en_IL": "en_001", + "en_IM": "en_001", + "en_IN": "en_001", + "en_IO": "en_001", + "en_JE": "en_001", + "en_JM": "en_001", + "en_KE": "en_001", + "en_KI": "en_001", + "en_KN": "en_001", + "en_KY": "en_001", + "en_LC": "en_001", + "en_LR": "en_001", + "en_LS": "en_001", + "en_MG": "en_001", + "en_MO": "en_001", + "en_MS": "en_001", + "en_MT": "en_001", + "en_MU": "en_001", + "en_MW": "en_001", + "en_MY": "en_001", + "en_NA": "en_001", + "en_NF": "en_001", + "en_NG": "en_001", + "en_NL": "en_150", + "en_NR": "en_001", + "en_NU": "en_001", + "en_NZ": "en_001", + "en_PG": "en_001", + "en_PH": "en_001", + "en_PK": "en_001", + "en_PN": "en_001", + "en_PW": "en_001", + "en_RW": "en_001", + "en_SB": "en_001", + "en_SC": "en_001", + "en_SD": "en_001", + "en_SE": "en_150", + "en_SG": "en_001", + "en_SH": "en_001", + "en_SI": "en_150", + "en_SL": "en_001", + "en_SS": "en_001", + "en_SX": "en_001", + "en_SZ": "en_001", + "en_TC": "en_001", + "en_TK": "en_001", + "en_TO": "en_001", + "en_TT": "en_001", + "en_TV": "en_001", + "en_TZ": "en_001", + "en_UG": "en_001", + "en_VC": "en_001", + "en_VG": "en_001", + "en_VU": "en_001", + "en_WS": "en_001", + "en_ZA": "en_001", + "en_ZM": "en_001", + "en_ZW": "en_001", + "es_AR": "es_419", + "es_BO": "es_419", + "es_BR": "es_419", + "es_BZ": "es_419", + "es_CL": "es_419", + "es_CO": "es_419", + "es_CR": "es_419", + "es_CU": "es_419", + "es_DO": "es_419", + "es_EC": "es_419", + "es_GT": "es_419", + "es_HN": "es_419", + "es_MX": "es_419", + "es_NI": "es_419", + "es_PA": "es_419", + "es_PE": "es_419", + "es_PR": "es_419", + "es_PY": "es_419", + "es_SV": "es_419", + "es_US": "es_419", + "es_UY": "es_419", + "es_VE": "es_419", + "pa_Arab": "root", + "pt_AO": "pt_PT", + "pt_CH": "pt_PT", + "pt_CV": "pt_PT", + "pt_GQ": "pt_PT", + "pt_GW": "pt_PT", + "pt_LU": "pt_PT", + "pt_MO": "pt_PT", + "pt_MZ": "pt_PT", + "pt_ST": "pt_PT", + "pt_TL": "pt_PT", + "sr_Latn": "root", + "uz_Arab": "root", + "uz_Cyrl": "root", + "zh_Hant": "root", + "zh_Hant_MO": "zh_Hant_HK" +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd b/classes/Utilities/Misc/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd new file mode 100755 index 00000000..dface628 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd @@ -0,0 +1,2223 @@ + + + + + + + + + + + + + + + Values for the attribute 'context-type'. + + + + + Indicates a database content. + + + + + Indicates the content of an element within an XML document. + + + + + Indicates the name of an element within an XML document. + + + + + Indicates the line number from the sourcefile (see context-type="sourcefile") where the <source> is found. + + + + + Indicates a the number of parameters contained within the <source>. + + + + + Indicates notes pertaining to the parameters in the <source>. + + + + + Indicates the content of a record within a database. + + + + + Indicates the name of a record within a database. + + + + + Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original <file> attribute in that this sourcefile is one of many that make up that file. + + + + + + + Values for the attribute 'count-type'. + + + + + Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts. + + + + + Indicates the count units are translation units existing already in the same document. + + + + + Indicates a total count. + + + + + + + Values for the attribute 'ctype' when used other elements than <ph> or <x>. + + + + + Indicates a run of bolded text. + + + + + Indicates a run of text in italics. + + + + + Indicates a run of underlined text. + + + + + Indicates a run of hyper-text. + + + + + + + Values for the attribute 'ctype' when used with <ph> or <x>. + + + + + Indicates a inline image. + + + + + Indicates a page break. + + + + + Indicates a line break. + + + + + + + + + + + + Values for the attribute 'datatype'. + + + + + Indicates Active Server Page data. + + + + + Indicates C source file data. + + + + + Indicates Channel Definition Format (CDF) data. + + + + + Indicates ColdFusion data. + + + + + Indicates C++ source file data. + + + + + Indicates C-Sharp data. + + + + + Indicates strings from C, ASM, and driver files data. + + + + + Indicates comma-separated values data. + + + + + Indicates database data. + + + + + Indicates portions of document that follows data and contains metadata. + + + + + Indicates portions of document that precedes data and contains metadata. + + + + + Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import). + + + + + Indicates standard user input screen data. + + + + + Indicates HyperText Markup Language (HTML) data - document instance. + + + + + Indicates content within an HTML document’s <body> element. + + + + + Indicates Windows INI file data. + + + + + Indicates Interleaf data. + + + + + Indicates Java source file data (extension '.java'). + + + + + Indicates Java property resource bundle data. + + + + + Indicates Java list resource bundle data. + + + + + Indicates JavaScript source file data. + + + + + Indicates JScript source file data. + + + + + Indicates information relating to formatting. + + + + + Indicates LISP source file data. + + + + + Indicates information relating to margin formats. + + + + + Indicates a file containing menu. + + + + + Indicates numerically identified string table. + + + + + Indicates Maker Interchange Format (MIF) data. + + + + + Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute. + + + + + Indicates GNU Machine Object data. + + + + + Indicates Message Librarian strings created by Novell's Message Librarian Tool. + + + + + Indicates information to be displayed at the bottom of each page of a document. + + + + + Indicates information to be displayed at the top of each page of a document. + + + + + Indicates a list of property values (e.g., settings within INI files or preferences dialog). + + + + + Indicates Pascal source file data. + + + + + Indicates Hypertext Preprocessor data. + + + + + Indicates plain text file (no formatting other than, possibly, wrapping). + + + + + Indicates GNU Portable Object file. + + + + + Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc. + + + + + Indicates Windows .NET binary resources. + + + + + Indicates Windows .NET Resources. + + + + + Indicates Rich Text Format (RTF) data. + + + + + Indicates Standard Generalized Markup Language (SGML) data - document instance. + + + + + Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD). + + + + + Indicates Scalable Vector Graphic (SVG) data. + + + + + Indicates VisualBasic Script source file. + + + + + Indicates warning message. + + + + + Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file). + + + + + Indicates Extensible HyperText Markup Language (XHTML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD). + + + + + Indicates Extensible Stylesheet Language (XSL) data. + + + + + Indicates XUL elements. + + + + + + + Values for the attribute 'mtype'. + + + + + Indicates the marked text is an abbreviation. + + + + + ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept. + + + + + ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective'). + + + + + ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging'). + + + + + ISO-12620: A proper-name term, such as the name of an agency or other proper entity. + + + + + ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another. + + + + + ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language. + + + + + Indicates the marked text is a date and/or time. + + + + + ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign. + + + + + ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form. + + + + + ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula. + + + + + ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record. + + + + + ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy'). + + + + + ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body. + + + + + ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages. + + + + + ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like. + + + + + ISO-12620 2.1.17: A unit to track object. + + + + + Indicates the marked text is a name. + + + + + ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others. + + + + + ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system. + + + + + Indicates the marked text is a phrase. + + + + + ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase. + + + + + Indicates the marked text should not be translated. + + + + + ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet. + + + + + Indicates that the marked text represents a segment. + + + + + ISO-12620 2.1.18.2: A fixed, lexicalized phrase. + + + + + ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs'). + + + + + ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system. + + + + + ISO-12620 2.1.19: A fixed chunk of recurring text. + + + + + ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof. + + + + + ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry. + + + + + ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language. + + + + + Indicates the marked text is a term. + + + + + ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted. + + + + + ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system. + + + + + ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza'). + + + + + ISO-12620 2.1.9: One of the alternate forms of a term. + + + + + + + Values for the attribute 'restype'. + + + + + Indicates a Windows RC AUTO3STATE control. + + + + + Indicates a Windows RC AUTOCHECKBOX control. + + + + + Indicates a Windows RC AUTORADIOBUTTON control. + + + + + Indicates a Windows RC BEDIT control. + + + + + Indicates a bitmap, for example a BITMAP resource in Windows. + + + + + Indicates a button object, for example a BUTTON control Windows. + + + + + Indicates a caption, such as the caption of a dialog box. + + + + + Indicates the cell in a table, for example the content of the <td> element in HTML. + + + + + Indicates check box object, for example a CHECKBOX control in Windows. + + + + + Indicates a menu item with an associated checkbox. + + + + + Indicates a list box, but with a check-box for each item. + + + + + Indicates a color selection dialog. + + + + + Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows. + + + + + Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234). + + + + + Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403). + + + + + Indicates a UI base class element that cannot be represented by any other element. + + + + + Indicates a context menu. + + + + + Indicates a Windows RC CTEXT control. + + + + + Indicates a cursor, for example a CURSOR resource in Windows. + + + + + Indicates a date/time picker. + + + + + Indicates a Windows RC DEFPUSHBUTTON control. + + + + + Indicates a dialog box. + + + + + Indicates a Windows RC DLGINIT resource block. + + + + + Indicates an edit box object, for example an EDIT control in Windows. + + + + + Indicates a filename. + + + + + Indicates a file dialog. + + + + + Indicates a footnote. + + + + + Indicates a font name. + + + + + Indicates a footer. + + + + + Indicates a frame object. + + + + + Indicates a XUL grid element. + + + + + Indicates a groupbox object, for example a GROUPBOX control in Windows. + + + + + Indicates a header item. + + + + + Indicates a heading, such has the content of <h1>, <h2>, etc. in HTML. + + + + + Indicates a Windows RC HEDIT control. + + + + + Indicates a horizontal scrollbar. + + + + + Indicates an icon, for example an ICON resource in Windows. + + + + + Indicates a Windows RC IEDIT control. + + + + + Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF. + + + + + Indicates a label object. + + + + + Indicates a label that is also a HTML link (not necessarily a URL). + + + + + Indicates a list (a group of list-items, for example an <ol> or <ul> element in HTML). + + + + + Indicates a listbox object, for example an LISTBOX control in Windows. + + + + + Indicates an list item (an entry in a list). + + + + + Indicates a Windows RC LTEXT control. + + + + + Indicates a menu (a group of menu-items). + + + + + Indicates a toolbar containing one or more tope level menus. + + + + + Indicates a menu item (an entry in a menu). + + + + + Indicates a XUL menuseparator element. + + + + + Indicates a message, for example an entry in a MESSAGETABLE resource in Windows. + + + + + Indicates a calendar control. + + + + + Indicates an edit box beside a spin control. + + + + + Indicates a catch all for rectangular areas. + + + + + Indicates a standalone menu not necessarily associated with a menubar. + + + + + Indicates a pushbox object, for example a PUSHBOX control in Windows. + + + + + Indicates a Windows RC PUSHBUTTON control. + + + + + Indicates a radio button object. + + + + + Indicates a menuitem with associated radio button. + + + + + Indicates raw data resources for an application. + + + + + Indicates a row in a table. + + + + + Indicates a Windows RC RTEXT control. + + + + + Indicates a user navigable container used to show a portion of a document. + + + + + Indicates a generic divider object (e.g. menu group separator). + + + + + Windows accelerators, shortcuts in resource or property files. + + + + + Indicates a UI control to indicate process activity but not progress. + + + + + Indicates a splitter bar. + + + + + Indicates a Windows RC STATE3 control. + + + + + Indicates a window for providing feedback to the users, like 'read-only', etc. + + + + + Indicates a string, for example an entry in a STRINGTABLE resource in Windows. + + + + + Indicates a layers of controls with a tab to select layers. + + + + + Indicates a display and edits regular two-dimensional tables of cells. + + + + + Indicates a XUL textbox element. + + + + + Indicates a UI button that can be toggled to on or off state. + + + + + Indicates an array of controls, usually buttons. + + + + + Indicates a pop up tool tip text. + + + + + Indicates a bar with a pointer indicating a position within a certain range. + + + + + Indicates a control that displays a set of hierarchical data. + + + + + Indicates a URI (URN or URL). + + + + + Indicates a Windows RC USERBUTTON control. + + + + + Indicates a user-defined control like CONTROL control in Windows. + + + + + Indicates the text of a variable. + + + + + Indicates version information about a resource like VERSIONINFO in Windows. + + + + + Indicates a vertical scrollbar. + + + + + Indicates a graphical window. + + + + + + + Values for the attribute 'size-unit'. + + + + + Indicates a size in 8-bit bytes. + + + + + Indicates a size in Unicode characters. + + + + + Indicates a size in columns. Used for HTML text area. + + + + + Indicates a size in centimeters. + + + + + Indicates a size in dialog units, as defined in Windows resources. + + + + + Indicates a size in 'font-size' units (as defined in CSS). + + + + + Indicates a size in 'x-height' units (as defined in CSS). + + + + + Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster' + + + + + Indicates a size in inches. + + + + + Indicates a size in millimeters. + + + + + Indicates a size in percentage. + + + + + Indicates a size in pixels. + + + + + Indicates a size in point. + + + + + Indicates a size in rows. Used for HTML text area. + + + + + + + Values for the attribute 'state'. + + + + + Indicates the terminating state. + + + + + Indicates only non-textual information needs adaptation. + + + + + Indicates both text and non-textual information needs adaptation. + + + + + Indicates only non-textual information needs review. + + + + + Indicates both text and non-textual information needs review. + + + + + Indicates that only the text of the item needs to be reviewed. + + + + + Indicates that the item needs to be translated. + + + + + Indicates that the item is new. For example, translation units that were not in a previous version of the document. + + + + + Indicates that changes are reviewed and approved. + + + + + Indicates that the item has been translated. + + + + + + + Values for the attribute 'state-qualifier'. + + + + + Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously. + + + + + Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.). + + + + + Indicates a match based on matching IDs (in addition to matching text). + + + + + Indicates a translation derived from a glossary. + + + + + Indicates a translation derived from existing translation. + + + + + Indicates a translation derived from machine translation. + + + + + Indicates a translation derived from a translation repository. + + + + + Indicates a translation derived from a translation memory. + + + + + Indicates the translation is suggested by machine translation. + + + + + Indicates that the item has been rejected because of incorrect grammar. + + + + + Indicates that the item has been rejected because it is incorrect. + + + + + Indicates that the item has been rejected because it is too long or too short. + + + + + Indicates that the item has been rejected because of incorrect spelling. + + + + + Indicates the translation is suggested by translation memory. + + + + + + + Values for the attribute 'unit'. + + + + + Refers to words. + + + + + Refers to pages. + + + + + Refers to <trans-unit> elements. + + + + + Refers to <bin-unit> elements. + + + + + Refers to glyphs. + + + + + Refers to <trans-unit> and/or <bin-unit> elements. + + + + + Refers to the occurrences of instances defined by the count-type value. + + + + + Refers to characters. + + + + + Refers to lines. + + + + + Refers to sentences. + + + + + Refers to paragraphs. + + + + + Refers to segments. + + + + + Refers to placeables (inline elements). + + + + + + + Values for the attribute 'priority'. + + + + + Highest priority. + + + + + High priority. + + + + + High priority, but not as important as 2. + + + + + High priority, but not as important as 3. + + + + + Medium priority, but more important than 6. + + + + + Medium priority, but less important than 5. + + + + + Low priority, but more important than 8. + + + + + Low priority, but more important than 9. + + + + + Low priority. + + + + + Lowest priority. + + + + + + + + + This value indicates that all properties can be reformatted. This value must be used alone. + + + + + This value indicates that no properties should be reformatted. This value must be used alone. + + + + + + + + + + + + + This value indicates that all information in the coord attribute can be modified. + + + + + This value indicates that the x information in the coord attribute can be modified. + + + + + This value indicates that the y information in the coord attribute can be modified. + + + + + This value indicates that the cx information in the coord attribute can be modified. + + + + + This value indicates that the cy information in the coord attribute can be modified. + + + + + This value indicates that all the information in the font attribute can be modified. + + + + + This value indicates that the name information in the font attribute can be modified. + + + + + This value indicates that the size information in the font attribute can be modified. + + + + + This value indicates that the weight information in the font attribute can be modified. + + + + + This value indicates that the information in the css-style attribute can be modified. + + + + + This value indicates that the information in the style attribute can be modified. + + + + + This value indicates that the information in the exstyle attribute can be modified. + + + + + + + + + + + + + Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document. + + + + + Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed. + + + + + Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed. + + + + + + + + + Represents a translation proposal from a translation memory or other resource. + + + + + Represents a previous version of the target element. + + + + + Represents a rejected version of the target element. + + + + + Represents a translation to be used for reference purposes only, for example from a related product or a different language. + + + + + Represents a proposed translation that was used for the translation of the trans-unit, possibly modified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Values for the attribute 'coord'. + + + + + + + + Version values: 1.0 and 1.1 are allowed for backward compatibility. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Resources/schemas/xliff-core-2.0.xsd b/classes/Utilities/Misc/Symfony/Component/Translation/Resources/schemas/xliff-core-2.0.xsd new file mode 100755 index 00000000..963232f9 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Resources/schemas/xliff-core-2.0.xsd @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Resources/schemas/xml.xsd b/classes/Utilities/Misc/Symfony/Component/Translation/Resources/schemas/xml.xsd new file mode 100755 index 00000000..a46162a7 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Resources/schemas/xml.xsd @@ -0,0 +1,309 @@ + + + + + + +
+

About the XML namespace

+ +
+

+ + This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

+

+ See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

+ +

+ Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

+

+ See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

+
+
+ +
+
+ + + + +
+ +

lang (as an attribute name)

+

+ + denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

+ +
+
+

Notes

+

+ Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

+

+ + See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

+

+ + The union allows for the 'un-declaration' of xml:lang with + the empty string. +

+
+
+
+ + + + + + + + + + +
+ + + + + +
+ +

space (as an attribute name)

+

+ denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

+ +
+
+
+ + + + + + + +
+ + + + +
+ +

base (as an attribute name)

+

+ denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

+ +

+ See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

+ +
+
+
+
+ + + + +
+ +

id (as an attribute name)

+

+ + denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

+ +

+ See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

+
+
+
+ +
+ + + + + + + + + + + +
+ +

Father (in any context at all)

+ +
+

+ denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

+
+

+ + In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

+
+
+
+
+
+ + + + +
+

About this schema document

+ +
+

+ This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

+ +

+ To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

+
+          <schema.. .>
+          .. .
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+     
+

+ or +

+
+
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+     
+

+ Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

+
+          <type.. .>
+          .. .
+           <attributeGroup ref="xml:specialAttrs"/>
+     
+

+ will define a type which will schema-validate an instance element + with any of those attributes. +

+ +
+
+
+
+ + + +
+

Versioning policy for this schema document

+ +
+

+ In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

+

+ At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

+ +

+ The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

+

+ + Previous dated (and unchanging) versions of this schema + document are at: +

+ +
+
+
+
+ +
diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Translator.php b/classes/Utilities/Misc/Symfony/Component/Translation/Translator.php new file mode 100755 index 00000000..081c271c --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Translator.php @@ -0,0 +1,442 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\ConfigCacheFactory; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\ConfigCacheFactoryInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\ConfigCacheInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\RuntimeException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\IntlFormatterInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\MessageFormatter; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\MessageFormatterInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\LoaderInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface; +/** + * @author Fabien Potencier + */ +class Translator implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\TranslatorInterface, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\TranslatorBagInterface +{ + /** + * @var MessageCatalogueInterface[] + */ + protected $catalogues = []; + /** + * @var string + */ + private $locale; + /** + * @var array + */ + private $fallbackLocales = []; + /** + * @var LoaderInterface[] + */ + private $loaders = []; + /** + * @var array + */ + private $resources = []; + /** + * @var MessageFormatterInterface + */ + private $formatter; + /** + * @var string + */ + private $cacheDir; + /** + * @var bool + */ + private $debug; + private $cacheVary; + /** + * @var ConfigCacheFactoryInterface|null + */ + private $configCacheFactory; + /** + * @var array|null + */ + private $parentLocales; + private $hasIntlFormatter; + /** + * @throws InvalidArgumentException If a locale contains invalid characters + */ + public function __construct(?string $locale, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = \false, array $cacheVary = []) + { + if (null === $locale) { + @\trigger_error(\sprintf('Passing "null" as the $locale argument to %s() is deprecated since Symfony 4.4.', __METHOD__), \E_USER_DEPRECATED); + } + $this->setLocale($locale, \false); + if (null === $formatter) { + $formatter = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\MessageFormatter(); + } + $this->formatter = $formatter; + $this->cacheDir = $cacheDir; + $this->debug = $debug; + $this->cacheVary = $cacheVary; + $this->hasIntlFormatter = $formatter instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\IntlFormatterInterface; + } + public function setConfigCacheFactory(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\ConfigCacheFactoryInterface $configCacheFactory) + { + $this->configCacheFactory = $configCacheFactory; + } + /** + * Adds a Loader. + * + * @param string $format The name of the loader (@see addResource()) + */ + public function addLoader($format, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Loader\LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + /** + * Adds a Resource. + * + * @param string $format The name of the loader (@see addLoader()) + * @param mixed $resource The resource name + * @param string $locale The locale + * @param string $domain The domain + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function addResource($format, $resource, $locale, $domain = null) + { + if (null === $domain) { + $domain = 'messages'; + } + if (null === $locale) { + @\trigger_error(\sprintf('Passing "null" to the third argument of the "%s" method has been deprecated since Symfony 4.4 and will throw an error in 5.0.', __METHOD__), \E_USER_DEPRECATED); + } + $this->assertValidLocale($locale); + $this->resources[$locale][] = [$format, $resource, $domain]; + if (\in_array($locale, $this->fallbackLocales)) { + $this->catalogues = []; + } else { + unset($this->catalogues[$locale]); + } + } + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + if (null === $locale && (2 > \func_num_args() || \func_get_arg(1))) { + @\trigger_error(\sprintf('Passing "null" as the $locale argument to %s() is deprecated since Symfony 4.4.', __METHOD__), \E_USER_DEPRECATED); + } + $this->assertValidLocale($locale); + $this->locale = $locale; + } + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale; + } + /** + * Sets the fallback locales. + * + * @param array $locales The fallback locales + * + * @throws InvalidArgumentException If a locale contains invalid characters + */ + public function setFallbackLocales(array $locales) + { + // needed as the fallback locales are linked to the already loaded catalogues + $this->catalogues = []; + foreach ($locales as $locale) { + if (null === $locale) { + @\trigger_error(\sprintf('Passing "null" as the $locale argument to %s() is deprecated since Symfony 4.4.', __METHOD__), \E_USER_DEPRECATED); + } + $this->assertValidLocale($locale); + } + $this->fallbackLocales = $this->cacheVary['fallback_locales'] = $locales; + } + /** + * Gets the fallback locales. + * + * @internal since Symfony 4.2 + * + * @return array The fallback locales + */ + public function getFallbackLocales() + { + return $this->fallbackLocales; + } + /** + * {@inheritdoc} + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null) + { + if ('' === ($id = (string) $id)) { + return ''; + } + if (null === $domain) { + $domain = 'messages'; + } + $catalogue = $this->getCatalogue($locale); + $locale = $catalogue->getLocale(); + while (!$catalogue->defines($id, $domain)) { + if ($cat = $catalogue->getFallbackCatalogue()) { + $catalogue = $cat; + $locale = $catalogue->getLocale(); + } else { + break; + } + } + if ($this->hasIntlFormatter && $catalogue->defines($id, $domain . \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters); + } + return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters); + } + /** + * {@inheritdoc} + * + * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter + */ + public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null) + { + @\trigger_error(\sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%%count%%" parameter.', __METHOD__), \E_USER_DEPRECATED); + if ('' === ($id = (string) $id)) { + return ''; + } + if (!$this->formatter instanceof \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\LogicException(\sprintf('The formatter "%s" does not support plural translations.', \get_class($this->formatter))); + } + if (null === $domain) { + $domain = 'messages'; + } + $catalogue = $this->getCatalogue($locale); + $locale = $catalogue->getLocale(); + while (!$catalogue->defines($id, $domain)) { + if ($cat = $catalogue->getFallbackCatalogue()) { + $catalogue = $cat; + $locale = $catalogue->getLocale(); + } else { + break; + } + } + if ($this->hasIntlFormatter && $catalogue->defines($id, $domain . \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, ['%count%' => $number] + $parameters); + } + return $this->formatter->choiceFormat($catalogue->get($id, $domain), $number, $locale, $parameters); + } + /** + * {@inheritdoc} + */ + public function getCatalogue($locale = null) + { + if (null === $locale) { + $locale = $this->getLocale(); + } else { + $this->assertValidLocale($locale); + } + if (!isset($this->catalogues[$locale])) { + $this->loadCatalogue($locale); + } + return $this->catalogues[$locale]; + } + /** + * Gets the loaders. + * + * @return array LoaderInterface[] + */ + protected function getLoaders() + { + return $this->loaders; + } + /** + * @param string $locale + */ + protected function loadCatalogue($locale) + { + if (null === $this->cacheDir) { + $this->initializeCatalogue($locale); + } else { + $this->initializeCacheCatalogue($locale); + } + } + /** + * @param string $locale + */ + protected function initializeCatalogue($locale) + { + $this->assertValidLocale($locale); + try { + $this->doLoadCatalogue($locale); + } catch (\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\NotFoundResourceException $e) { + if (!$this->computeFallbackLocales($locale)) { + throw $e; + } + } + $this->loadFallbackCatalogues($locale); + } + private function initializeCacheCatalogue(string $locale) : void + { + if (isset($this->catalogues[$locale])) { + /* Catalogue already initialized. */ + return; + } + $this->assertValidLocale($locale); + $cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale), function (\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\ConfigCacheInterface $cache) use($locale) { + $this->dumpCatalogue($locale, $cache); + }); + if (isset($this->catalogues[$locale])) { + /* Catalogue has been initialized as it was written out to cache. */ + return; + } + /* Read catalogue from cache. */ + $this->catalogues[$locale] = (include $cache->getPath()); + } + private function dumpCatalogue(string $locale, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\ConfigCacheInterface $cache) : void + { + $this->initializeCatalogue($locale); + $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]); + $content = \sprintf(<<getAllMessages($this->catalogues[$locale]), \true), $fallbackContent); + $cache->write($content, $this->catalogues[$locale]->getResources()); + } + private function getFallbackContent(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalogue) : string + { + $fallbackContent = ''; + $current = ''; + $replacementPattern = '/[^a-z0-9_]/i'; + $fallbackCatalogue = $catalogue->getFallbackCatalogue(); + while ($fallbackCatalogue) { + $fallback = $fallbackCatalogue->getLocale(); + $fallbackSuffix = \ucfirst(\preg_replace($replacementPattern, '_', $fallback)); + $currentSuffix = \ucfirst(\preg_replace($replacementPattern, '_', $current)); + $fallbackContent .= \sprintf(<<<'EOF' +$catalogue%s = new MessageCatalogue('%s', %s); +$catalogue%s->addFallbackCatalogue($catalogue%s); + +EOF +, $fallbackSuffix, $fallback, \var_export($this->getAllMessages($fallbackCatalogue), \true), $currentSuffix, $fallbackSuffix); + $current = $fallbackCatalogue->getLocale(); + $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); + } + return $fallbackContent; + } + private function getCatalogueCachePath(string $locale) : string + { + return $this->cacheDir . '/catalogue.' . $locale . '.' . \strtr(\substr(\base64_encode(\hash('sha256', \serialize($this->cacheVary), \true)), 0, 7), '/', '_') . '.php'; + } + /** + * @internal + */ + protected function doLoadCatalogue(string $locale) : void + { + $this->catalogues[$locale] = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue($locale); + if (isset($this->resources[$locale])) { + foreach ($this->resources[$locale] as $resource) { + if (!isset($this->loaders[$resource[0]])) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\RuntimeException(\sprintf('The "%s" translation loader is not registered.', $resource[0])); + } + $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2])); + } + } + } + private function loadFallbackCatalogues(string $locale) : void + { + $current = $this->catalogues[$locale]; + foreach ($this->computeFallbackLocales($locale) as $fallback) { + if (!isset($this->catalogues[$fallback])) { + $this->initializeCatalogue($fallback); + } + $fallbackCatalogue = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue($fallback, $this->getAllMessages($this->catalogues[$fallback])); + foreach ($this->catalogues[$fallback]->getResources() as $resource) { + $fallbackCatalogue->addResource($resource); + } + $current->addFallbackCatalogue($fallbackCatalogue); + $current = $fallbackCatalogue; + } + } + protected function computeFallbackLocales($locale) + { + if (null === $this->parentLocales) { + $parentLocales = \json_decode(\file_get_contents(__DIR__ . '/Resources/data/parents.json'), \true); + } + $locales = []; + foreach ($this->fallbackLocales as $fallback) { + if ($fallback === $locale) { + continue; + } + $locales[] = $fallback; + } + while ($locale) { + $parent = $parentLocales[$locale] ?? null; + if (!$parent && \false !== \strrchr($locale, '_')) { + $locale = \substr($locale, 0, -\strlen(\strrchr($locale, '_'))); + } elseif ('root' !== $parent) { + $locale = $parent; + } else { + $locale = null; + } + if (null !== $locale) { + \array_unshift($locales, $locale); + } + } + return \array_unique($locales); + } + /** + * Asserts that the locale is valid, throws an Exception if not. + * + * @param string $locale Locale to tests + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + protected function assertValidLocale($locale) + { + if (1 !== \preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('Invalid "%s" locale.', $locale)); + } + } + /** + * Provides the ConfigCache factory implementation, falling back to a + * default implementation if necessary. + */ + private function getConfigCacheFactory() : \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\ConfigCacheFactoryInterface + { + if (!$this->configCacheFactory) { + $this->configCacheFactory = new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Config\ConfigCacheFactory($this->debug); + } + return $this->configCacheFactory; + } + private function getAllMessages(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogueInterface $catalogue) : array + { + $allMessages = []; + foreach ($catalogue->all() as $domain => $messages) { + if ($intlMessages = $catalogue->all($domain . \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + $allMessages[$domain . \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue::INTL_DOMAIN_SUFFIX] = $intlMessages; + $messages = \array_diff_key($messages, $intlMessages); + } + if ($messages) { + $allMessages[$domain] = $messages; + } + } + return $allMessages; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/TranslatorBagInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/TranslatorBagInterface.php new file mode 100755 index 00000000..3ae67d82 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/TranslatorBagInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +/** + * TranslatorBagInterface. + * + * @author Abdellatif Ait boudad + */ +interface TranslatorBagInterface +{ + /** + * Gets the catalogue by locale. + * + * @param string|null $locale The locale or null to use the default + * + * @return MessageCatalogueInterface + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function getCatalogue($locale = null); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/TranslatorInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/TranslatorInterface.php new file mode 100755 index 00000000..7ed16616 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/TranslatorInterface.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 ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\LocaleAwareInterface; +/** + * TranslatorInterface. + * + * @author Fabien Potencier + * + * @deprecated since Symfony 4.2, use Symfony\Contracts\Translation\TranslatorInterface instead + */ +interface TranslatorInterface extends \ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation\LocaleAwareInterface +{ + /** + * Translates the given message. + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param array $parameters An array of parameters for the message + * @param string|null $domain The domain for the message or null to use the default + * @param string|null $locale The locale or null to use the default + * + * @return string The translated string + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null); + /** + * Translates the given choice message by choosing a translation according to a number. + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param int $number The number to use to find the index of the message + * @param array $parameters An array of parameters for the message + * @param string|null $domain The domain for the message or null to use the default + * @param string|null $locale The locale or null to use the default + * + * @return string The translated string + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null); + /** + * Sets the current locale. + * + * @param string $locale The locale + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale(string $locale); + /** + * Returns the current locale. + * + * @return string The locale + */ + public function getLocale(); +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/TranslatorTrait.php b/classes/Utilities/Misc/Symfony/Component/Translation/TranslatorTrait.php new file mode 100755 index 00000000..79c0e9f0 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/TranslatorTrait.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Contracts\Translation; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +/** + * A trait to help implement TranslatorInterface and LocaleAwareInterface. + * + * @author Fabien Potencier + */ +trait TranslatorTrait +{ + private $locale; + /** + * {@inheritdoc} + */ + public function setLocale(string $locale) + { + $this->locale = $locale; + } + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale ?: \Locale::getDefault(); + } + /** + * {@inheritdoc} + */ + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null) : string + { + if (null === $id || '' === $id) { + return ''; + } + if (!isset($parameters['%count%']) || !\is_numeric($parameters['%count%'])) { + return \strtr($id, $parameters); + } + $number = (float) $parameters['%count%']; + $locale = $locale ?: $this->getLocale(); + $parts = []; + if (\preg_match('/^\\|++$/', $id)) { + $parts = \explode('|', $id); + } elseif (\preg_match_all('/(?:\\|\\||[^\\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + $standardRules = []; + foreach ($parts as $part) { + $part = \trim(\str_replace('||', '|', $part)); + // try to match an explicit rule, then fallback to the standard ones + if (\preg_match($intervalRegexp, $part, $matches)) { + if ($matches[2]) { + foreach (\explode(',', $matches[3]) as $n) { + if ($number == $n) { + return \strtr($matches['message'], $parameters); + } + } + } else { + $leftNumber = '-Inf' === $matches['left'] ? -\INF : (float) $matches['left']; + $rightNumber = \is_numeric($matches['right']) ? (float) $matches['right'] : \INF; + if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber)) { + return \strtr($matches['message'], $parameters); + } + } + } elseif (\preg_match('/^\\w+\\:\\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + $position = $this->getPluralizationRule($number, $locale); + if (!isset($standardRules[$position])) { + // when there's exactly one rule given, and that rule is a standard + // rule, use this rule + if (1 === \count($parts) && isset($standardRules[0])) { + return \strtr($standardRules[0], $parameters); + } + $message = \sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number); + if (\class_exists(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException::class)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException($message); + } + throw new \InvalidArgumentException($message); + } + return \strtr($standardRules[$position], $parameters); + } + /** + * Returns the plural position to use for the given locale and number. + * + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + private function getPluralizationRule(int $number, string $locale) : int + { + switch ('pt_BR' !== $locale && \strlen($locale) > 3 ? \substr($locale, 0, \strrpos($locale, '_')) : $locale) { + case 'af': + case 'bn': + case 'bg': + case 'ca': + case 'da': + case 'de': + case 'el': + case 'en': + case 'eo': + case 'es': + case 'et': + case 'eu': + case 'fa': + case 'fi': + case 'fo': + case 'fur': + case 'fy': + case 'gl': + case 'gu': + case 'ha': + case 'he': + case 'hu': + case 'is': + case 'it': + case 'ku': + case 'lb': + case 'ml': + case 'mn': + case 'mr': + case 'nah': + case 'nb': + case 'ne': + case 'nl': + case 'nn': + case 'no': + case 'oc': + case 'om': + case 'or': + case 'pa': + case 'pap': + case 'ps': + case 'pt': + case 'so': + case 'sq': + case 'sv': + case 'sw': + case 'ta': + case 'te': + case 'tk': + case 'ur': + case 'zu': + return 1 == $number ? 0 : 1; + case 'am': + case 'bh': + case 'fil': + case 'fr': + case 'gun': + case 'hi': + case 'hy': + case 'ln': + case 'mg': + case 'nso': + case 'pt_BR': + case 'ti': + case 'wa': + return 0 == $number || 1 == $number ? 0 : 1; + case 'be': + case 'bs': + case 'hr': + case 'ru': + case 'sh': + case 'sr': + case 'uk': + return 1 == $number % 10 && 11 != $number % 100 ? 0 : ($number % 10 >= 2 && $number % 10 <= 4 && ($number % 100 < 10 || $number % 100 >= 20) ? 1 : 2); + case 'cs': + case 'sk': + return 1 == $number ? 0 : ($number >= 2 && $number <= 4 ? 1 : 2); + case 'ga': + return 1 == $number ? 0 : (2 == $number ? 1 : 2); + case 'lt': + return 1 == $number % 10 && 11 != $number % 100 ? 0 : ($number % 10 >= 2 && ($number % 100 < 10 || $number % 100 >= 20) ? 1 : 2); + case 'sl': + return 1 == $number % 100 ? 0 : (2 == $number % 100 ? 1 : (3 == $number % 100 || 4 == $number % 100 ? 2 : 3)); + case 'mk': + return 1 == $number % 10 ? 0 : 1; + case 'mt': + return 1 == $number ? 0 : (0 == $number || $number % 100 > 1 && $number % 100 < 11 ? 1 : ($number % 100 > 10 && $number % 100 < 20 ? 2 : 3)); + case 'lv': + return 0 == $number ? 0 : (1 == $number % 10 && 11 != $number % 100 ? 1 : 2); + case 'pl': + return 1 == $number ? 0 : ($number % 10 >= 2 && $number % 10 <= 4 && ($number % 100 < 12 || $number % 100 > 14) ? 1 : 2); + case 'cy': + return 1 == $number ? 0 : (2 == $number ? 1 : (8 == $number || 11 == $number ? 2 : 3)); + case 'ro': + return 1 == $number ? 0 : (0 == $number || $number % 100 > 0 && $number % 100 < 20 ? 1 : 2); + case 'ar': + return 0 == $number ? 0 : (1 == $number ? 1 : (2 == $number ? 2 : ($number % 100 >= 3 && $number % 100 <= 10 ? 3 : ($number % 100 >= 11 && $number % 100 <= 99 ? 4 : 5)))); + default: + return 0; + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Util/ArrayConverter.php b/classes/Utilities/Misc/Symfony/Component/Translation/Util/ArrayConverter.php new file mode 100755 index 00000000..1a8638ec --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Util/ArrayConverter.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Util; + +/** + * ArrayConverter generates tree like structure from a message catalogue. + * e.g. this + * 'foo.bar1' => 'test1', + * 'foo.bar2' => 'test2' + * converts to follows: + * foo: + * bar1: test1 + * bar2: test2. + * + * @author Gennady Telegin + */ +class ArrayConverter +{ + /** + * Converts linear messages array to tree-like array. + * For example this array('foo.bar' => 'value') will be converted to ['foo' => ['bar' => 'value']]. + * + * @param array $messages Linear messages array + * + * @return array Tree-like messages array + */ + public static function expandToTree(array $messages) + { + $tree = []; + foreach ($messages as $id => $value) { + $referenceToElement =& self::getElementByPath($tree, \explode('.', $id)); + $referenceToElement = $value; + unset($referenceToElement); + } + return $tree; + } + private static function &getElementByPath(array &$tree, array $parts) + { + $elem =& $tree; + $parentOfElem = null; + foreach ($parts as $i => $part) { + if (isset($elem[$part]) && \is_string($elem[$part])) { + /* Process next case: + * 'foo': 'test1', + * 'foo.bar': 'test2' + * + * $tree['foo'] was string before we found array {bar: test2}. + * Treat new element as string too, e.g. add $tree['foo.bar'] = 'test2'; + */ + $elem =& $elem[\implode('.', \array_slice($parts, $i))]; + break; + } + $parentOfElem =& $elem; + $elem =& $elem[$part]; + } + if ($elem && \is_array($elem) && $parentOfElem) { + /* Process next case: + * 'foo.bar': 'test1' + * 'foo': 'test2' + * + * $tree['foo'] was array = {bar: 'test1'} before we found string constant `foo`. + * Cancel treating $tree['foo'] as array and cancel back it expansion, + * e.g. make it $tree['foo.bar'] = 'test1' again. + */ + self::cancelExpand($parentOfElem, $part, $elem); + } + return $elem; + } + private static function cancelExpand(array &$tree, $prefix, array $node) + { + $prefix .= '.'; + foreach ($node as $id => $value) { + if (\is_string($value)) { + $tree[$prefix . $id] = $value; + } else { + self::cancelExpand($tree, $prefix . $id, $value); + } + } + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Util/XliffUtils.php b/classes/Utilities/Misc/Symfony/Component/Translation/Util/XliffUtils.php new file mode 100755 index 00000000..ac3ae4d6 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Util/XliffUtils.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Util; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidResourceException; +/** + * Provides some utility methods for XLIFF translation files, such as validating + * their contents according to the XSD schema. + * + * @author Fabien Potencier + */ +class XliffUtils +{ + /** + * Gets xliff file version based on the root "version" attribute. + * + * Defaults to 1.2 for backwards compatibility. + * + * @throws InvalidArgumentException + */ + public static function getVersionNumber(\DOMDocument $dom) : string + { + /** @var \DOMNode $xliff */ + foreach ($dom->getElementsByTagName('xliff') as $xliff) { + $version = $xliff->attributes->getNamedItem('version'); + if ($version) { + return $version->nodeValue; + } + $namespace = $xliff->attributes->getNamedItem('xmlns'); + if ($namespace) { + if (0 !== \substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34)) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('Not a valid XLIFF namespace "%s"', $namespace)); + } + return \substr($namespace, 34); + } + } + // Falls back to v1.2 + return '1.2'; + } + /** + * Validates and parses the given file into a DOMDocument. + * + * @throws InvalidResourceException + */ + public static function validateSchema(\DOMDocument $dom) : array + { + $xliffVersion = static::getVersionNumber($dom); + $internalErrors = \libxml_use_internal_errors(\true); + $disableEntities = \libxml_disable_entity_loader(\false); + $isValid = @$dom->schemaValidateSource(self::getSchema($xliffVersion)); + if (!$isValid) { + \libxml_disable_entity_loader($disableEntities); + return self::getXmlErrors($internalErrors); + } + \libxml_disable_entity_loader($disableEntities); + $dom->normalizeDocument(); + \libxml_clear_errors(); + \libxml_use_internal_errors($internalErrors); + return []; + } + public static function getErrorsAsString(array $xmlErrors) : string + { + $errorsAsString = ''; + foreach ($xmlErrors as $error) { + $errorsAsString .= \sprintf("[%s %s] %s (in %s - line %d, column %d)\n", \LIBXML_ERR_WARNING === $error['level'] ? 'WARNING' : 'ERROR', $error['code'], $error['message'], $error['file'], $error['line'], $error['column']); + } + return $errorsAsString; + } + private static function getSchema(string $xliffVersion) : string + { + if ('1.2' === $xliffVersion) { + $schemaSource = \file_get_contents(__DIR__ . '/../Resources/schemas/xliff-core-1.2-strict.xsd'); + $xmlUri = 'http://www.w3.org/2001/xml.xsd'; + } elseif ('2.0' === $xliffVersion) { + $schemaSource = \file_get_contents(__DIR__ . '/../Resources/schemas/xliff-core-2.0.xsd'); + $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'; + } else { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion)); + } + return self::fixXmlLocation($schemaSource, $xmlUri); + } + /** + * Internally changes the URI of a dependent xsd to be loaded locally. + */ + private static function fixXmlLocation(string $schemaSource, string $xmlUri) : string + { + $newPath = \str_replace('\\', '/', __DIR__) . '/../Resources/schemas/xml.xsd'; + $parts = \explode('/', $newPath); + $locationstart = 'file:///'; + if (0 === \stripos($newPath, 'phar://')) { + $tmpfile = \tempnam(\sys_get_temp_dir(), 'symfony'); + if ($tmpfile) { + \copy($newPath, $tmpfile); + $parts = \explode('/', \str_replace('\\', '/', $tmpfile)); + } else { + \array_shift($parts); + $locationstart = 'phar:///'; + } + } + $drive = '\\' === \DIRECTORY_SEPARATOR ? \array_shift($parts) . '/' : ''; + $newPath = $locationstart . $drive . \implode('/', \array_map('rawurlencode', $parts)); + return \str_replace($xmlUri, $newPath, $schemaSource); + } + /** + * Returns the XML errors of the internal XML parser. + */ + private static function getXmlErrors(bool $internalErrors) : array + { + $errors = []; + foreach (\libxml_get_errors() as $error) { + $errors[] = ['level' => \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', 'code' => $error->code, 'message' => \trim($error->message), 'file' => $error->file ?: 'n/a', 'line' => $error->line, 'column' => $error->column]; + } + \libxml_clear_errors(); + \libxml_use_internal_errors($internalErrors); + return $errors; + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Writer/TranslationWriter.php b/classes/Utilities/Misc/Symfony/Component/Translation/Writer/TranslationWriter.php new file mode 100755 index 00000000..8fbccafe --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Writer/TranslationWriter.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Writer; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\DumperInterface; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\RuntimeException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * TranslationWriter writes translation messages. + * + * @author Michel Salib + */ +class TranslationWriter implements \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Writer\TranslationWriterInterface +{ + private $dumpers = []; + /** + * Adds a dumper to the writer. + * + * @param string $format The format of the dumper + */ + public function addDumper($format, \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Dumper\DumperInterface $dumper) + { + $this->dumpers[$format] = $dumper; + } + /** + * Disables dumper backup. + * + * @deprecated since Symfony 4.1 + */ + public function disableBackup() + { + @\trigger_error(\sprintf('The "%s()" method is deprecated since Symfony 4.1.', __METHOD__), \E_USER_DEPRECATED); + foreach ($this->dumpers as $dumper) { + if (\method_exists($dumper, 'setBackup')) { + $dumper->setBackup(\false); + } + } + } + /** + * Obtains the list of supported formats. + * + * @return array + */ + public function getFormats() + { + return \array_keys($this->dumpers); + } + /** + * Writes translation from the catalogue according to the selected format. + * + * @param string $format The format to use to dump the messages + * @param array $options Options that are passed to the dumper + * + * @throws InvalidArgumentException + */ + public function write(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalogue, $format, $options = []) + { + if (!isset($this->dumpers[$format])) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException(\sprintf('There is no dumper associated with format "%s".', $format)); + } + // get the right dumper + $dumper = $this->dumpers[$format]; + if (isset($options['path']) && !\is_dir($options['path']) && !@\mkdir($options['path'], 0777, \true) && !\is_dir($options['path'])) { + throw new \ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\RuntimeException(\sprintf('Translation Writer was not able to create directory "%s"', $options['path'])); + } + // save + $dumper->dump($catalogue, $options); + } +} diff --git a/classes/Utilities/Misc/Symfony/Component/Translation/Writer/TranslationWriterInterface.php b/classes/Utilities/Misc/Symfony/Component/Translation/Writer/TranslationWriterInterface.php new file mode 100755 index 00000000..ed31b032 --- /dev/null +++ b/classes/Utilities/Misc/Symfony/Component/Translation/Writer/TranslationWriterInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Writer; + +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\Exception\InvalidArgumentException; +use ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue; +/** + * TranslationWriter writes translation messages. + * + * @author Michel Salib + */ +interface TranslationWriterInterface +{ + /** + * Writes translation from the catalogue according to the selected format. + * + * @param string $format The format to use to dump the messages + * @param array $options Options that are passed to the dumper + * + * @throws InvalidArgumentException + */ + public function write(\ILAB\MediaCloud\Utilities\Misc\Symfony\Component\Translation\MessageCatalogue $catalogue, $format, $options = []); +} diff --git a/composer.json b/composer.json index 23f8e1a5..3c105adf 100755 --- a/composer.json +++ b/composer.json @@ -27,12 +27,11 @@ "duncan3dc/blade": ">3.3", "superbalist/flysystem-google-storage": "^7.1", "google/cloud-vision": "^0.19.0", - "nesbot/carbon": "^1.21", "ilab/b2-sdk-php": "^1.4", "psr/http-message-implementation": "*", "mikey179/vfsstream": "*", - "ivopetkov/html5-dom-document-php": "^2.0", "ralouphie/mimey": "^2.1", + "ivopetkov/html5-dom-document-php": "^2.0", "zumba/amplitude-php": "^1.0" }, "autoload": { diff --git a/ilab-media-tools.php b/ilab-media-tools.php index f18ac888..09d83a54 100755 --- a/ilab-media-tools.php +++ b/ilab-media-tools.php @@ -5,7 +5,7 @@ Plugin URI: https://github.com/interfacelab/ilab-media-tools Description: Automatically upload media to Amazon S3 and integrate with Imgix, a real-time image processing CDN. Boosts site performance and simplifies workflows. Author: interfacelab -Version: 3.3.7 +Version: 3.3.8 Author URI: http://interfacelab.io */ // Copyright (c) 2016 Interfacelab LLC. All rights reserved. @@ -93,7 +93,7 @@ } // Version Defines -define( 'MEDIA_CLOUD_VERSION', '3.3.6' ); +define( 'MEDIA_CLOUD_VERSION', '3.3.8' ); define( 'MEDIA_CLOUD_INFO_VERSION', '1.0.0' ); // Directory defines define( 'ILAB_TOOLS_DIR', dirname( __FILE__ ) ); diff --git a/public/css/ilab-media-cloud.css b/public/css/ilab-media-cloud.css index dd319650..2e938308 100755 --- a/public/css/ilab-media-cloud.css +++ b/public/css/ilab-media-cloud.css @@ -8,4 +8,4 @@ * Date: 2018-04-01T06:26:32.417Z */.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline-color:rgba(51,153,255,.75);outline:1px solid #39f;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}.ilab-admin-separator-container{display:flex;height:12px;align-items:center;margin:0 -10px 0 0}.ilab-admin-separator-container .ilab-admin-separator-title{font-size:.68em;text-transform:uppercase;font-weight:700;margin-right:10px;color:hsla(0,0%,100%,.25)}.ilab-admin-separator-container .ilab-admin-separator{display:block;flex:1;padding:0;height:1px;line-height:1px;background:hsla(0,0%,100%,.125)}#wpadminbar #wp-admin-bar-media-cloud-admin-bar>.ab-item>.ab-icon:before{content:"\F176";top:3px}.ilabm-backdrop{position:fixed;display:block;background-color:rgba(0,0,0,.66)}.ilabm-backdrop,.ilabm-container{left:0;top:0;right:0;bottom:0;z-index:160000!important}.ilabm-container{background-color:#fcfcfc;position:absolute;border-radius:0;display:flex;flex-direction:column}.ilabm-titlebar{border-bottom:1px solid #ddd;min-height:50px;max-height:50px;box-shadow:0 0 4px rgba(0,0,0,.15);display:flex;align-items:center}.ilabm-titlebar h1{flex:1;padding:0 16px;font-size:22px;line-height:50px;margin:0 50px 0 0;display:flex;align-items:center;justify-content:space-between}.ilabm-titlebar .modal-actions{display:flex}.ilabm-titlebar .modal-actions a{margin-left:8px;display:flex;align-items:center}.ilabm-titlebar .modal-actions a svg{height:12px;width:auto;margin-right:4px}.ilabm-titlebar .modal-actions a svg>path,.ilabm-titlebar .modal-actions a svg>rect{fill:#000}.ilabm-titlebar .modal-actions div.spacer{width:8px;min-width:8px}.ilabm-titlebar>a{display:block;max-width:50px;min-width:50px;border-left:1px solid #ddd}.ilabm-window-area{flex:2 100%;display:flex;flex-direction:row}.ilabm-window-area-content{background-color:#fff;flex:2 100%;display:flex;flex-direction:column}.ilabm-editor-container{flex:2 100%;position:relative}.ilabm-editor-area{position:absolute;left:0;top:0;right:0;bottom:0;background-image:url(../img/ilab-imgix-edit-bg.png);display:block;margin:10px}.ilabm-sidebar{min-width:380px;max-width:380px;background-color:#f3f3f3;display:flex;flex-direction:column;border-left:3px solid #ddd}.ilabm-sidebar-content{position:relative;display:flex;flex:2 100%}.ilabm-sidebar-tabs{background:#ddd;display:flex;min-height:36px;max-height:36px}.ilabm-sidebar-tabs .ilabm-sidebar-tab{min-width:40px;white-space:nowrap;text-align:center;margin-top:3px;background-color:#ccc;line-height:30px;padding:0 15px;margin-right:3px;font-size:11px;text-transform:uppercase;color:#888;font-weight:700;cursor:pointer!important}.ilabm-sidebar-tabs .active-tab{background-color:#f3f3f3;color:#777}.ilabm-sidebar-actions{display:flex;justify-content:flex-end;background-color:#fff;border-top:1px solid #eee;padding:11px}.ilabm-sidebar-actions a{display:block;margin-left:10px!important}a.button-reset{background:#a00!important;border-color:#700!important;color:#fff!important;box-shadow:inset 0 1px 0 #d00,0 1px 0 rgba(0,0,0,.15)!important;text-shadow:none!important}.ilabm-editor-tabs{background:#ddd;overflow:hidden;min-height:36px}.ilabm-editor-tabs,.ilabm-editor-tabs .ilabm-tabs-select-ui{display:flex;flex-direction:row}.ilabm-editor-tabs .ilabm-tabs-select-ui .ilabm-tabs-select-label{margin-top:3px;line-height:32px;padding:0 5px 0 15px;margin-right:3px;font-size:11px;text-transform:uppercase;color:#888;font-weight:700;cursor:pointer!important}.ilabm-editor-tabs .ilabm-tabs-select-ui .ilabm-tabs-select{margin-top:4px;line-height:32px;font-size:11px}.ilabm-editor-tabs .ilabm-tabs-ui{display:flex;flex-direction:row}.ilabm-editor-tabs .ilabm-tabs-ui .ilabm-editor-tab{white-space:nowrap;min-width:50px;text-align:center;min-height:32px;max-height:33px;margin-top:3px;background-color:#ccc;line-height:31px;padding:0 15px;margin-right:3px;font-size:11px;text-transform:uppercase;color:#888;font-weight:700;cursor:pointer!important}.ilabm-editor-tabs .ilabm-tabs-ui .active-tab{background:#fff;margin-top:2px;border-right:1px solid #ddd;border-left:1px solid #ddd;border-top:1px solid #ddd}.ilabm-status-container{display:flex;flex:1;justify-content:flex-start}.ilabm-status-container .is-hidden{display:none}.ilabm-status-container .spinner{margin:0 8px 0 0}.ilabm-status-label{font-size:13px}.ilabm-preview-wait-modal{position:absolute;box-shadow:0 0 10px 1px rgba(0,0,0,.75);text-align:center;padding:20px 40px;border-radius:10px;background-color:hsla(0,0%,100%,.66);left:50%;top:50%;margin-left:-60px;margin-top:-32px}.ilabm-preview-wait-modal h3{text-transform:uppercase;font-size:13px}.ilabm-preview-wait-modal span.spinner{float:none!important}.ilabm-bottom-bar{font-size:12px!important;padding:0 10px 10px;display:flex!important;justify-content:flex-end;align-items:center;min-height:20px}.ilabm-bottom-bar .ilabm-bottom-bar-seperator{position:relative;width:1px;height:20px;background-color:#ccc;margin:0 10px 0 20px!important}.ilabm-bottom-bar a,.ilabm-bottom-bar select{margin-left:10px!important}.ilabm-bottom-bar select{font-size:13px!important;min-width:140px}.ilabm-bottom-bar label{font-size:13px!important}.is-hidden{display:none}.ilabm-modal-close{top:0;right:0;cursor:pointer;color:#777;background-color:transparent;height:50px;width:50px;position:absolute;text-align:center;border:0;border-left:1px solid #ddd;transition:color .1s ease-in-out,background .1s ease-in-out;text-decoration:none;z-index:1000;box-sizing:content-box;transition-property:border,background,color;transition-duration:.05s;transition-timing-function:ease-in-out}.ilabm-modal-icon{background-repeat:no-repeat;display:block;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.ilabm-modal-icon:before{content:"\F335";font:normal 22px/1 dashicons;vertical-align:middle;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#666}.setup-body{display:flex;flex-direction:column;align-items:center;justify-content:center;padding-top:40px}.setup-body .service-selection-grid{display:grid;grid-template-columns:repeat(3,1fr);grid-gap:15px;grid-auto-rows:158px}.setup-body .service-selection-grid a{border-radius:5px;background-color:#fff;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;border:1px solid #eaeaea;width:128px;height:128px;text-align:center;text-decoration:none;padding:15px;background-position:50% calc(50% - 15px);background-repeat:no-repeat;transition:transform .5s ease-out}.setup-body .service-selection-grid a:hover{transform:scale(1.1)}.setup-body .service-selection-grid a[data-service=s3]{grid-column:1;grid-row:1;background-image:url(../img/icon-service-s3.svg)}.setup-body .service-selection-grid a[data-service=google]{grid-column:2;grid-row:1;background-image:url(../img/icon-service-google.svg)}.setup-body .service-selection-grid a[data-service=minio]{grid-column:3;grid-row:1;background-image:url(../img/icon-service-minio.svg)}.setup-body .service-selection-grid a[data-service=backblaze]{grid-column:1;grid-row:2;background-image:url(../img/icon-service-backblaze.svg)}.setup-body .service-selection-grid a[data-service=do]{grid-column:2;grid-row:2;background-image:url(../img/icon-service-do.svg)}.setup-body .service-selection-grid a[data-service=other-s3]{grid-column:3;grid-row:2;background-image:url(../img/icon-service-other-s3.svg)}#ilab-video-upload-target{position:relative;padding:30px;border:4px dashed #e0e0e0;background-color:#fafafa;margin:20px 0;display:flex;flex-wrap:wrap;min-height:128px;cursor:pointer;transition:border .5s ease-out}#ilab-video-upload-target.drag-inside{border:4px solid #70a9dd;background-color:#bcd3e2}.ilab-upload-item{position:relative;min-width:128px;min-height:128px;max-width:128px;max-height:128px;width:128px;height:128px;background-color:#eaeaea;margin:10px;border-radius:0;border:1px solid #ddd;overflow:hidden;transition:opacity .5s ease-out,left .3s ease-out,top .3s ease-out,width .3s ease-out,height .3s ease-out,transform .3s ease-out;background-repeat:no-repeat;background-position:50%}.ilab-upload-item.upload-error{background-color:#eabab3;border:1px solid #bb6a6b}.ilab-upload-item.ilab-upload-selected{box-shadow:0 0 0 2px #fff,0 0 0 5px #0073aa}.ilab-upload-cell-image{background-image:url(../img/ilab-icon-image.svg);background-size:60px}.ilab-upload-cell-video{background-image:url(../img/ilab-icon-video.svg);background-size:60px}.ilab-upload-cell-doc{background-image:url(../img/ilab-icon-document.svg);background-size:45px}.no-mouse{cursor:default!important}.ilab-upload-item-background{position:absolute;left:-5px;top:-5px;right:-5px;bottom:-5px;background-repeat:no-repeat;background-size:cover;background-position:50%;transition:opacity .5s}.ilab-upload-status-container{position:absolute;left:0;top:0;right:0;bottom:0;display:flex;flex-direction:column;justify-content:center;align-items:center}.ilab-upload-status{color:#fff;font-weight:700;font-size:1em;text-shadow:0 0 3px #000}.ilab-upload-progress{width:80%;max-width:80%;height:9px;overflow:hidden;position:relative;margin-top:10px;border-radius:9px;background-color:hsla(0,0%,100%,.66)}.ilab-upload-progress-track{background-color:#0085ba;position:absolute;left:0;top:0;bottom:0;transition:width .125s ease-out}.ilab-upload-directions{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-size:2em;opacity:.5}.ilab-loader-container{display:flex;justify-content:center;align-items:center;position:absolute;left:0;top:0;right:0;bottom:0;transition:opacity .5s}.ilab-loader,.ilab-loader:after{border-radius:50%;width:24px;height:24px}.ilab-loader{font-size:5px;text-indent:-9999em;border:1.1em solid hsla(0,0%,100%,.2);border-left-color:#fff;-webkit-animation:load8 1.1s linear infinite;animation:load8 1.1s linear infinite}.ilab-loader.ilab-loader-dark{border:1.1em solid rgba(0,0,0,.2);border-left-color:#000}.ilab-upload-footer{padding-right:10px;position:absolute;left:0;right:0;border-top:1px solid #ddd;bottom:0;height:52px;background-color:#fff;display:flex;justify-content:flex-end;align-items:center;background-color:#fcfcfc;visibility:hidden}#ilab-attachment-info{position:absolute;right:-300px;top:0;bottom:54px;width:267px;transition:right .33s ease-out}.ilab-upload-insert-mode{position:relative;background-color:#fff}.ilab-upload-insert-mode div.wrap{position:absolute;left:0;top:0;right:0;bottom:52px;margin:0;padding:0 20px;overflow:auto;transition:right .33s ease-out}.ilab-upload-insert-mode div.wrap h2:first-of-type{display:none}.ilab-upload-insert-mode .ilab-upload-footer{visibility:visible}.ilab-item-selected div.wrap{right:300px}.ilab-item-selected #ilab-attachment-info{right:0}.media-cloud-upload-logo{width:240px;height:auto;margin-bottom:40px;opacity:.66;margin-top:-40px}.has-upload-message .upload-ui .media-cloud-upload-logo{display:none}.attachments-browser .upload-ui .media-cloud-upload-logo{margin-top:0}.minicolors{position:relative}.minicolors-sprite{background-image:url(../img/jquery.minicolors.png)}.minicolors-swatch{position:absolute;vertical-align:middle;background-position:-80px 0;border:1px solid #ccc;cursor:text;padding:0;margin:0;display:inline-block}.minicolors-swatch-color{position:absolute;top:0;left:0;right:0;bottom:0}.minicolors input[type=hidden]+.minicolors-swatch{width:28px;position:static;cursor:pointer}.minicolors input[type=hidden][disabled]+.minicolors-swatch{cursor:default}.minicolors-panel{position:absolute;width:173px;background:#fff;border:1px solid #ccc;box-shadow:0 0 20px rgba(0,0,0,.2);z-index:99999;box-sizing:content-box;display:none}.minicolors-panel.minicolors-visible{display:block}.minicolors-position-top .minicolors-panel{top:-154px}.minicolors-position-right .minicolors-panel{right:0}.minicolors-position-bottom .minicolors-panel{top:auto}.minicolors-position-left .minicolors-panel{left:0}.minicolors-with-opacity .minicolors-panel{width:194px}.minicolors .minicolors-grid{position:relative;top:1px;left:1px;width:150px;height:150px;margin-bottom:2px;background-position:-120px 0;cursor:crosshair}[dir=rtl] .minicolors .minicolors-grid{right:1px}.minicolors .minicolors-grid-inner{position:absolute;top:0;left:0;width:150px;height:150px}.minicolors-slider-saturation .minicolors-grid{background-position:-420px 0}.minicolors-slider-saturation .minicolors-grid-inner{background-position:-270px 0;background-image:inherit}.minicolors-slider-brightness .minicolors-grid{background-position:-570px 0}.minicolors-slider-brightness .minicolors-grid-inner{background-color:#000}.minicolors-slider-wheel .minicolors-grid{background-position:-720px 0}.minicolors-opacity-slider,.minicolors-slider{position:absolute;top:1px;left:152px;width:20px;height:150px;background-color:#fff;background-position:0 0;cursor:row-resize}[dir=rtl] .minicolors-opacity-slider,[dir=rtl] .minicolors-slider{right:152px}.minicolors-slider-saturation .minicolors-slider{background-position:-60px 0}.minicolors-slider-brightness .minicolors-slider,.minicolors-slider-wheel .minicolors-slider{background-position:-20px 0}.minicolors-opacity-slider{left:173px;background-position:-40px 0;display:none}[dir=rtl] .minicolors-opacity-slider{right:173px}.minicolors-with-opacity .minicolors-opacity-slider{display:block}.minicolors-grid .minicolors-picker{position:absolute;top:70px;left:70px;width:12px;height:12px;border:1px solid #000;border-radius:10px;margin-top:-6px;margin-left:-6px;background:none}.minicolors-grid .minicolors-picker>div{position:absolute;top:0;left:0;width:8px;height:8px;border-radius:8px;border:2px solid #fff;box-sizing:content-box}.minicolors-picker{position:absolute;top:0;left:0;width:18px;height:2px;background:#fff;border:1px solid #000;margin-top:-2px;box-sizing:content-box}.minicolors-swatches,.minicolors-swatches li{margin:5px 0 3px 5px;padding:0;list-style:none;overflow:hidden}[dir=rtl] .minicolors-swatches,[dir=rtl] .minicolors-swatches li{margin:5px 5px 3px 0}.minicolors-swatches .minicolors-swatch{position:relative;float:left;cursor:pointer;margin:0 4px 0 0}[dir=rtl] .minicolors-swatches .minicolors-swatch{float:right;margin:0 0 0 4px}.minicolors-with-opacity .minicolors-swatches .minicolors-swatch{margin-right:7px}[dir=rtl] .minicolors-with-opacity .minicolors-swatches .minicolors-swatch{margin-right:0;margin-left:7px}.minicolors-swatch.selected{border-color:#000}.minicolors-inline{display:inline-block}.minicolors-inline .minicolors-input{display:none!important}.minicolors-inline .minicolors-panel{position:relative;top:auto;left:auto;box-shadow:none;z-index:auto;display:inline-block}[dir=rtl] .minicolors-inline .minicolors-panel{right:auto}.minicolors-theme-default .minicolors-swatch{top:5px;left:5px;width:18px;height:18px}[dir=rtl] .minicolors-theme-default .minicolors-swatch{right:5px}.minicolors-theme-default .minicolors-swatches .minicolors-swatch{margin-bottom:2px;top:0;left:0;width:18px;height:18px}[dir=rtl] .minicolors-theme-default .minicolors-swatches .minicolors-swatch{right:0}.minicolors-theme-default.minicolors-position-right .minicolors-swatch{left:auto;right:5px}[dir=rtl] .minicolors-theme-default.minicolors-position-left .minicolors-swatch{right:auto;left:5px}.minicolors-theme-default.minicolors{display:inline-block}.minicolors-theme-default .minicolors-input{height:20px;width:auto;display:inline-block;padding-left:26px}[dir=rtl] .minicolors-theme-default .minicolors-input{text-align:right;unicode-bidi:-moz-plaintext;unicode-bidi:plaintext;padding-left:1px;padding-right:26px}.minicolors-theme-default.minicolors-position-right .minicolors-input{padding-right:26px;padding-left:inherit}[dir=rtl] .minicolors-theme-default.minicolors-position-left .minicolors-input{padding-right:inherit;padding-left:26px}.minicolors-theme-bootstrap .minicolors-swatch{z-index:2;top:3px;left:3px;width:28px;height:28px;border-radius:3px}[dir=rtl] .minicolors-theme-bootstrap .minicolors-swatch{right:3px}.minicolors-theme-bootstrap .minicolors-swatches .minicolors-swatch{margin-bottom:2px;top:0;left:0;width:20px;height:20px}[dir=rtl] .minicolors-theme-bootstrap .minicolors-swatches .minicolors-swatch{right:0}.minicolors-theme-bootstrap .minicolors-swatch-color{border-radius:inherit}.minicolors-theme-bootstrap.minicolors-position-right>.minicolors-swatch{left:auto;right:3px}[dir=rtl] .minicolors-theme-bootstrap.minicolors-position-left>.minicolors-swatch{right:auto;left:3px}.minicolors-theme-bootstrap .minicolors-input{float:none;padding-left:44px}[dir=rtl] .minicolors-theme-bootstrap .minicolors-input{text-align:right;unicode-bidi:-moz-plaintext;unicode-bidi:plaintext;padding-left:12px;padding-right:44px}.minicolors-theme-bootstrap.minicolors-position-right .minicolors-input{padding-right:44px;padding-left:12px}[dir=rtl] .minicolors-theme-bootstrap.minicolors-position-left .minicolors-input{padding-right:12px;padding-left:44px}.minicolors-theme-bootstrap .minicolors-input.input-lg+.minicolors-swatch{top:4px;left:4px;width:37px;height:37px;border-radius:5px}[dir=rtl] .minicolors-theme-bootstrap .minicolors-input.input-lg+.minicolors-swatch{right:4px}.minicolors-theme-bootstrap .minicolors-input.input-sm+.minicolors-swatch{width:24px;height:24px}.minicolors-theme-bootstrap .minicolors-input.input-xs+.minicolors-swatch{width:18px;height:18px}.input-group .minicolors-theme-bootstrap:not(:first-child) .minicolors-input{border-top-left-radius:0;border-bottom-left-radius:0}[dir=rtl] .input-group .minicolors-theme-bootstrap .minicolors-input{border-radius:4px}[dir=rtl] .input-group .minicolors-theme-bootstrap:not(:first-child) .minicolors-input{border-top-right-radius:0;border-bottom-right-radius:0}[dir=rtl] .input-group .minicolors-theme-bootstrap:not(:last-child) .minicolors-input{border-top-left-radius:0;border-bottom-left-radius:0}[dir=rtl] .input-group-addon,[dir=rtl] .input-group-btn>.btn,[dir=rtl] .input-group-btn>.btn-group>.btn,[dir=rtl] .input-group-btn>.dropdown-toggle,[dir=rtl] .input-group .form-control{border:1px solid #ccc;border-radius:4px}[dir=rtl] .input-group-addon:first-child,[dir=rtl] .input-group-btn:first-child>.btn,[dir=rtl] .input-group-btn:first-child>.btn-group>.btn,[dir=rtl] .input-group-btn:first-child>.dropdown-toggle,[dir=rtl] .input-group-btn:last-child>.btn-group:not(:last-child)>.btn,[dir=rtl] .input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),[dir=rtl] .input-group .form-control:first-child{border-top-left-radius:0;border-bottom-left-radius:0;border-left:0}[dir=rtl] .input-group-addon:last-child,[dir=rtl] .input-group-btn:first-child>.btn-group:not(:first-child)>.btn,[dir=rtl] .input-group-btn:first-child>.btn:not(:first-child),[dir=rtl] .input-group-btn:last-child>.btn,[dir=rtl] .input-group-btn:last-child>.btn-group>.btn,[dir=rtl] .input-group-btn:last-child>.dropdown-toggle,[dir=rtl] .input-group .form-control:last-child{border-top-right-radius:0;border-bottom-right-radius:0}.minicolors-theme-semanticui .minicolors-swatch{top:0;left:0;padding:18px}[dir=rtl] .minicolors-theme-semanticui .minicolors-swatch{right:0}.minicolors-theme-semanticui input{text-indent:30px}.imgix-preview-image{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);max-width:100%;max-height:100%;display:block;pointer-events:none}.imgix-parameters-container{position:absolute;left:0;top:0;bottom:0;right:0;overflow-x:hidden;overflow-y:auto;display:flex;flex-direction:column;padding:15px 10px}.imgix-parameters-container.is-hidden{display:none}.imgix-parameters-container .imgix-parameter-group select{font-size:12px}.imgix-parameters-container .imgix-parameter-group h4{margin:0;font-size:10px;text-transform:uppercase;color:#999;background-color:#ddd;padding:5px 15px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.imgix-parameters-container .imgix-parameter-group>div{padding:15px}.imgix-parameter{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding-bottom:15px;margin-bottom:15px}.imgix-parameter .imgix-param-imagick-warning{position:absolute;left:-10px;top:-5px;right:-10px;bottom:0;padding:0 5px;display:flex;align-items:center;justify-content:center;background:hsla(0,0%,100%,.8)}.imgix-parameter .imgix-param-imagick-warning>div{text-align:center}.imgix-parameter:last-of-type{margin-bottom:0;padding-bottom:0}.imgix-param-title{display:flex;align-items:baseline;margin-bottom:0}.imgix-param-title-colortype{align-items:center!important;margin-bottom:8px}.imgix-param-title-colortype h3{margin:0!important}.imgix-param-title-left{flex:1 50%}.imgix-param-title-right{flex:1 50%;padding-left:40px;text-align:right;position:relative}.imgix-param-title-right h3{text-align:right}.imgix-param-blend-mode h3,.imgix-param-title h3{margin-top:0;font-size:11px;text-transform:uppercase;color:#666}.imgix-media-param-title .imgix-param-title-left{flex:2 80%}.imgix-media-param-title .imgix-param-title-right{flex:1 20%}.imgix-media-param-title .imgix-param-title-right a{text-align:center!important}.minicolors-theme-default.minicolors{width:auto;display:block;padding:0!important;margin:0;min-height:29px}.ilab-color-input{position:relative;top:0;right:0;height:30px!important;margin:0!important;padding-left:8px!important;padding-right:30px!important}.imgix-param-blend-mode{margin-top:15px;display:flex;align-items:baseline}.imgix-param-blend-mode h3{flex:1 50%}.imgix-param-blend-mode select{flex:2 100%}.imgix-parameter input[type=range]{display:block;width:100%;-webkit-appearance:none;margin:0 0 10px;background:none;padding:0!important}.imgix-parameter input[type=range]:focus{outline:none}.imgix-parameter input[type=range]:focus::-webkit-slider-runnable-track{background:#fff}.imgix-parameter input[type=range]::-webkit-slider-runnable-track{width:100%;height:5px;cursor:pointer;animate:.2s;box-shadow:inset 1px 1px 2px 0 rgba(0,0,0,.25);background:#d4cfd4;border-radius:4px;border:0 solid #000101}.imgix-parameter input[type=range]::-webkit-slider-thumb{border:1px solid rgba(0,0,0,.25);box-shadow:inset 0 2px 2px 0 hsla(0,0%,100%,.5);height:17px;width:17px;border-radius:9px;background:#dcdcdc;cursor:pointer;-webkit-appearance:none;margin-top:-6px}.imgix-parameter input[type=range]::-moz-range-track{width:100%;height:5px;cursor:pointer;animate:.2s;box-shadow:inset 1px 1px 2px 0 rgba(0,0,0,.25);background:#d4cfd4;border-radius:4px;border:0 solid #000101}.imgix-parameter input[type=range]::-moz-range-thumb{border:1px solid rgba(0,0,0,.25);box-shadow:inset 0 2px 2px 0 hsla(0,0%,100%,.5);height:17px;width:17px;border-radius:9px;background:#dcdcdc;cursor:pointer;-webkit-appearance:none;margin-top:-6px}.imgix-parameter .imgix-param-reset{display:flex;width:100%;justify-content:flex-end}.imgix-parameter .imgix-param-reset a{font-size:11px;font-style:italic;text-decoration:none}.imgix-parameter .imgix-param-reset a,.imgix-parameter a:focus{outline:none!important;border:0!important}.imgix-media-preview{position:relative;margin:0!important;padding:0 0 100%!important;background-image:url(../img/ilab-imgix-edit-bg.png);width:100%}.imgix-media-preview img{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);max-width:100%;max-height:100%;display:block}.imgix-media-preview-inner{position:absolute;left:0;right:0;bottom:0;top:0;display:flex;align-items:center;justify-content:center}.imgix-alignment-container{display:flex;flex:row;flex-wrap:wrap;justify-content:space-around;align-items:baseline;padding:0 35px}.imgix-alignment-button{background-color:#ddd;display:block;width:60px;height:60px;margin:5px;text-decoration:none;border-radius:4px;border:1px solid #888;box-shadow:inset 0 1px 0 #fff,0 1px 0 rgba(0,0,0,.15)!important}.selected-alignment{background-color:#bbb;box-shadow:inset 1px 1px 1px rgba(0,0,0,.25),0 1px 0 rgba(0,0,0,.15)!important}.ilabm-pillbox{flex-wrap:wrap;border-bottom:0!important}.ilabm-pillbox,.ilabm-pillbox .ilabm-pill{align-items:center;display:flex;justify-content:center}.ilabm-pillbox .ilabm-pill{white-space:nowrap;line-height:1;height:14px;min-height:32px;width:140px;min-width:140px;max-width:140px;font-size:10px;background-color:#eaeaea;text-transform:uppercase;font-weight:700;color:#444;text-decoration:none;border-radius:8px;margin:3px}.ilabm-pillbox .ilabm-pill span{display:block;margin-right:8px}.ilabm-pillbox .ilabm-pill span.icon{width:18px;height:18px;min-width:18px;min-height:18px;max-width:18px;max-height:18px;background-repeat:no-repeat;background-position:50%;background-size:contain;margin-left:8px}.ilabm-pillbox .pill-selected{background-color:#ccc;box-shadow:inset 1px 1px 1px rgba(0,0,0,.25);color:#fff}.ilabm-pillbox-no-icon .ilabm-pill{width:100px;min-width:100px;max-width:100px}.ilabm-pillbox-no-icon .ilabm-pill span{margin-right:0}.ilabm-pillbox-no-icon .ilabm-pill span.icon{display:none}.imgix-pill-enhance>span.icon{background-image:url(../img/ilab-imgix-magic-wand-black.svg)}.imgix-pill-enhance.pill-selected>span.icon{background-image:url(../img/ilab-imgix-magic-wand-white.svg)}.imgix-pill-redeye>span.icon{background-image:url(../img/ilab-imgix-red-eye-black.svg)}.imgix-pill-redeye.pill-selected>span.icon{background-image:url(../img/ilab-imgix-red-eye-white.svg)}.imgix-pill-usefaces>span.icon{background-image:url(../img/ilab-imgix-faces-black.svg)}.imgix-pill-usefaces.pill-selected>span.icon{background-image:url(../img/ilab-imgix-faces-white.svg)}.imgix-pill-focalpoint>span.icon{background-image:url(../img/ilab-imgix-focalpoint-black.svg)}.imgix-pill-focalpoint.pill-selected>span.icon{background-image:url(../img/ilab-imgix-focalpoint-white.svg)}.imgix-pill-entropy>span.icon{background-image:url(../img/ilab-imgix-chaos-black.svg)}.imgix-pill-entropy.pill-selected>span.icon{background-image:url(../img/ilab-imgix-chaos-white.svg)}.imgix-pill-edges>span.icon{background-image:url(../img/ilab-imgix-edges-black.svg)}.imgix-pill-edges.pill-selected>span.icon{background-image:url(../img/ilab-imgix-edges-white.svg)}.imgix-pill-h>span.icon{background-image:url(../img/ilab-flip-horizontal-black.svg)}.imgix-pill-h.pill-selected>span.icon{background-image:url(../img/ilab-flip-horizontal-white.svg)}.imgix-pill-v>span.icon{background-image:url(../img/ilab-flip-vertical-black.svg)}.imgix-pill-v.pill-selected>span.icon{background-image:url(../img/ilab-flip-vertical-white.svg)}.imgix-pill-clip>span.icon{background-image:url(../img/ilab-imgix-clip-black.svg)}.imgix-pill-clip.pill-selected>span.icon{background-image:url(../img/ilab-imgix-clip-white.svg)}.imgix-pill-crop>span.icon{background-image:url(../img/ilab-imgix-crop-black.svg)}.imgix-pill-crop.pill-selected>span.icon{background-image:url(../img/ilab-imgix-crop-white.svg)}.imgix-pill-max>span.icon{background-image:url(../img/ilab-imgix-max-black.svg)}.imgix-pill-max.pill-selected>span.icon{background-image:url(../img/ilab-imgix-max-white.svg)}.imgix-pill-scale>span.icon{background-image:url(../img/ilab-imgix-scale-black.svg)}.imgix-pill-scale.pill-selected>span.icon{background-image:url(../img/ilab-imgix-scale-white.svg)}.imgix-preset-make-default-container{align-items:center;display:flex;min-height:30px;margin-left:10px}.imgix-preset-container{align-items:center;display:flex}.imgix-preset-container.is-hidden,.imgix-preset-make-default-container.is-hidden{display:none}.imgix-param-label{font-style:italic;text-transform:none!important}.imgix-label-editor{position:absolute;right:-4px;top:0;width:40px;font-size:11px;padding:1px;text-align:right}.ilabm-focal-point-icon{position:absolute;background-image:url(../img/ilab-imgix-focalpoint-icon.svg);width:24px;height:24px;background-size:contain;pointer-events:none}.ilab-face-outline{position:absolute;border:3px solid #fff;-webkit-filter:drop-shadow(0 2px 3px #000);filter:drop-shadow(0 2px 3px black);opacity:.33;z-index:999;cursor:pointer}.ilab-face-outline span{display:block;position:absolute;background-color:#fff;color:#000;font-size:9px;width:12px;height:12px;text-align:center;font-weight:700;line-height:1}.ilab-face-outline.active{opacity:1;z-index:1000}.ilab-all-faces-outline{position:absolute;border:3px solid #fff;-webkit-filter:drop-shadow(0 2px 3px #000);filter:drop-shadow(0 2px 3px black)}input[type=range].imgix-param{-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=range].imgix-param:focus{outline:none}input[type=range].imgix-param:focus::-webkit-slider-runnable-track{background:#bababa}input[type=range].imgix-param::-webkit-slider-runnable-track{width:100%;height:17px;cursor:pointer;animate:.2s;background:#cfcfcf;border-radius:17px;box-shadow:none}input[type=range].imgix-param::-moz-range-track{width:100%;height:17px;cursor:pointer;animate:.2s;background:#cfcfcf;border-radius:17px;box-shadow:none}input[type=range].imgix-param::-webkit-slider-thumb{-webkit-appearance:none;cursor:pointer;width:18px;height:17px;border-radius:17px;margin-top:0}input[type=range].imgix-param::-moz-range-thumb{-webkit-appearance:none;cursor:pointer;width:18px;height:17px;border-radius:17px;margin-top:0}.ilab-crop-preview{overflow:hidden;max-width:100%;max-height:100%}.ilab-crop-now-wrapper{margin-top:12px}.ilabc-cropper{max-width:100%;max-height:100%}.ilabm-sidebar-content-cropper{flex-direction:column!important;padding:10px;overflow:scroll}.ilabm-sidebar-content-cropper h3{margin-top:0;font-size:11px;text-transform:uppercase;color:#888;font-weight:700}.cropper-dashed.dashed-h{top:38.4615385%;height:23.076923%}.cropper-dashed.dashed-v{left:38.4615385%;width:23.076923%}.ilabc-current-crop-container{position:relative;margin-bottom:15px}.ilabc-crop-preview-container,.ilabc-current-crop-container{background-image:url(../img/ilab-imgix-edit-bg.png);display:flex;align-items:center;justify-content:center;flex:1}.ilab-current-crop-img{position:absolute;-o-object-fit:contain;object-fit:contain;padding:0!important;margin:0!important;height:100%;width:100%}#ilab-crop-aspect-checkbox-container{display:flex;align-items:center}#ilab-crop-aspect-checkbox-container input{margin:0 8px 0 0;padding:0}#ilab-s3-info-meta .inside{padding:0 5px 10px}.info-panel-tabs{margin:-7px -5px 0;padding:6px 10px 0;background-color:rgba(0,0,0,.125)}.info-panel-tabs ul{display:flex;margin:0;padding:0;height:100%}.info-panel-tabs ul li{padding:5px 10px;font-size:11px;text-transform:uppercase;margin:0 10px 0 0;display:block;background-color:rgba(0,0,0,.0625);cursor:pointer;font-weight:700}.info-panel-tabs ul li.info-panel-missing-sizes{color:#9e0000}.info-panel-tabs ul li.active{background-color:#fff}.info-panel-contents{padding:15px 10px 0}.info-panel-contents .info-line{display:flex;flex-direction:column;margin-bottom:15px}.info-panel-contents .info-line h3,.info-panel-contents .info-line label{font-size:11px;text-transform:uppercase;margin:0;font-weight:700}.info-panel-contents .info-line label{margin-bottom:4px}.info-panel-contents .info-line select{font-size:12px!important}.info-panel-contents .info-line a{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.info-panel-contents .info-notice{padding:10px 5px 20px}.info-panel-contents .info-size-selector{margin-bottom:15px}.info-panel-contents .button-row{margin-bottom:15px;border-top:1px solid #eaeaea;padding-top:15px;display:flex;justify-content:flex-end;align-items:center}.info-panel-contents .button-row #ilab-info-regenerate-status{display:flex;padding:0;justify-content:center;align-items:center;width:100%;font-size:12px}.info-panel-contents .button-row #ilab-info-regenerate-status .spinner{float:none;display:block;margin:0 8px 0 0;width:16px;height:16px;background-size:16px 16px}.info-panel-contents .links-row{display:flex;align-items:center;justify-content:center;background-color:rgba(0,0,0,.05);border:1px solid rgba(0,0,0,.1);border-radius:8px;padding:10px 0;margin-bottom:15px}.info-panel-contents .links-row a{color:#000;display:flex;align-items:center;margin-right:20px;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:11px}.info-panel-contents .links-row a:last-of-type{margin-right:0}.info-panel-contents .links-row a .dashicons{margin-right:3px;width:16px;height:16px;font-size:16px}#ilab-media-grid-info-popup{position:absolute;z-index:170000;opacity:1;transition:opacity .33s;-webkit-filter:drop-shadow(0 0 5px rgba(50,50,50,.5));filter:drop-shadow(0 0 5px rgba(50,50,50,.5));display:flex;align-items:center}#ilab-media-grid-info-popup.hidden{opacity:0;pointer-events:none}#ilab-media-grid-info-popup h2{text-transform:uppercase;font-size:9px;padding:4px 10px;margin:0;color:rgba(0,0,0,.33)}#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content{min-height:554px;background-color:#fff;width:275px;max-width:275px;padding-bottom:1px;display:flex;flex-direction:column;position:relative}#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content .info-panel-tabs{margin:0}#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content .info-panel-contents>div{display:flex;flex-direction:column}#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content .info-panel-contents>div>div{flex:1}#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content .info-panel-contents .info-file-info-size{flex-grow:1;display:flex;flex-direction:column}#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content .button-row{position:absolute;bottom:0;right:0;left:0}#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content .ilab-loader-container{display:flex;justify-content:center;align-items:center;position:absolute;left:0;top:0;right:0;bottom:0;transition:opacity .5s}#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content .ilab-loader,#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content .ilab-loader:after{border-radius:50%;width:24px;height:24px}#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content .ilab-loader{font-size:5px;text-indent:-9999em;border:1.1em solid hsla(0,0%,100%,.2);border-left-color:#fff;-webkit-animation:load8 1.1s linear infinite;animation:load8 1.1s linear infinite}#ilab-media-grid-info-popup .ilab-media-grid-info-popup-content .ilab-loader.ilab-loader-dark{border:1.1em solid rgba(0,0,0,.2);border-left-color:#000}@-webkit-keyframes load8{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes load8{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}#ilab-media-grid-info-popup .ilab-media-popup-arrow-left{width:45px;display:flex;justify-content:flex-end}#ilab-media-grid-info-popup .ilab-media-popup-arrow-left>div{width:0;height:0;border-color:transparent #fff transparent transparent;border-style:solid;border-width:9px 15.6px 9px 0}#ilab-media-grid-info-popup .ilab-media-popup-arrow-right{width:45px;display:flex;justify-content:flex-start}#ilab-media-grid-info-popup .ilab-media-popup-arrow-right>div{width:0;height:0;border-color:transparent transparent transparent #fff;border-style:solid;border-width:9px 0 9px 15.6px;margin-right:30px}#ilab-media-grid-info-popup.popup-left .ilab-media-popup-arrow-right,#ilab-media-grid-info-popup.popup-right .ilab-media-popup-arrow-left{display:none}li.attachment{transition:opacity .3s ease-out,transform .3s ease-out,box-shadow .3s ease-out}li.attachment .ilab-loader-container{z-index:100}li.attachment.info-focused{transform:scale(1.1)}li.attachment.info-focused>div:first-of-type{box-shadow:0 0 5px 0 rgba(50,50,50,.5)}li.attachment.info-unfocused{opacity:.33}#ilab-media-grid-info-popup.ilab-popup-document h2{background-color:rgba(0,0,0,.125);color:#000}#ilab-media-grid-info-popup.ilab-popup-document .info-panel-contents{padding:10px 10px 0}#ilab-media-grid-info-popup.ilab-popup-document .ilab-media-grid-info-popup-content{min-height:484px}table.ilab-image-sizes{border-collapse:collapse;width:100%}table.ilab-image-sizes td,table.ilab-image-sizes th{padding:10px}table.ilab-image-sizes td.center{text-align:center}table.ilab-image-sizes thead th{border:2px solid #f1f1f1;background-color:#dadada}table.ilab-image-sizes tr{border-bottom:1px solid #dadada}.ilab-add-image-size-backdrop{display:flex;align-items:center;justify-content:center}.ilab-add-image-size-container{left:auto;right:auto;bottom:auto;top:auto}.ilab-new-image-size-form{padding:20px;display:flex;flex-direction:column}.ilab-new-image-size-form div.row{display:flex;align-items:center;margin-bottom:15px}.ilab-new-image-size-form div.row>label{width:90px;text-align:right;margin-right:15px;font-weight:700}.ilab-new-image-size-form div.button-row{padding:10px;text-align:right}.ilab-delete-size-button{font-size:0;display:inline-block;width:18px;height:18px;background-image:url(../img/ilab-ui-icon-trash.svg);background-size:contain;background-position:50%;background-repeat:no-repeat;background-size:12px;margin-right:10px}.ilab-delete-size-button.disabled{opacity:.33;pointer-events:none}.ilab-delete-size-button:hover{background-image:url(../img/ilab-ui-icon-trash-hover.svg)}.ilab-size-settings-button{font-size:0;display:inline-block;width:18px;height:18px;background-image:url(../img/ilab-ui-icon-settings.svg);background-size:contain;background-position:50%;background-repeat:no-repeat;background-size:14px}.ilab-size-settings-button.disabled{opacity:.33;pointer-events:none}.ilab-size-settings-button:hover{background-image:url(../img/ilab-ui-icon-settings-hover.svg)}.ilab-browser-select-table-container table,.ilab-storage-browser table{border-collapse:collapse;width:100%;font-size:1.1em}.ilab-browser-select-table-container table td,.ilab-browser-select-table-container table th,.ilab-storage-browser table td,.ilab-storage-browser table th{padding:12px}.ilab-browser-select-table-container table thead tr th,.ilab-storage-browser table thead tr th{text-align:left;border:2px solid #f1f1f1;background-color:#dadada}.ilab-browser-select-table-container table thead tr th.checkbox,.ilab-storage-browser table thead tr th.checkbox{max-width:30px;width:30px;text-align:center}.ilab-browser-select-table-container table tbody tr,.ilab-storage-browser table tbody tr{transition:background-color .125s linear;cursor:pointer}.ilab-browser-select-table-container table tbody tr:hover,.ilab-storage-browser table tbody tr:hover{background-color:#fff}.ilab-browser-select-table-container table tbody tr input[type=checkbox],.ilab-storage-browser table tbody tr input[type=checkbox]{z-index:1000}.ilab-browser-select-table-container table tbody tr td,.ilab-storage-browser table tbody tr td{text-decoration:none}.ilab-browser-select-table-container table tbody tr td.checkbox,.ilab-storage-browser table tbody tr td.checkbox{max-width:30px;width:30px;text-align:center}.ilab-browser-select-table-container table tbody tr td.entry,.ilab-storage-browser table tbody tr td.entry{display:flex;align-items:center}.ilab-browser-select-table-container table tbody tr td.actions,.ilab-storage-browser table tbody tr td.actions{text-align:center;width:130px;max-width:130px}.ilab-browser-select-table-container table tbody tr td.actions .button-delete,.ilab-storage-browser table tbody tr td.actions .button-delete{margin-right:0;margin-left:10px;color:#fff;border-color:#920002;background:#ca0002;box-shadow:0 1px 0 #cc0005}.ilab-browser-select-table-container table tbody tr td.actions .button-delete svg,.ilab-storage-browser table tbody tr td.actions .button-delete svg{height:14px}.ilab-browser-select-table-container table tbody tr td.actions .button-delete.disabled,.ilab-storage-browser table tbody tr td.actions .button-delete.disabled{color:#ff6468!important;border-color:#920002!important;background:#c6282a!important;box-shadow:0 1px 0 #cc0005!important;text-shadow:0 1px 0 #cc0005!important}.ilab-browser-select-table-container table tbody tr td.actions .button-delete.disabled svg>path,.ilab-browser-select-table-container table tbody tr td.actions .button-delete.disabled svg>rect,.ilab-storage-browser table tbody tr td.actions .button-delete.disabled svg>path,.ilab-storage-browser table tbody tr td.actions .button-delete.disabled svg>rect{fill:#ff6468}.ilab-browser-select-table-container table tbody tr td img.loader,.ilab-storage-browser table tbody tr td img.loader{display:none;margin-right:10px;width:16px;height:16px}.ilab-browser-select-table-container table tbody tr td span,.ilab-storage-browser table tbody tr td span{display:block;width:16px;height:16px;background-size:contain;background-repeat:no-repeat;margin-right:10px}.ilab-browser-select-table-container table tbody tr td span.icon-dir,.ilab-storage-browser table tbody tr td span.icon-dir{background-image:url(../img/ilab-ui-icon-folder.svg)}.ilab-browser-select-table-container table tbody tr td span.icon-file,.ilab-storage-browser table tbody tr td span.icon-file{background-image:url(../img/ilab-ui-icon-file.svg)}.ilab-browser-select-table-container table tbody tr td span.icon-up,.ilab-storage-browser table tbody tr td span.icon-up{background-image:url(../img/ilab-ui-icon-up-dir.svg)}.ilab-browser-select-table-container table tr,.ilab-storage-browser table tr{border-bottom:1px solid #dadada}.mcsb-buttons .button{margin-right:5px;display:flex;align-items:center}.mcsb-buttons .button svg{height:16px;width:auto;margin-right:8px}.mcsb-buttons .button svg>path,.mcsb-buttons .button svg>rect{fill:#fff}.mcsb-buttons .button-primary.disabled svg>path,.mcsb-buttons .button-primary.disabled svg>rect{fill:#66c6e4}.mcsb-buttons .button-create-folder svg{height:12px}.mcsb-buttons .button-import{margin-left:10px}.mcsb-buttons .button-import svg{height:14px}.mcsb-buttons .button-delete{margin-right:0;margin-left:10px;color:#fff;border-color:#920002;background:#ca0002;box-shadow:0 1px 0 #cc0005}.mcsb-buttons .button-delete svg{height:14px}.mcsb-buttons .button-delete.disabled{color:#ff6468!important;border-color:#920002!important;background:#c6282a!important;box-shadow:0 1px 0 #cc0005!important;text-shadow:0 1px 0 #cc0005!important}.mcsb-buttons .button-delete.disabled svg>path,.mcsb-buttons .button-delete.disabled svg>rect{fill:#ff6468}.mcsb-buttons .button-cancel{color:#fff;border-color:#920002;background:#ca0002;text-shadow:0 1px 0 #cc0005!important;box-shadow:0 1px 0 #cc0005}.mcsb-buttons .button-cancel:hover{border-color:#9f0002;background:#d80002;text-shadow:0 1px 0 #d60005!important;box-shadow:0 1px 0 #d60005}.mcsb-actions{margin-bottom:18px;font-size:1.1em}.mcsb-actions,.mcsb-actions div.mcsb-action-buttons{display:flex;align-items:center}.ilab-storage-browser-header{flex:1;padding:14px 9px;box-shadow:inset 0 0 3px 0 rgba(0,0,0,.125);border:1px solid #ddd;border-radius:8px;margin-right:18px}.ilab-storage-browser-header ul{margin:0;padding:0;display:flex}.ilab-storage-browser-header ul li{padding:0;position:relative;display:block;margin:0 30px 0 0}.ilab-storage-browser-header ul li a{text-decoration:none}.ilab-storage-browser-header ul li:first-of-type{padding-left:35px}.ilab-storage-browser-header ul li:first-of-type:before{background-image:url(../img/ilab-ui-icon-folder.svg);width:16px;height:16px;left:0}.ilab-storage-browser-header ul li:after,.ilab-storage-browser-header ul li:first-of-type:before{content:" ";position:absolute;background-size:contain;background-position:50%;background-repeat:no-repeat;top:50%;margin-left:10px;transform:translateY(-50%)}.ilab-storage-browser-header ul li:after{background-image:url(../img/ilab-ui-path-divider.svg);width:9px;height:9px}.ilab-storage-browser-header ul li:last-of-type:after{display:none}#mcsb-progress-modal{z-index:10000;position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,.66);display:flex;align-items:center;justify-content:center;transition:opacity .15s linear;opacity:1;pointer-events:none}#mcsb-progress-modal.hidden{opacity:0}#mcsb-progress-modal .mcsb-progress-container{min-width:40vw;background-color:#fff;padding:30px}#mcsb-progress-modal .mcsb-progress-container .mcsb-progress-label{font-weight:700;font-size:1.1em;margin-bottom:20px}#mcsb-progress-modal .mcsb-progress-container .mcsb-progress-bar{position:relative;background-color:#eaeaea;height:24px;border-radius:12px;overflow:hidden}#mcsb-progress-modal .mcsb-progress-container .mcsb-progress-bar #mcsb-progress{position:absolute;left:0;top:0;bottom:0;background-color:#4f90c4}#mcsb-import-options-modal{z-index:10000;position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,.66);display:flex;align-items:center;justify-content:center;transition:opacity .15s linear;opacity:1;pointer-events:none}#mcsb-import-options-modal.hidden{opacity:0}#mcsb-import-options-modal.hidden .mcsb-import-options-container{pointer-events:none}#mcsb-import-options-modal .mcsb-import-options-container{min-width:40vw;max-width:800px;background-color:#fff;padding:30px;pointer-events:all;display:flex;flex-direction:column}#mcsb-import-options-modal .mcsb-import-options-container h3{display:block;padding:0;margin:0 0 25px;position:relative;font-weight:700;font-size:1.125em}#mcsb-import-options-modal .mcsb-import-options-container div.mcsb-import-options{margin-bottom:50px}#mcsb-import-options-modal .mcsb-import-options-container div.mcsb-import-options ul{display:grid;grid-template-columns:repeat(1,1fr);grid-row-gap:20px;grid-column-gap:10px}#mcsb-import-options-modal .mcsb-import-options-container div.mcsb-import-options ul li{display:flex;align-items:flex-start}#mcsb-import-options-modal .mcsb-import-options-container div.mcsb-import-options ul li h4{margin:0;padding:0;font-size:1em}#mcsb-import-options-modal .mcsb-import-options-container div.mcsb-import-options ul li .mcsb-option{padding-top:2px}#mcsb-import-options-modal .mcsb-import-options-container div.mcsb-import-options ul li .mcsb-option-description{margin-left:15px}#mcsb-import-options-modal .mcsb-import-options-container div.mcsb-import-buttons{display:flex;justify-content:flex-end}#mcsb-import-options-modal .mcsb-import-options-container div.mcsb-import-buttons .button{margin:0 0 0 20px}#ilab-upload-target{position:fixed;left:0;right:0;bottom:0;top:0;display:flex;justify-content:center;align-items:center;font-size:2em;font-weight:700;color:#fff;background-color:rgba(28,90,129,.75);z-index:100000;transition:opacity .125s linear;opacity:0;pointer-events:none}#wpbody.drag-inside #ilab-upload-target{opacity:1}#mcsb-upload-modal{position:fixed;left:0;right:0;bottom:0;top:0;display:flex;justify-content:center;align-items:center;background:rgba(0,0,0,.66);z-index:100000;transition:opacity .125s linear;opacity:1}#mcsb-upload-modal.hidden{opacity:0;pointer-events:none}#mcsb-upload-modal #mcsb-upload-container{min-width:630px;min-height:385px;background-color:#fff;display:flex;flex-direction:column}#mcsb-upload-modal #mcsb-upload-container div.mcsb-upload-header{padding:20px;position:relative;font-weight:700}#mcsb-upload-modal #mcsb-upload-container div.mcsb-upload-items{position:relative;flex:1;background-color:#eaeaea}#mcsb-upload-modal #mcsb-upload-container div.mcsb-upload-items #mcsb-upload-items-container{padding:15px;position:absolute;left:0;top:0;right:0;bottom:0;overflow:auto;display:flex;flex-wrap:wrap}.ilab-browser-select{position:absolute;left:0;top:0;width:100%;height:100%;display:flex;flex-direction:column}.ilab-browser-select .ilab-browser-select-header{height:48px;min-height:48px;padding-left:12px;padding-right:64px;display:flex;align-items:center}.ilab-browser-select .ilab-browser-select-header input{flex:1;padding:7px 11px;border-radius:4px}.ilab-browser-select .ilab-browser-select-header input:disabled{color:#000}.ilab-browser-select .ilab-browser-select-table-container{flex:1;overflow-y:auto;background-color:#efefef}.ilab-browser-select .ilab-browser-select-footer{height:48px;min-height:48px;display:flex;align-items:center;justify-content:flex-end;padding-right:12px}.ilab-browser-select .ilab-browser-select-footer .button{margin-left:12px}.mcsb-modal-contents{border-radius:8px}.mcsb-modal-contents h3{background-image:url(../img/icon-cloud.svg);background-position:0;background-repeat:no-repeat;background-size:44px 44px;padding:12px 0 12px 60px!important}#task-manager div.available-tasks{padding:15px 20px 10px;display:flex;flex-direction:column;border-radius:8px;margin-bottom:25px;margin-top:30px;background-color:#e4e4e4}#task-manager div.available-tasks h2{margin:0 0 15px;padding:0;text-transform:uppercase;font-size:11px;color:#777}#task-manager div.available-tasks div.buttons{display:flex;flex-wrap:wrap}#task-manager div.available-tasks div.buttons .button{margin:0 10px 10px 0}#task-manager div.task-list{padding:18px 20px;border-radius:8px;background-color:#e4e4e4;margin-bottom:25px}#task-manager div.task-list h2{margin:0 0 15px;padding:0;text-transform:uppercase;font-size:11px;color:#777;display:flex;align-items:center;justify-content:space-between}#task-manager div.task-list table.task-table{width:100%;font-size:.9em}#task-manager div.task-list table.task-table td,#task-manager div.task-list table.task-table th{text-align:left;white-space:nowrap;padding:10px 20px;background-color:#efefef}#task-manager div.task-list table.task-table td.progress,#task-manager div.task-list table.task-table td.schedule,#task-manager div.task-list table.task-table th.progress,#task-manager div.task-list table.task-table th.schedule{width:100%}#task-manager div.task-list table.task-table th{background-color:#3a5674;color:#fff}#task-manager div.task-list table.task-table th:first-of-type{border-top-left-radius:4px}#task-manager div.task-list table.task-table th:last-of-type{border-top-right-radius:4px}#task-manager div.task-list table.task-table td.status{text-transform:capitalize}#task-manager div.task-list table.task-table td.status.status-complete{font-weight:700;color:green}#task-manager div.task-list table.task-table td.status.status-error{font-weight:700;color:#a70000}#task-manager div.task-list table.task-table td.progress{position:relative}#task-manager div.task-list table.task-table td.progress div.progress-bar{position:absolute;left:10px;top:10px;bottom:10px;right:10px;background-color:#ccc;display:flex;align-items:center;justify-content:center;overflow:hidden;border-radius:4px}#task-manager div.task-list table.task-table td.progress div.progress-bar .bar{position:absolute;left:0;top:0;bottom:0;width:75%;background-color:#50ade2}#task-manager div.task-list table.task-table td.progress div.progress-bar .amount{z-index:2;color:#fff;font-weight:700}#task-manager div.task-list table.task-table tbody tr:last-of-type td:first-of-type{border-bottom-left-radius:4px}#task-manager div.task-list table.task-table tbody tr:last-of-type td:last-of-type{border-bottom-right-radius:4px}.task-options-modal{position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,.75);z-index:100000;display:flex;align-items:center;justify-content:center;transition:opacity .25s linear}.task-options-modal.invisible{opacity:0}.task-options-modal.invisible .task-modal{transform:scale(.95)}.task-options-modal .task-modal{background-color:#fff;padding:30px;border-radius:8px;transition:transform .25s ease-in-out}.task-options-modal .task-modal h2{padding:0;font-size:1.2em;margin:0 0 40px}.task-options-modal .task-modal form ul{padding:0;display:flex;flex-direction:column;margin:20px 0 0}.task-options-modal .task-modal form ul li{display:flex;margin-bottom:30px}.task-options-modal .task-modal form ul li:last-of-type{margin-bottom:0}.task-options-modal .task-modal form ul li>div:first-of-type{padding:10px 10px 20px 0;width:160px;min-width:160px;line-height:1.3;font-weight:600}.task-options-modal .task-modal form ul li>div:last-of-type{flex:1}.task-options-modal .task-modal form ul li div.description{margin-top:8px}.task-options-modal .task-modal form ul li div.option-ui{display:flex;align-items:center;width:100%}.task-options-modal .task-modal form ul li div.option-ui.option-ui-browser input[type=text]{flex:1;margin-right:10px;padding:7px 11px;border-radius:4px}.task-options-modal .task-modal form ul li div.option-ui.option-ui-browser input[type=text]:disabled{color:#000}.task-options-modal .task-modal form ul li div.option-ui.option-ui-media-select{display:flex;flex:1}.task-options-modal .task-modal form ul li div.option-ui.option-ui-media-select .media-select-label{flex:1;box-sizing:border-box;margin-right:10px;padding:7px 11px;border-radius:4px;background-color:hsla(0,0%,100%,.498039);border:1px solid hsla(0,0%,87.1%,.74902)}.task-options-modal .task-modal form ul li div.option-ui.option-ui-media-select .button{margin-right:5px}.task-options-modal .task-modal div.buttons{margin-top:40px;display:flex;justify-content:flex-end}.task-options-modal .task-modal div.buttons .button{margin-left:15px}#task-batch div.buttons{display:flex;justify-content:flex-end;margin-top:40px}#task-batch div.buttons .button-whoa{background:#a42929!important;border-color:#e62a2a #a42929 #a42929!important;box-shadow:0 1px 0 #a42929!important;color:#fff!important;text-decoration:none!important;text-shadow:0 -1px 1px #a42929,1px 0 1px #a42929,0 1px 1px #a42929,-1px 0 1px #a42929!important}#task-batch div.task-info .info-warning{border:1px solid orange;padding:24px;background:rgba(255,165,0,.125);margin-top:20px;border-radius:8px}#task-batch div.task-info .info-warning h4{padding:0;font-size:14px;margin:0 0 8px}#task-batch div.task-info .wp-cli-callout{padding:24px;background:#ddd;margin-top:20px;border-radius:8px}#task-batch div.task-info .wp-cli-callout h3{margin:0;padding:0;font-size:14px}#task-batch div.task-info .wp-cli-callout code{background-color:#bbb;padding:10px 15px;margin-top:5px;display:inline-block}#task-batch div.task-info .task-options{padding:24px;background:#e7e7e7;margin-top:20px;border-radius:8px}#task-batch div.task-info .task-options h3{margin:0;padding:0;font-size:14px}#task-batch div.task-info .task-options ul{padding:0;display:flex;flex-direction:column;margin:20px 0 0}#task-batch div.task-info .task-options ul li{display:flex;margin-bottom:30px}#task-batch div.task-info .task-options ul li:last-of-type{margin-bottom:0}#task-batch div.task-info .task-options ul li>div:first-of-type{padding:10px 10px 20px 0;width:160px;min-width:160px;line-height:1.3;font-weight:600}#task-batch div.task-info .task-options ul li div.description{margin-top:8px}#task-batch div.task-info .task-options ul li div.option-ui{display:flex;align-items:center}#task-batch div.task-info .task-options ul li div.option-ui.option-ui-browser{display:flex;width:50vw}#task-batch div.task-info .task-options ul li div.option-ui.option-ui-browser input[type=text]{flex:1;margin-right:10px;padding:7px 11px;border-radius:4px}#task-batch div.task-info .task-options ul li div.option-ui.option-ui-browser input[type=text]:disabled{color:#000}#task-batch div.task-info .task-options ul li div.option-ui.option-ui-media-select{display:flex;width:50vw}#task-batch div.task-info .task-options ul li div.option-ui.option-ui-media-select .media-select-label{flex:1;box-sizing:border-box;margin-right:10px;padding:7px 11px;border-radius:4px;background-color:hsla(0,0%,100%,.498039)}#task-batch div.task-info .task-options ul li div.option-ui.option-ui-media-select .button{margin-right:5px}#task-batch div.task-progress{padding:24px;background:#ddd;border-radius:8px}#task-batch div.task-progress .progress-container{position:relative;width:100%;height:14px;background:#aaa;border-radius:16px;overflow:hidden;background-image:url(../img/candy-stripe.svg)}#task-batch div.task-progress .progress-container .progress-bar{background-color:#4f90c4;height:100%;width:10px}#task-batch div.task-progress .progress-thumbnails{position:relative;width:100%;height:150px;margin-bottom:15px}#task-batch div.task-progress .progress-thumbnails .progress-thumbnails-container{position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;-webkit-mask-image:linear-gradient(90deg,#ddd 0,hsla(0,0%,86.7%,0) 90%);mask-image:linear-gradient(90deg,#ddd 0,hsla(0,0%,86.7%,0) 90%)}#task-batch div.task-progress .progress-thumbnails .progress-thumbnails-container img{width:150px;height:150px;max-width:150px;max-height:150px;border-radius:4px;margin-right:10px}#task-batch div.task-progress .progress-thumbnails .progress-thumbnails-container .item-title{color:#fff;position:absolute;right:10px;bottom:7px;text-align:right;left:10px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;text-shadow:1px 1px 2px rgba(0,0,0,.75);font-weight:700}#task-batch div.task-progress .progress-thumbnails .progress-thumbnails-container .progress-thumb{position:absolute;left:0;top:0;width:150px;min-width:150px;max-width:150px;height:150px;min-height:150px;max-height:150px;background-size:cover;background-position:50%;background-repeat:no-repeat;margin-right:10px;border-radius:4px;background-color:#888;transition:opacity .25s linear,transform .25s linear}#task-batch div.task-progress .progress-thumbnails .progress-thumbnails-container .progress-thumb.invisible{opacity:0;transform:scale(.7)}#task-batch div.task-progress .progress-thumbnails .progress-thumbnails-container .progress-icon{position:absolute;left:0;top:0;position:relative;width:150px;min-width:150px;max-width:150px;height:150px;min-height:150px;max-height:150px;display:flex;align-items:center;justify-content:center;transition:opacity .25s linear,transform .25s linear}#task-batch div.task-progress .progress-thumbnails .progress-thumbnails-container .progress-icon.invisible{opacity:0;transform:scale(.8)}#task-batch div.task-progress .progress-thumbnails .progress-thumbnails-fade{background:linear-gradient(90deg,#ddd 0,hsla(0,0%,86.7%,0) 90%);position:absolute;left:150px;top:0;right:0;bottom:0}@supports ((-webkit-mask-image:linear-gradient(to left,rgba(221,221,221,0) 0%,#dddddd 95%,#dddddd 100%)) or (mask-image:linear-gradient(to left,rgba(221,221,221,0) 0%,#dddddd 95%,#dddddd 100%))){#task-batch div.task-progress .progress-thumbnails .progress-thumbnails-fade{display:none}}#task-batch div.task-progress .progress-thumbnails .progress-thumbnails-cloud{position:absolute;right:20px;top:50%;transform:translateY(-50%)}#task-batch div.task-progress .progress-stats{margin-top:20px;display:flex;align-items:center;justify-content:center}@media (max-width:960px){#task-batch div.task-progress .progress-stats{flex-direction:column;align-items:flex-start;justify-content:flex-start}}#task-batch div.task-progress .progress-stats div.group-break{display:flex;margin-right:1.2195121951vw}#task-batch div.task-progress .progress-stats div.group-break:last-of-type{margin-right:0}#task-batch div.task-progress .progress-stats div.group-break:first-of-type{flex:1}@media (max-width:960px){#task-batch div.task-progress .progress-stats div.group-break{width:100%;margin-bottom:1.2195121951vw}}#task-batch div.task-progress .progress-stats div.group{display:flex;align-items:center;padding:1.0975609756vw 0;background-color:#e6e6e6;border-radius:8px;margin-right:1.2195121951vw}#task-batch div.task-progress .progress-stats div.group *{white-space:nowrap}@media (max-width:960px){#task-batch div.task-progress .progress-stats div.group.mobile-flexed{flex:1}}#task-batch div.task-progress .progress-stats div.group.flexed{flex:1}#task-batch div.task-progress .progress-stats div.group:last-of-type{margin-right:0}#task-batch div.task-progress .progress-stats div.group div.callout{position:relative;margin-right:.6097560976vw;padding:0 1.4634146341vw}#task-batch div.task-progress .progress-stats div.group div.callout:after{display:block;position:absolute;content:"";height:50%;width:1px;right:0;top:25%;background-color:rgba(0,0,0,.25)}#task-batch div.task-progress .progress-stats div.group div.callout:last-of-type:after{display:none}#task-batch div.task-progress .progress-stats div.group div.callout p.value{line-height:1;padding:0;font-size:1.5853658537vw;font-weight:300;margin:0 0 .487804878vw}#task-batch div.task-progress .progress-stats div.group div.callout p.value.status{text-transform:capitalize}@media (max-width:960px){#task-batch div.task-progress .progress-stats div.group div.callout p.value{font-size:2.9268292683vw}}#task-batch div.task-progress .progress-stats div.group div.callout h4{line-height:1;margin:0;padding:0;font-size:.6097560976vw;text-transform:uppercase}@media (max-width:960px){#task-batch div.task-progress .progress-stats div.group div.callout h4{font-size:.8536585366vw}} -/*! mediabox v1.1.3 | (c) 2018 Pedro Rogerio | https://github.com/pinceladasdaweb/mediabox */.stop-scroll{height:100%;overflow:hidden}.mediabox-wrap{position:fixed;width:100%;height:100%;background-color:rgba(0,0,0,.8);top:0;left:0;opacity:0;z-index:999;-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:mediabox;animation-name:mediabox}@-webkit-keyframes mediabox{0%{opacity:0}to{opacity:1}}@keyframes mediabox{0%{opacity:0}to{opacity:1}}.mediabox-content{max-width:853px;display:block;margin:0 auto;height:100%;position:relative}.mediabox-content iframe{max-width:100%!important;width:100%!important;display:block!important;height:480px!important;border:none!important;position:absolute;top:0;bottom:0;margin:auto 0}.mediabox-hide{-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:mediaboxhide;animation-name:mediaboxhide}@-webkit-keyframes mediaboxhide{0%{opacity:1}to{opacity:0}}@keyframes mediaboxhide{0%{opacity:1}to{opacity:0}}.mediabox-close{position:absolute;top:0;cursor:pointer;bottom:528px;right:0;margin:auto 0;width:24px;height:24px;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAMvSURBVHja7Js9aBRBFMd/d1lPY6FiJVjY+Fkoxl7wA1Q0prQRS6tgoZV2MWIRRVHUUq3U+JnESrS2sBXBzipREWMlATXwt8gFznC5nd15M7Nn8uC45nZnfr/dY96+N1uTxFKOOks8lgUU/H2t4tJqIQUcAiaBGeBymcECRgO4B/wBPgJ9zkdKcvkclfRL/8ZtSTXH40N+GpLGF8zth6Q9Lse7DHCsDXxVJLSDLyQhb4B+Sb/VOVJJ6ATfKqGvrIDjDvCpJLjAz8d0JwmLDTBQAD62hIakiYJzm5a021VAfwn4WBLKwLdK2JUnIJP0XX4RSoIP/Hy8W3jeepv1dL3nmjwI3DLOExrAU2DA8zwb8xKhGeCuwYQtJTSAZwbwAHdcEqFM0mPZhO/foSHppdFcrraby2IDV0FCcPi8PCClhCjwLplgCgkrDeGv5I3pcjViSogK7yogloTo8EUEhJaQBL6oAGsJ9yVtkrRD0qsU8JKolagKZ8AD4ETFymFXgPOAQpXE5mMWOAk86XZ4n6pwlSSUhvcti1dBghe8RV8gpYQRX3irxkgKCSPABV94y85QTAlm8NatsRgSTOGBUnmAS57w3KiA0Ro3gHOW8KEEAOwE3hvfXWubFauu6A6vCND07OmW9viq5vpsGT3AtRAN2XoA+BfAwQBiTweoNpMZw48BRwKuAoPN7zNVWwZjwAfpO9S7DN5cQmYAPw4cTvAsYPJ3qHcpvNmdUO9ieBMJZQT0AhMVgfeWUC8BP87cjjHfuA6sATY0c4c0EgpUUHslvTaq3l5aUL1N1oarAnxSCVWBTyYhJvyw41XJJI3GkpAH/yYyfHQJi01gdUL4qBKqCh9NQrtBx4wGvGi0XS6T9MhoTkN5AtZVDN5awlTePsGfwDfPjGwYGDKu3s4Cp4BRz/N8cskED0iaqciVt7wTvkra5roKlJEQGt5HwhdJ24vmAUUkDEV+VyCT9NBxbp/bXXnXTNBFQmz4IhI6wrs+C+zvICEVvIuEKUlbrZ4G97WRkBq+k4RJSVusd4ntlfSheVudrQh8q4SbmntH6K2kzSF3if1Xsfzq7LKAJR5/BwCdAQBJn4egPgAAAABJRU5ErkJggg==") no-repeat;background-size:24px 24px}.mediabox-close:hover{opacity:.5}@media (max-width:768px){.mediabox-content{max-width:90%}}@media (max-width:600px){.mediabox-content iframe{height:320px!important}.mediabox-close{bottom:362px}}@media (max-width:480px){.mediabox-content iframe{height:220px!important}.mediabox-close{bottom:262px}}.mediabox-wrap{z-index:1000000}.mediabox-content{max-width:75vw}.mediabox-content iframe{height:42.1875vw!important}.mediabox-close{bottom:46vw}.ic-Super-toggle__label{box-sizing:border-box;margin:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ic-Form-group.ic-Form-group--horizontal .ic-Super-toggle__label{display:inline-flex;align-items:center}.ic-Super-toggle__input{opacity:0;position:absolute}.ic-Super-toggle__input:checked~label .ic-Super-toggle-switch:after{transform:translate3d(100%,0,0)}.ic-Super-toggle__input:checked~label .ic-Super-toggle__disabled-msg:before{content:attr(data-checked)}.ic-Super-toggle__input[disabled]{opacity:0!important}.ic-Super-toggle__input[disabled]~label .ic-Super-toggle-switch{opacity:.33}.ic-Super-toggle-switch{transition:background .1s,border-color .1s;position:relative;line-height:1;display:flex;align-items:center;background-clip:padding-box}.ic-Super-toggle-switch:after{transition:all .1s ease-in-out;content:"";position:absolute;top:0;left:0;transform:translateZ(0);border-radius:100%;box-shadow:0 3px 6px rgba(0,0,0,.3);background-image:url(https://cl.ly/320m31452k2X/handle.svg);background-position:50% 50%;background-repeat:no-repeat;background-size:20px}.ic-Super-toggle__disabled-msg{display:none}.ic-Super-toggle__disabled-msg:before{content:attr(data-unchecked);font-style:italic;opacity:.8}[class^=ic-Super-toggle-option-]{transition:all .2s ease-out;flex:0 0 50%;text-align:center;position:relative;z-index:1;text-transform:uppercase;font-weight:700;line-height:1;speak:none;box-sizing:border-box}.ic-Super-toggle__screenreader{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute}.ic-Super-toggle--on-off{display:inline-block;vertical-align:middle}.ic-Super-toggle--on-off .ic-Super-toggle__input:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #5b6c79,0 3px 6px rgba(0,0,0,.3)}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-switch{background:#4cace3;border-color:#4cace3}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT{color:#fff}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#fff}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT{color:#fff}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#fff}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #4cace3,0 3px 6px rgba(0,0,0,.3)}.ic-Super-toggle--on-off .ic-Super-toggle-switch{width:50px;height:25px;background:#5b6c79;border:2px solid #5b6c79;border-radius:14.5px}.ic-Form-group.ic-Form-group--horizontal .ic-Super-toggle--on-off .ic-Super-toggle-switch{flex:0 0 50px}.ic-Super-toggle--on-off .ic-Super-toggle-switch:after{background-color:#fff;width:25px;height:25px}.ic-Super-toggle--on-off .ic-Super-toggle-option-LEFT{color:#fff}.ic-Super-toggle--on-off .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#fff}.ic-Super-toggle--on-off .ic-Super-toggle-option-RIGHT{color:#fff}.ic-Super-toggle--on-off .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#fff}.ic-Super-toggle--on-off .ic-Super-toggle__svg{max-width:12.5px;max-height:12.5px}.ic-Super-toggle--on-off [class^=ic-Super-toggle-option-]{transition-delay:.1s}.ic-Super-toggle--on-off .ic-Super-toggle-option-LEFT{transform:scale(.1);opacity:0}.ic-Super-toggle--on-off .ic-Super-toggle-option-RIGHT,.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT{transform:scale(1);opacity:1}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT{transform:scale(.1);opacity:0}.toggle-warning{display:inline-block;vertical-align:middle}.toggle-warning .ic-Super-toggle__input:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #5b6c79,0 3px 6px rgba(0,0,0,.3)}.toggle-warning .ic-Super-toggle__input:checked~label .ic-Super-toggle-switch{background:#ffaa10;border-color:#ffaa10}.toggle-warning .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT{color:#fff}.toggle-warning .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#fff}.toggle-warning .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT{color:#fff}.toggle-warning .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#fff}.toggle-warning .ic-Super-toggle__input:checked:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #ffaa10,0 3px 6px rgba(0,0,0,.3)}.toggle-warning .ic-Super-toggle-switch{width:50px;height:25px;background:#5b6c79;border:2px solid #5b6c79;border-radius:14.5px}.ic-Form-group.ic-Form-group--horizontal .toggle-warning .ic-Super-toggle-switch{flex:0 0 50px}.toggle-warning .ic-Super-toggle-switch:after{background-color:#fff;width:25px;height:25px}.toggle-warning .ic-Super-toggle-option-LEFT{color:#fff}.toggle-warning .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#fff}.toggle-warning .ic-Super-toggle-option-RIGHT{color:#fff}.toggle-warning .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#fff}.toggle-warning .ic-Super-toggle__svg{max-width:12.5px;max-height:12.5px}.ic-Super-toggle--ui-switch{display:inline-block;vertical-align:middle}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #5b6c79,0 3px 6px rgba(0,0,0,.3)}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-switch{background:#5b6c79;border-color:#5b6c79}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT{color:#888}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#888}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT{color:#08c}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#08c}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #5b6c79,0 3px 6px rgba(0,0,0,.3)}.ic-Super-toggle--ui-switch .ic-Super-toggle-switch{width:50px;height:25px;background:#5b6c79;border:2px solid #5b6c79;border-radius:14.5px}.ic-Form-group.ic-Form-group--horizontal .ic-Super-toggle--ui-switch .ic-Super-toggle-switch{flex:0 0 50px}.ic-Super-toggle--ui-switch .ic-Super-toggle-switch:after{background-color:#fff;width:25px;height:25px}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-LEFT{color:#08c}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#08c}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-RIGHT{color:#888}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#888}.ic-Super-toggle--ui-switch .ic-Super-toggle__svg{max-width:12.5px;max-height:12.5px}.ic-Super-toggle--ui-switch .ic-Super-toggle__label{display:inline-flex;align-items:center}.ic-Super-toggle--ui-switch .ic-Super-toggle-switch{display:block}.ic-Super-toggle--ui-switch [class^=ic-Super-toggle-option-]{flex:none;min-width:24px}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-LEFT{text-align:left;transform:scale(1.1)}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-RIGHT{text-align:right;transform:scale(.9)}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT{transform:scale(.9)}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT{transform:scale(1.1)}.settings-container{font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;margin:0 0 64px -20px}.settings-container header{position:relative;background-image:url(../img/settings-bg-large.svg);background-position:0;background-repeat:no-repeat;background-size:cover;min-height:80px;width:100%;display:flex;align-items:center}.settings-container header>img{margin:0 20px;width:88px;max-width:88px}.settings-container header h1{margin-left:5px;font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;text-transform:uppercase;font-weight:400;font-size:1.5em;color:#777}.settings-container header .header-actions{position:absolute!important;right:40px;top:50%;transform:translateY(-50%)!important;display:flex}.settings-container header .header-actions a{margin-left:8px;display:flex;align-items:center}.settings-container header .header-actions a svg{height:16px;width:auto;margin-right:8px}.settings-container header .header-actions a svg>path,.settings-container header .header-actions a svg>rect{fill:#fff}.settings-container header .header-actions div.spacer{width:8px;min-width:8px}.settings-container header.all-settings{display:flex;flex-direction:column;align-items:flex-start}.settings-container header.all-settings div.contents{height:104px;display:flex;justify-content:space-between;align-items:center;width:100%}.settings-container header.all-settings div.contents img.logo{margin:0 0 0 23px;width:108px;max-width:108px}.settings-container header.all-settings div.contents div.settings-select-container{background-color:hsla(0,0%,100%,.6);padding:10px 14px;border-radius:8px;z-index:1000;margin-right:23px;display:none}@media (max-width:992px){.settings-container header.all-settings div.contents div.settings-select-container{display:unset}}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown{display:flex;align-items:center;z-index:1000}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown>div:first-of-type{font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;margin-right:10px;color:#777;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:1em}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown{position:relative;width:200px;height:36px;z-index:1000;cursor:pointer}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.current{font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;display:flex;align-items:center;position:absolute;left:0;top:0;right:0;bottom:0;background-color:#eee;border:1px solid #ddd;padding-left:10px;color:#777;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:13px;cursor:pointer;background-image:url(../img/icon-dropdown-arrow.svg);background-repeat:no-repeat;background-position:right 12px center;transition:background-color .15s linear}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.current:hover{background-color:#fff}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items{z-index:1001;position:absolute;top:0;left:0;right:0;transition:opacity .15s linear,transform .15s linear;opacity:0;pointer-events:none}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items.visible{pointer-events:auto;opacity:1}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul{margin:0;padding:0;box-shadow:0 0 8px 1px rgba(0,0,0,.125)}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li{position:relative;margin:0;padding:0;border:1px solid #ddd;border-top:0;align-items:center}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li:first-of-type{border:1px solid #ddd}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li a.tool{position:relative;display:flex;align-items:center;height:36px;background-color:#eee;padding-left:10px;color:#777;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:13px;transition:background-color .15s linear}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li a.tool:hover{background:#fff}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li a.tool-pin{display:block;position:absolute;top:0;width:36px;height:36px;right:0;background-image:url(../img/icon-pin-deselected.svg);background-repeat:no-repeat;background-position:50%}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li a.tool-pin.pinned{background-image:url(../img/icon-pin-selected.svg)}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li.active a.tool{background:#fff}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown.active div.dropdown div.current{background-color:#ddd}.settings-container header.all-settings div.mcloud-settings-tabs{position:relative;width:100%;padding-left:23px;border-bottom:1px solid #d1d1d1;width:calc(100% - 24px);margin-top:8px}@media (max-width:992px){.settings-container header.all-settings div.mcloud-settings-tabs{display:none}}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap{position:relative;overflow:hidden;transform:translateY(1px);width:100%;height:36px}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul{position:absolute;left:0;top:0;bottom:0;width:30000px;display:flex;align-items:center;padding:0;margin:0}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li{height:100%;display:flex;margin:0 3px 0 0;padding:0 11px 0 14px;background-color:#ddd;align-items:center;border:1px solid #d1d1d1;border-bottom:none;border-top-left-radius:4px;border-top-right-radius:4px}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li a.tool{display:flex;align-items:center;color:#777;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:11px;white-space:nowrap}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li a.tool-pin{display:block;width:20px;height:36px;margin-left:8px;background-image:url(../img/icon-pin-deselected.svg);background-repeat:no-repeat;background-position:50%;background-size:14px}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li a.tool-pin.pinned{background-image:url(../img/icon-pin-selected.svg)}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li.active{background-color:#f1f1f1}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li.active a{color:#000}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-nav{position:absolute;display:flex;align-items:center;justify-content:center;width:96px;top:-1px;bottom:0}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-nav span{font-size:0;line-height:0;display:block;width:20px;height:20px;background-repeat:no-repeat;background-position:50%;background-size:contain}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-nav.hidden{opacity:0;pointer-events:none}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-prev{left:0;justify-content:flex-start;background:linear-gradient(90deg,#f1f1f1 50%,hsla(0,0%,94.5%,0))}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-prev span{margin-left:10px;background-image:url(../img/ilab-icons-prev.svg)}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-next{right:0;justify-content:flex-end;background:linear-gradient(270deg,#f1f1f1 50%,hsla(0,0%,94.5%,0))}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-next span{margin-right:10px;background-image:url(../img/ilab-icons-next.svg)}.settings-container header.all-settings div.mcloud-settings-tabs.animated .navwrap ul{transition:transform .25s linear}.settings-container header.all-settings div.mcloud-settings-tabs.animated a.tabs-nav{transition:opacity .25s linear}.settings-container .settings-body{margin:20px}.settings-container .settings-body .settings-description{font-size:1.1em;text-align:center;background-color:#fafafa;padding:25px;border-radius:8px;margin-bottom:20px}.settings-container .settings-body.show-upgrade{display:flex}.settings-container .settings-body.show-upgrade>.settings-interior{flex:1;margin-right:20px}@media (max-width:64em){.settings-container .settings-body.show-upgrade>.settings-interior{order:1;margin-right:0}}@media (max-width:64em){.settings-container .settings-body.show-upgrade{flex-direction:column}}.settings-container .settings-body .upgrade-feature{background-color:#fafafa;border-radius:8px;padding:15px 20px}.settings-container .settings-body .upgrade-feature h2{padding:0;margin:0 0 30px;color:#46a4dd}.settings-container .settings-body .upgrade-feature ul{margin-left:20px;list-style:unset}.settings-container .settings-body .upgrade-feature ul li{list-style-type:square}.settings-container .settings-body .upgrade-feature div.button-container{text-align:right;padding:20px 0}.settings-container .settings-body .upgrade-feature div.button-container a{padding:10px;background-color:#46a4dd;border-radius:6px;color:#fff;text-decoration:none;font-weight:700;font-size:1.1em}.settings-container .settings-body .upgrade-promo{min-width:200px;max-width:320px;position:relative}.settings-container .settings-body .upgrade-promo .upgrade-interior{position:relative;background-color:#fafafa;border-radius:8px;padding:15px 20px}.settings-container .settings-body .upgrade-promo .upgrade-interior h2{padding:0;margin:0 0 30px;color:#46a4dd}.settings-container .settings-body .upgrade-promo .upgrade-interior ul{margin-left:20px}.settings-container .settings-body .upgrade-promo .upgrade-interior ul li{list-style-type:square}@media (max-width:64em){.settings-container .settings-body .upgrade-promo .upgrade-interior ul{display:flex;flex-wrap:wrap;width:100%}@supports (display:grid){.settings-container .settings-body .upgrade-promo .upgrade-interior ul{display:grid;grid-template-columns:1fr 1fr 1fr}}.settings-container .settings-body .upgrade-promo .upgrade-interior ul li{margin-right:30px}}@media (max-width:48.9275em){@supports (display:grid){.settings-container .settings-body .upgrade-promo .upgrade-interior ul{grid-template-columns:1fr 1fr}}}.settings-container .settings-body .upgrade-promo .upgrade-interior div.button-container{text-align:right;padding:20px 0}.settings-container .settings-body .upgrade-promo .upgrade-interior div.button-container a{padding:10px;background-color:#46a4dd;border-radius:6px;color:#fff;text-decoration:none;font-weight:700;font-size:1.1em}.settings-container .settings-body .upgrade-promo .upgrade-interior a.upgrade-close{display:none;position:absolute;top:15px;right:20px}@media (max-width:64em){.settings-container .settings-body .upgrade-promo .upgrade-interior a.upgrade-close{display:block}}@media (max-width:64em){.settings-container .settings-body .upgrade-promo{order:0;margin-bottom:20px;max-width:100%}}@media (max-width:64em){.settings-container .settings-body .upgrade-promo.hide-on-mobile{display:none}}.button-warning{background:#dd9000!important;border-color:#dd9000 #b97800 #b97800!important;box-shadow:0 1px 0 #b97800!important;color:#fff!important;text-decoration:none!important;text-shadow:0 -1px 1px #b97800,1px 0 1px #b97800,0 1px 1px #b97800,-1px 0 1px #b97800!important}.media-cloud-tool-description{padding:24px;background:#ddd;border-radius:8px;margin-bottom:20px}.media-cloud-tool-description h2{font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;text-transform:uppercase;margin:0 0 5px;padding:0}.media-cloud-tool-description p{margin-top:0;font-size:1.2em}.ilab-notification-container .notice{margin-left:0;margin-right:0;margin-bottom:10px}.ilab-notification-container .notice:last-of-type{margin-bottom:20px}.ilab-settings-section{background-color:#fafafa;padding:25px;border-radius:8px;margin-bottom:20px;overflow:hidden;border:1px solid #eaeaea}.ilab-settings-section h2{padding:10px 25px;background-color:#fff;margin:-25px -25px 0;border-bottom:1px solid #eaeaea;font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;font-weight:700;font-size:13px;color:#50ade2!important;text-transform:uppercase;display:flex;align-items:center}.ilab-settings-section h2 a.help-beacon{margin:0 0 0 10px;padding:0;display:block;width:16px;height:16px;color:transparent;overflow:hidden;background-image:url(../img/mcloud-icon-help.svg);background-position:50%;background-size:contain;background-repeat:no-repeat}.ilab-settings-section .section-description{margin-top:20px;margin-bottom:15px;font-style:italic}.ilab-settings-section .checkbox-w-description{display:flex;align-items:center}.ilab-settings-section .checkbox-w-description label{margin-right:20px}.ilab-settings-section .checkbox-w-description>div>p{margin:0}.ilab-settings-toggle{padding:0 25px 5px}.ilab-settings-toggle table.form-table tr{display:flex;flex-direction:row;align-items:center}@media (max-width:48.9275em){.ilab-settings-toggle table.form-table tr{flex-direction:column;align-items:flex-start}}.ilab-settings-toggle table.form-table tr th{min-width:200px;max-width:200px}@media (max-width:48.9275em){.ilab-settings-toggle table.form-table tr{margin-bottom:20px}.ilab-settings-toggle table.form-table tr th{margin-bottom:10px}}.ilab-settings-features{padding:10px 25px 15px}.ilab-settings-features table.form-table tr{display:flex;flex-direction:row;align-items:center}.ilab-settings-features table.form-table tr td.toggle{display:flex;align-items:center;max-width:220px;min-width:220px}.ilab-settings-features table.form-table tr td.toggle div.title{display:flex;flex-direction:column;margin-left:30px;white-space:nowrap;font-weight:700;font-size:1.05em}.ilab-settings-features table.form-table tr td.toggle div.title div.tool-links{display:flex}.ilab-settings-features table.form-table tr td.toggle div.title div.tool-links a{margin-right:10px;margin-top:5px;font-size:.85em;font-weight:400}.ilab-settings-features table.form-table tr td.toggle div.title div.tool-links a:last-of-type{margin-right:0}.ilab-settings-features table.form-table tr td.description p{font-size:1.05em}@media (max-width:48.9275em){.ilab-settings-features table.form-table tr{flex-direction:column;align-items:flex-start;margin-bottom:30px}.ilab-settings-features table.form-table td.toggle div.title{font-size:1.2em!important}}.ilab-settings-button{margin-top:40px;display:flex;justify-content:center}.ilab-settings-button p{padding:0;margin:0}.ilab-settings-batch-tools{display:flex}.ilab-settings-batch-tools a.button{margin-right:10px}.ilab-settings-batch-tools.has-submit{padding-right:10px;margin-right:20px;border-right:1px solid #ccc}span.tool-indicator{background:#ccc;border:1px solid #979797;display:block;width:9px;height:9px;border-radius:9px;margin-right:6px}span.tool-indicator.tool-active{background:#6dd51b}span.tool-indicator.tool-env-active{background:#fdac00}div.ilab-section-doc-links{margin-top:10px}div.ilab-section-doc-links div.doc-links-setting{background-color:rgba(0,0,0,.04);width:100%;border-radius:6px;display:flex;align-items:center;justify-content:center;padding:12px 0}div.ilab-section-doc-links div.doc-links-setting a{margin:0 5px!important}.troubleshooter-info li{margin:0;padding:8px 0 8px 28px;list-style:none;background-repeat:no-repeat;background-position:left top 6px;background-size:20px}.troubleshooter-info li.info-warning{background-image:url(../img/icon-warning.svg)}.troubleshooter-info li.info-success{background-image:url(../img/icon-success.svg)}.troubleshooter-info li.info-error{background-image:url(../img/icon-error.svg)}.troubleshooter-wait{display:flex;align-items:center}.troubleshooter-wait.hidden{display:none}.troubleshooter-wait>img{margin-right:7px;height:18px}.upload-path-preview{margin:10px 0;padding:10px;font-style:italic;background-color:#fff;border:1px dashed #ddd;display:flex;line-height:1;align-items:center}.upload-path-preview span:first-of-type{text-transform:uppercase;color:#ccc;font-size:11px;font-style:normal;margin-right:10px}.subsite-setting-group{margin-bottom:20px}.subsite-setting-group:last-of-type{margin-bottom:0}.subsite-upload-path{display:flex;align-items:center}.subsite-upload-path label{min-width:100px}.presigned-url-container>div{display:flex;align-items:flex-start;margin-bottom:20px}.presigned-url-container>div:nth-of-type(2n){margin-bottom:40px}.presigned-url-container>div:last-of-type{margin-bottom:0}.presigned-url-container>div div.presigned-label{line-height:1.3;font-weight:600;margin-right:10px;margin-top:6px;min-width:175px}.privacy-container>div{display:flex;align-items:flex-start;margin-bottom:20px}.privacy-container>div:last-of-type{margin-bottom:0}.privacy-container>div div.privacy-label{line-height:1.3;font-weight:600;margin-right:10px;margin-top:6px;min-width:135px}#beacon-container iframe{z-index:200000!important}.ilab-popup{position:fixed;left:0;top:0;width:100%;height:100%;background-color:rgba(0,0,0,.85);pointer-events:all;z-index:100002;display:flex;align-items:center;justify-content:center;transition:opacity .25s linear;opacity:1}.ilab-popup .ilab-popup-body{position:relative}.ilab-popup .ilab-popup-body .ilab-popup-contents{width:80vw;height:80vh;min-width:80vw;min-height:80vh;max-width:80vw;max-height:80vh;background-color:#fff}.ilab-popup .ilab-popup-body .ilab-popup-close{position:absolute;right:38px;top:12px;font-size:0}.ilab-popup .ilab-popup-body .ilab-popup-close:after,.ilab-popup .ilab-popup-body .ilab-popup-close:before{position:absolute;left:13px;content:" ";height:25px;width:2px;background-color:#000}.ilab-popup .ilab-popup-body .ilab-popup-close:before{transform:rotate(45deg)}.ilab-popup .ilab-popup-body .ilab-popup-close:after{transform:rotate(-45deg)}.ilab-popup.hidden{pointer-events:none;opacity:0}.mcloud-inline-help-container{position:fixed;left:0;top:0;right:0;bottom:0;z-index:100002;transition:opacity .25s linear}.mcloud-inline-help-container .mcloud-inline-help{background-color:#fff;position:absolute;width:375px;height:425px;box-shadow:0 0 10px 1px rgba(0,0,0,.25);border-radius:8px;transform-origin:left center;transition:transform .25s ease-out}.mcloud-inline-help-container .mcloud-inline-help .mcloud-inline-help-arrow{right:100%;top:50%;content:" ";height:0;width:0;position:absolute;pointer-events:none;border:10px solid hsla(0,0%,100%,0);border-right-color:#fff;margin-top:-10px}.mcloud-inline-help-container .mcloud-inline-help .mcloud-inline-help-body{box-sizing:border-box;position:absolute;left:15px;top:15px;right:7.5px;bottom:15px;padding-right:15px;overflow:auto}.mcloud-inline-help-container.mcloud-invisible{opacity:0;pointer-events:none}.mcloud-inline-help-container.mcloud-invisible .mcloud-inline-help{transform:scale(.8)}.mcloud-sidebar-help-container{position:fixed;left:0;top:0;right:0;bottom:0;z-index:1000001}.mcloud-sidebar-help-container .mcloud-sidebar-help{position:absolute;right:0;top:0;bottom:0;width:450px;transition:transform .25s linear;background-color:#fff;box-shadow:0 0 10px 1px rgba(0,0,0,.25)}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-help-body{box-sizing:border-box;position:absolute;left:15px;top:0;right:7.5px;bottom:0;padding-top:15px;padding-right:22.5px;overflow:auto}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-help-body figure{margin-left:0;margin-right:0;padding-left:0;padding-right:0}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-help-body figure img{width:100%;height:auto}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-help-body div.code-block{overflow-x:auto}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-close{display:block;position:absolute;right:10px;top:10px;font-size:0;line-height:0;width:14px;height:14px}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-close:before{position:absolute;content:"";width:14px;height:2px;background-color:#aaa;transform:translateX(-50%) rotate(-45deg);left:50%;top:50%}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-close:after{position:absolute;content:"";width:14px;height:2px;background-color:#aaa;transform:translateX(-50%) rotate(45deg);left:50%;top:50%}.mcloud-sidebar-help-container.mcloud-invisible{pointer-events:none}.mcloud-sidebar-help-container.mcloud-invisible .mcloud-sidebar-help{transform:translateX(100%)}body.modal-open #beacon-container{display:none!important}.BeaconContainer{right:10px!important;bottom:88px!important}.BeaconFabButtonFrame{right:10px!important;bottom:10px!important}.section-jumps{display:flex;align-items:center;justify-content:center;margin-top:30px;margin-bottom:35px}.section-jumps span.label{color:#777;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:10px;margin-right:20px;margin-top:2px}.section-jumps a,.section-jumps span.label{display:block;line-height:1}.section-jumps span.sep{margin-left:10px;margin-right:10px;color:#777;font-weight:700;font-size:11px}.section-submit{display:flex;justify-content:center;border:1px solid #eaeaea;background-color:rgba(0,0,0,.04);width:100%;border-radius:6px;align-items:center;padding:12px 0;margin-top:20px}.section-submit p{margin:0;padding:0}.wizard-container{position:fixed;left:0;top:0;width:100%;height:100%;background-color:#000;z-index:100000;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:opacity .333s linear}.wizard-container *{font-family:SF Pro Text,SFProText,system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif}.wizard-container a{text-decoration:none}.wizard-container a:focus{outline:none;box-shadow:none}.wizard-container .wizard-modal{position:relative;width:87.8048780488vw;height:51.2195121951vw;transition:transform .333s linear,opacity .333s linear}@media (min-width:102.5em){.wizard-container .wizard-modal{width:1440px}}@media (max-width:48.9275em){.wizard-container .wizard-modal{width:94.5083014049vw}}@media (min-width:102.5em){.wizard-container .wizard-modal{height:840px}}@media (max-width:48.9275em){.wizard-container .wizard-modal{height:81.7369093231vw}}.wizard-container .wizard-modal div.steps-background{position:absolute;left:calc(100% - 320px);top:-100vh;width:100vw;height:300vh;background-color:rgba(58,86,116,.5);transition:transform .25s linear,opacity .25s linear}@media (max-width:48.9275em){.wizard-container .wizard-modal div.steps-background{left:calc(100% - 26.81992vw)}}@media (min-width:48.9375em) and (max-width:102.49em){.wizard-container .wizard-modal div.steps-background{left:calc(100% - 19.5122vw)}}.wizard-container .wizard-modal a.close-modal{position:absolute;left:.6097560976vw;top:.6097560976vw;width:1.7073170732vw;height:1.7073170732vw;background-image:url(../img/wizard-close-modal.svg);background-position:50%;background-repeat:no-repeat;background-size:contain;font-size:0;line-height:0}@media (min-width:102.5em){.wizard-container .wizard-modal a.close-modal{left:10px}}@media (max-width:48.9275em){.wizard-container .wizard-modal a.close-modal{left:1.2771392082vw}}@media (min-width:102.5em){.wizard-container .wizard-modal a.close-modal{top:10px}}@media (max-width:48.9275em){.wizard-container .wizard-modal a.close-modal{top:1.2771392082vw}}@media (min-width:102.5em){.wizard-container .wizard-modal a.close-modal{width:28px}}@media (max-width:48.9275em){.wizard-container .wizard-modal a.close-modal{width:3.5759897829vw}}@media (min-width:102.5em){.wizard-container .wizard-modal a.close-modal{height:28px}}@media (max-width:48.9275em){.wizard-container .wizard-modal a.close-modal{height:3.5759897829vw}}.wizard-content{position:absolute;left:0;top:0;right:0;bottom:0;font-size:.9756097561vw;border-radius:.7317073171vw;overflow:hidden;background-color:#fff;display:flex;flex-direction:column}@media (min-width:102.5em){.wizard-content{font-size:16px}}@media (max-width:48.9275em){.wizard-content{font-size:1.7879948914vw}}@media (min-width:102.5em){.wizard-content{border-radius:12px}}@media (max-width:48.9275em){.wizard-content{border-radius:1.5325670498vw}}.wizard-content div.sections{flex:1;position:relative;overflow:hidden}.wizard-content div.sections div.wizard-section{position:absolute;left:0;right:0;top:0;bottom:0;transform:translateX(87.8048780488vw);transition:transform .25s linear,opacity .25s linear,filter .25s linear,-webkit-filter .25s linear;overflow-x:hidden;opacity:0}.wizard-content div.sections div.wizard-section.current{opacity:1;transform:translateX(0)}.wizard-content div.sections div.wizard-section.past{transform:translateX(-87.8048780488vw)}@media (min-width:102.5em){.wizard-content div.sections div.wizard-section{transform:translateX(1440px)}.wizard-content div.sections div.wizard-section.past{transform:translateX(-1440px)}}.wizard-content div.sections div.wizard-section div.wizard-step{position:absolute;left:0;right:0;top:0;bottom:0;transform:translateX(100%);transition:transform .25s linear,opacity .25s linear;opacity:0}.wizard-content div.sections div.wizard-section div.wizard-step.current{opacity:1;transform:translateX(0)}.wizard-content div.sections div.wizard-section div.wizard-step.past{transform:translateX(-100%)}.wizard-content div.sections div.wizard-section[data-display-steps=true]{max-width:68.29268vw}@media (max-width:48.9275em){.wizard-content div.sections div.wizard-section[data-display-steps=true]{max-width:67.68838vw}}@media (min-width:102.5em){.wizard-content div.sections div.wizard-section[data-display-steps=true]{max-width:1120px}}.wizard-content div.sections div.wizard-section.section-tutorial[data-display-steps=true]{max-width:66.46341vw}@media (max-width:48.9275em){.wizard-content div.sections div.wizard-section.section-tutorial[data-display-steps=true]{max-width:61.30268vw}}@media (min-width:102.5em){.wizard-content div.sections div.wizard-section.section-tutorial[data-display-steps=true]{max-width:1090px}}.wizard-content div.steps{position:absolute;right:0;top:0;bottom:0;width:19.512195122vw;background-color:#3a5674;padding-top:2.9268292683vw;background-image:url(../img/wizard-steps-bg.svg);background-repeat:no-repeat;background-position:bottom;background-size:19.512195122vw;transition:transform .25s linear,opacity .25s linear}@media (min-width:102.5em){.wizard-content div.steps{width:320px}}@media (max-width:48.9275em){.wizard-content div.steps{width:26.8199233716vw}}@media (min-width:102.5em){.wizard-content div.steps{padding-top:48px}}@media (max-width:48.9275em){.wizard-content div.steps{padding-top:4.0868454662vw}}@media (min-width:102.5em){.wizard-content div.steps{background-size:320px}}@media (max-width:48.9275em){.wizard-content div.steps{background-size:26.8199233716vw}}.wizard-content div.steps ul{padding:0;margin:0}.wizard-content div.steps ul li{display:flex;align-items:flex-start;margin:0 0 2.9268292683vw;padding:0 1.4634146341vw 0 0;perspective:1000px}@media (min-width:102.5em){.wizard-content div.steps ul li{margin-bottom:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li{margin-bottom:3.0651340996vw}}@media (min-width:102.5em){.wizard-content div.steps ul li{padding-right:24px}}@media (max-width:48.9275em){.wizard-content div.steps ul li{padding-right:1.5325670498vw}}.wizard-content div.steps ul li input[type=checkbox]{display:none}.wizard-content div.steps ul li div.step-number{position:relative;width:3.9024390244vw;min-width:3.9024390244vw;max-width:3.9024390244vw;height:3.9024390244vw;min-height:3.9024390244vw;max-height:3.9024390244vw;margin-top:-.487804878vw;display:flex;align-items:center;justify-content:center;transform:translateX(-50%);transform-style:preserve-3d;transition:transform .5s linear}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{width:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{width:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{min-width:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{min-width:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{max-width:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{max-width:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{height:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{height:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{min-height:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{min-height:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{max-height:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{max-height:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{margin-top:-8px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{margin-top:-1.0217113665vw}}.wizard-content div.steps ul li div.step-number span{position:absolute;left:.487804878vw;top:.487804878vw;width:2.9268292683vw;min-width:2.9268292683vw;max-width:2.9268292683vw;height:2.9268292683vw;min-height:2.9268292683vw;max-height:2.9268292683vw;border-radius:2.9268292683vw;border:.0609756098vw solid #e6e6e6;background-color:#fff;color:#50ade2;display:flex;align-items:center;justify-content:center;transition:border-width .25s linear,border-color .25s linear,transform .25s linear;-webkit-backface-visibility:hidden;backface-visibility:hidden}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{left:8px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{left:1.0217113665vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{top:8px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{top:1.0217113665vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{width:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{width:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{min-width:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{min-width:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{max-width:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{max-width:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{height:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{height:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{min-height:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{min-height:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{max-height:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{max-height:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{border-radius:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{border-radius:6.1302681992vw}}.wizard-content div.steps ul li div.step-number span.back{transform:rotateY(180deg)}.wizard-content div.steps ul li div.step-number span.back img{width:.9756097561vw;min-width:.9756097561vw;max-width:.9756097561vw;height:auto}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span.back img{width:16px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span.back img{width:2.0434227331vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span.back img{min-width:16px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span.back img{min-width:2.0434227331vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span.back img{max-width:16px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span.back img{max-width:2.0434227331vw}}.wizard-content div.steps ul li.current div.step-number span{background:linear-gradient(135.29deg,#62c5f1 7.95%,#50ade2 101.07%);color:#fff;border:.487804878vw solid #fff;transform:translate(-12.5%,-12.5%)}.wizard-content div.steps ul li.current div.step-number span.back{transform:translate(-12.5%,-12.5%) rotateY(180deg)}@media (min-width:102.5em){.wizard-content div.steps ul li.current div.step-number span{border:8px solid #fff}}.wizard-content div.steps ul li.current div.description h3{color:#fff}.wizard-content div.steps ul li.complete div.step-number{transform:translateX(-50%) rotateY(180deg)}.wizard-content div.steps ul li.complete div.step-number span{background:linear-gradient(135.29deg,#62c5f1 7.95%,#50ade2 101.07%);color:#fff;border:0 solid hsla(0,0%,100%,0)}.wizard-content div.steps ul li div.description{margin-left:-.487804878vw}@media (min-width:102.5em){.wizard-content div.steps ul li div.description{margin-left:-8px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.description{margin-left:-2.5542784163vw}}.wizard-content div.steps ul li div.description h3{padding:0;color:hsla(0,0%,100%,.5);font-weight:700;font-size:1em;line-height:1.5em;margin:.7317073171vw 0 .487804878vw;transition:margin-top .25s linear}@media (min-width:102.5em){.wizard-content div.steps ul li div.description h3{margin-top:12px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.description h3{margin-top:1.5325670498vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.description h3{margin-bottom:8px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.description h3{margin-bottom:1.0217113665vw}}.wizard-content div.steps ul li div.description div.description-container{max-height:0;overflow:hidden;transition:max-height .25s linear}.wizard-content div.steps ul li div.description div.description-container p{opacity:0;margin:0;padding:0;font-size:.875em;color:hsla(0,0%,100%,.7);line-height:1.5em;transition:opacity .25s linear}.wizard-content div.steps ul li input[type=checkbox]:checked+div.description h3{margin-top:0}.wizard-content div.steps ul li input[type=checkbox]:checked+div.description div.description-container{max-height:8.5365853659vw}@media (min-width:102.5em){.wizard-content div.steps ul li input[type=checkbox]:checked+div.description div.description-container{max-height:140px}}@media (max-width:48.9275em){.wizard-content div.steps ul li input[type=checkbox]:checked+div.description div.description-container{max-height:17.8799489144vw}}.wizard-content div.steps ul li input[type=checkbox]:checked+div.description div.description-container p{opacity:1}.wizard-content footer{display:flex;height:5.8536585366vw;margin-right:19.512195122vw;padding:0 7.3170731707vw;align-items:center;justify-content:space-between;border-top:1px solid #e6e6e6;transition:margin-right .25s linear}@media (min-width:102.5em){.wizard-content footer{height:96px}}@media (max-width:48.9275em){.wizard-content footer{height:12.2605363985vw}}@media (min-width:102.5em){.wizard-content footer{margin-right:320px}}@media (max-width:48.9275em){.wizard-content footer{margin-right:26.8199233716vw}}@media (min-width:102.5em){.wizard-content footer{padding-bottom:0}}@media (max-width:48.9275em){.wizard-content footer{padding-bottom:0}}@media (min-width:102.5em){.wizard-content footer{padding-top:0}}@media (max-width:48.9275em){.wizard-content footer{padding-top:0}}@media (min-width:102.5em){.wizard-content footer{padding-left:120px}}@media (max-width:48.9275em){.wizard-content footer{padding-left:7.662835249vw}}@media (min-width:102.5em){.wizard-content footer{padding-right:120px}}@media (max-width:48.9275em){.wizard-content footer{padding-right:7.662835249vw}}.wizard-content footer img.logo{width:3.9024390244vw;height:auto}@media (min-width:102.5em){.wizard-content footer img.logo{width:64px}}@media (max-width:48.9275em){.wizard-content footer img.logo{width:8.1736909323vw}}.wizard-content footer a{font-style:normal;font-weight:500;font-size:1em;display:flex;align-items:center;justify-content:center;letter-spacing:.0457317073vw;text-transform:uppercase;text-decoration:none;color:#50abe0;transition:opacity .25s linear,background .25s linear}@media (min-width:102.5em){.wizard-content footer a{letter-spacing:.75px}}@media (max-width:48.9275em){.wizard-content footer a{letter-spacing:.0957854406vw}}.wizard-content footer a.disabled{color:#b3b3b3;pointer-events:none}.wizard-content footer a.invisible{opacity:0;pointer-events:none}.wizard-content footer nav{display:flex}.wizard-content footer nav a{margin-left:.6097560976vw;padding:.9146341463vw 2.1341463415vw}@media (min-width:102.5em){.wizard-content footer nav a{margin-left:10px}}@media (max-width:48.9275em){.wizard-content footer nav a{margin-left:1.2771392082vw}}@media (min-width:102.5em){.wizard-content footer nav a{padding-bottom:15px}}@media (max-width:48.9275em){.wizard-content footer nav a{padding-bottom:1.1494252874vw}}@media (min-width:102.5em){.wizard-content footer nav a{padding-top:15px}}@media (max-width:48.9275em){.wizard-content footer nav a{padding-top:1.1494252874vw}}@media (min-width:102.5em){.wizard-content footer nav a{padding-left:35px}}@media (max-width:48.9275em){.wizard-content footer nav a{padding-left:3.0651340996vw}}@media (min-width:102.5em){.wizard-content footer nav a{padding-right:35px}}@media (max-width:48.9275em){.wizard-content footer nav a{padding-right:3.0651340996vw}}.wizard-content footer nav a.hidden{display:none}.wizard-content footer nav a.next,.wizard-content footer nav a.return{color:#fff;border-radius:6.0975609756vw;background:linear-gradient(180deg,#62c5f1,#50ade2)}@media (min-width:102.5em){.wizard-content footer nav a.next,.wizard-content footer nav a.return{border-radius:100px}}@media (max-width:48.9275em){.wizard-content footer nav a.next,.wizard-content footer nav a.return{border-radius:6.3856960409vw}}.wizard-content footer nav a.next.disabled,.wizard-content footer nav a.return.disabled{background:linear-gradient(180deg,#f0f0f0,#e0e0e0)}.wizard-step{padding:0 7.3170731707vw;display:flex;flex-direction:column;justify-content:center;flex:1}@media (min-width:102.5em){.wizard-step{padding-bottom:0}}@media (max-width:48.9275em){.wizard-step{padding-bottom:0}}@media (min-width:102.5em){.wizard-step{padding-top:0}}@media (max-width:48.9275em){.wizard-step{padding-top:0}}@media (min-width:102.5em){.wizard-step{padding-left:120px}}@media (max-width:48.9275em){.wizard-step{padding-left:7.662835249vw}}@media (min-width:102.5em){.wizard-step{padding-right:120px}}@media (max-width:48.9275em){.wizard-step{padding-right:7.662835249vw}}.wizard-step .intro{margin-bottom:3.6585365854vw}.wizard-step .intro h1{line-height:1.2;margin-bottom:2.4390243902vw}@media (min-width:102.5em){.wizard-step .intro h1{margin-bottom:40px}}@media (max-width:48.9275em){.wizard-step .intro h1{margin-bottom:2.5542784163vw}}@media (min-width:102.5em){.wizard-step .intro{margin-bottom:60px}}@media (max-width:48.9275em){.wizard-step .intro{margin-bottom:3.8314176245vw}}.wizard-step .intro p{padding:0;margin:0 0 1.0975609756vw;font-size:1.125em;text-align:left}@media (min-width:102.5em){.wizard-step .intro p{margin-bottom:18px}}@media (max-width:48.9275em){.wizard-step .intro p{margin-bottom:1.1494252874vw}}.wizard-step .intro p:last-of-type{margin-bottom:0}div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding:0 5.487804878vw 0 7.3170731707vw}@media (min-width:102.5em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-left:120px}}@media (max-width:48.9275em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-left:7.662835249vw}}@media (min-width:102.5em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-top:0}}@media (max-width:48.9275em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-top:0}}@media (min-width:102.5em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-right:90px}}@media (max-width:48.9275em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-right:0}}@media (min-width:102.5em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-bottom:0}}@media (max-width:48.9275em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-bottom:0}}.wizard-step-select div.step-contents{margin-bottom:2.4390243902vw}@media (min-width:102.5em){.wizard-step-select div.step-contents{margin-bottom:40px}}@media (max-width:48.9275em){.wizard-step-select div.step-contents{margin-bottom:2.5542784163vw}}.wizard-step-select div.step-contents:last-of-type{margin-bottom:0}.wizard-step-select .intro{text-align:center}.wizard-step-select ul{display:flex;flex-wrap:wrap;padding:0;margin:0;justify-content:center;align-items:center}.wizard-step-select ul li{position:relative;display:block;padding:0;margin:1.8292682927vw 2.4390243902vw}@media (min-width:102.5em){.wizard-step-select ul li{margin-top:30px}}@media (max-width:48.9275em){.wizard-step-select ul li{margin-top:1.9157088123vw}}@media (min-width:102.5em){.wizard-step-select ul li{margin-bottom:30px}}@media (max-width:48.9275em){.wizard-step-select ul li{margin-bottom:1.9157088123vw}}@media (min-width:102.5em){.wizard-step-select ul li{margin-left:40px}}@media (max-width:48.9275em){.wizard-step-select ul li{margin-left:5.1085568327vw}}@media (min-width:102.5em){.wizard-step-select ul li{margin-right:40px}}@media (max-width:48.9275em){.wizard-step-select ul li{margin-right:5.1085568327vw}}.wizard-step-select ul li div.description{position:absolute;left:50%;transform:translate(-50%,24px) scale(.7);bottom:calc(100% + 30px);padding:1.4634146341vw;background-color:#3a5674;color:#fff;width:17.6829268293vw;display:flex;flex-direction:column;align-items:center;justify-content:center;border-radius:10px;box-shadow:0 0 10px 1px rgba(0,0,0,.25);opacity:0;pointer-events:none;transition:transform .125s linear,opacity .125s linear}@media (min-width:102.5em){.wizard-step-select ul li div.description{padding:24px}}@media (max-width:48.9275em){.wizard-step-select ul li div.description{padding:1.5325670498vw}}@media (min-width:102.5em){.wizard-step-select ul li div.description{width:290px}}@media (max-width:48.9275em){.wizard-step-select ul li div.description{width:32.5670498084vw}}.wizard-step-select ul li div.description div.arrow-down{position:absolute;bottom:-13px;width:0;height:0;left:calc(50% - 14px);border-left:14px solid transparent;border-right:14px solid transparent;border-top:14px solid #3a5674}.wizard-step-select ul li:hover div.description{opacity:1;transform:translate(-50%) scale(1)}ul.options.select-icons li:hover a img{transform:scale(1.2)}ul.options.select-icons li a{font-size:0}ul.options.select-icons li a img{transition:transform .2s linear;height:2.9268292683vw;width:auto}@media (min-width:102.5em){ul.options.select-icons li a img{height:48px}}@media (max-width:48.9275em){ul.options.select-icons li a img{height:3.0651340996vw}}ul.options.select-icons li a.select-s3 img{height:3.6585365854vw}@media (min-width:102.5em){ul.options.select-icons li a.select-s3 img{height:60px}}@media (max-width:48.9275em){ul.options.select-icons li a.select-s3 img{height:3.8314176245vw}}ul.options.select-buttons li{margin:1.8292682927vw .9146341463vw}@media (min-width:102.5em){ul.options.select-buttons li{margin-top:30px}}@media (max-width:48.9275em){ul.options.select-buttons li{margin-top:1.9157088123vw}}@media (min-width:102.5em){ul.options.select-buttons li{margin-bottom:30px}}@media (max-width:48.9275em){ul.options.select-buttons li{margin-bottom:1.9157088123vw}}@media (min-width:102.5em){ul.options.select-buttons li{margin-left:15px}}@media (max-width:48.9275em){ul.options.select-buttons li{margin-left:1.0217113665vw}}@media (min-width:102.5em){ul.options.select-buttons li{margin-right:15px}}@media (max-width:48.9275em){ul.options.select-buttons li{margin-right:1.0217113665vw}}ul.options.select-buttons li a{font-style:normal;font-weight:500;font-size:1em;display:flex;align-items:center;justify-content:center;letter-spacing:.0457317073vw;text-transform:uppercase;text-decoration:none;color:#fff;border-radius:6.0975609756vw;padding:.9146341463vw 2.1341463415vw;background:linear-gradient(180deg,#62c5f1,#50ade2)}@media (min-width:102.5em){ul.options.select-buttons li a{letter-spacing:.75px;padding:15px 35px}}ul.options.select-flat-buttons li{margin:1.8292682927vw .9146341463vw}@media (min-width:102.5em){ul.options.select-flat-buttons li{margin-top:30px}}@media (max-width:48.9275em){ul.options.select-flat-buttons li{margin-top:1.9157088123vw}}@media (min-width:102.5em){ul.options.select-flat-buttons li{margin-bottom:30px}}@media (max-width:48.9275em){ul.options.select-flat-buttons li{margin-bottom:1.9157088123vw}}@media (min-width:102.5em){ul.options.select-flat-buttons li{margin-left:15px}}@media (max-width:48.9275em){ul.options.select-flat-buttons li{margin-left:1.0217113665vw}}@media (min-width:102.5em){ul.options.select-flat-buttons li{margin-right:15px}}@media (max-width:48.9275em){ul.options.select-flat-buttons li{margin-right:1.0217113665vw}}ul.options.select-flat-buttons li a{font-style:normal;font-weight:500;font-size:1.5em;letter-spacing:.0457317073vw;text-transform:uppercase;text-decoration:none;border-bottom:1px dotted #50ade2;color:#50ade2}@media (min-width:102.5em){ul.options.select-flat-buttons li a{letter-spacing:.75px}}.wizard-step-video{padding:0}.wizard-step-video .step-contents .video,.wizard-step-video .step-contents .video iframe{position:absolute;top:0;left:0;width:100%;height:100%}@-webkit-keyframes logo-rotate{0%{transform:rotateY(0deg)}to{transform:rotateY(-1turn)}}@keyframes logo-rotate{0%{transform:rotateY(0deg)}to{transform:rotateY(-1turn)}}@-webkit-keyframes logo-rotate-x{0%{transform:rotateX(0deg)}to{transform:rotateX(-1turn)}}@keyframes logo-rotate-x{0%{transform:rotateX(0deg)}to{transform:rotateX(-1turn)}}@-webkit-keyframes logo-rotate-z{0%{transform:rotate(-1turn)}to{transform:rotate(0deg)}}@keyframes logo-rotate-z{0%{transform:rotate(-1turn)}to{transform:rotate(0deg)}}.wizard-step-form div.intro{margin-bottom:1.8292682927vw}@media (min-width:102.5em){.wizard-step-form div.intro{margin-bottom:30px}}@media (max-width:48.9275em){.wizard-step-form div.intro{margin-bottom:1.9157088123vw}}.wizard-step-form form{display:flex;flex-direction:column}.wizard-step-form form div.form-field{display:flex;flex-direction:column;border:1px solid #f3f3f3;background-color:#f3f3f3;padding:1.2195121951vw;border-radius:.9756097561vw;margin-bottom:1.2195121951vw}@media (min-width:102.5em){.wizard-step-form form div.form-field{padding:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field{padding:1.2771392082vw}}@media (min-width:102.5em){.wizard-step-form form div.form-field{border-radius:16px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field{border-radius:1.0217113665vw}}@media (min-width:102.5em){.wizard-step-form form div.form-field{margin-bottom:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field{margin-bottom:1.2771392082vw}}.wizard-step-form form div.form-field:last-of-type{margin-bottom:0}.wizard-step-form form div.form-field:focus-within{border:1px solid #50ade2}.wizard-step-form form div.form-field label{font-weight:500;font-size:.75em;line-height:1em;text-transform:uppercase;color:#3a5674;margin-bottom:.487804878vw}@media (min-width:102.5em){.wizard-step-form form div.form-field label{margin-bottom:8px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field label{margin-bottom:.5108556833vw}}.wizard-step-form form div.form-field input[type=password],.wizard-step-form form div.form-field input[type=text]{box-shadow:none;padding:0;border:0;background:none;font-size:1.125em}.wizard-step-form form div.form-field input[type=password]::-webkit-input-placeholder,.wizard-step-form form div.form-field input[type=text]::-webkit-input-placeholder{color:rgba(0,0,0,.125)}.wizard-step-form form div.form-field input[type=password]::-moz-placeholder,.wizard-step-form form div.form-field input[type=text]::-moz-placeholder{color:rgba(0,0,0,.125)}.wizard-step-form form div.form-field input[type=password]:-ms-input-placeholder,.wizard-step-form form div.form-field input[type=text]:-ms-input-placeholder{color:rgba(0,0,0,.125)}.wizard-step-form form div.form-field input[type=password]::-ms-input-placeholder,.wizard-step-form form div.form-field input[type=text]::-ms-input-placeholder{color:rgba(0,0,0,.125)}.wizard-step-form form div.form-field input[type=password]::placeholder,.wizard-step-form form div.form-field input[type=text]::placeholder{color:rgba(0,0,0,.125)}.wizard-step-form form div.form-field select{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;padding:0;background:transparent;outline:0;font-size:1.125em}.wizard-step-form form div.form-field select:focus{outline:0}.wizard-step-form form div.form-field.field-checkbox{background:none;flex-direction:row;align-items:flex-start;padding:1.2195121951vw 0;border:1px solid #fff}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox{padding-bottom:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox{padding-bottom:1.2771392082vw}}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox{padding-top:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox{padding-top:1.2771392082vw}}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox{padding-left:0}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox{padding-left:0}}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox{padding-right:0}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox{padding-right:0}}.wizard-step-form form div.form-field.field-checkbox div.checkbox{margin-right:1.2195121951vw}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox div.checkbox{margin-right:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox div.checkbox{margin-right:1.2771392082vw}}.wizard-step-form form div.form-field.field-checkbox div.title{padding-top:.3048780488vw;margin-right:1.2195121951vw;white-space:nowrap;font-size:1em;font-weight:500}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox div.title{padding-top:5px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox div.title{padding-top:.6385696041vw}}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox div.title{margin-right:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox div.title{margin-right:1.2771392082vw}}.wizard-step-form form div.form-field.field-checkbox div.description{padding-top:.3048780488vw;font-size:1em;font-weight:300}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox div.description{padding-top:5px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox div.description{padding-top:.6385696041vw;display:none}}.wizard-step-form .progress{position:absolute;left:0;right:0;bottom:0;top:0;display:flex;flex-direction:column;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .25s linear;perspective:100em}.wizard-step-form .progress h3{color:#50abe0;margin-bottom:3.0487804878vw;font-size:1.625em}@media (min-width:102.5em){.wizard-step-form .progress h3{margin-bottom:50px}}.wizard-step-form .progress div.logo-spinner{-webkit-animation:logo-rotate 2s;animation:logo-rotate 2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.wizard-step-form .progress div.logo-spinner img{width:7.3170731707vw;height:auto}@media (min-width:102.5em){.wizard-step-form .progress div.logo-spinner img{width:120px}}.wizard-step-form.processing .progress{opacity:1}.wizard-step-form.processing div.step-contents{-webkit-filter:blur(5px);filter:blur(5px)}.wizard-step-test{justify-content:flex-start}.wizard-step-test div.step-contents{margin-top:3.0487804878vw}@media (min-width:102.5em){.wizard-step-test div.step-contents{margin-top:50px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents{margin-top:6.3856960409vw}}.wizard-step-test div.step-contents div.intro{margin-bottom:1.8292682927vw}@media (min-width:102.5em){.wizard-step-test div.step-contents div.intro{margin-bottom:30px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents div.intro{margin-bottom:1.9157088123vw}}.wizard-step-test div.step-contents div.start-buttons{display:flex;justify-content:center;margin-bottom:1.8292682927vw}@media (max-width:48.9275em){.wizard-step-test div.step-contents div.start-buttons{margin-bottom:1.9157088123vw}}.wizard-step-test div.step-contents div.start-buttons a{font-style:normal;font-weight:500;font-size:1em;display:flex;align-items:center;justify-content:center;letter-spacing:.0457317073vw;text-transform:uppercase;text-decoration:none;color:#fff;border-radius:6.0975609756vw;padding:.9146341463vw 2.1341463415vw;background:linear-gradient(180deg,#62c5f1,#50ade2)}@media (min-width:102.5em){.wizard-step-test div.step-contents div.start-buttons a{letter-spacing:.75px;padding:15px 35px}}@media (min-width:102.5em){.wizard-step-test div.step-contents div.start-buttons{margin-bottom:30px}}.wizard-step-test div.step-contents ul.tests>li{border:1px solid #f3f3f3;background-color:#f3f3f3;border-radius:.9756097561vw;padding:1.2195121951vw;margin-bottom:20xp;display:flex;transition:opacity .25s linear}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li{border-radius:16px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li{border-radius:1.0217113665vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li{padding:20px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li{padding:1.2771392082vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li{margin-bottom:20xp}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li{margin-bottom:1.2771392082vw}}.wizard-step-test div.step-contents ul.tests>li.hidden{opacity:0}.wizard-step-test div.step-contents ul.tests>li div.icon{width:1.4634146341vw;min-width:1.4634146341vw;max-width:1.4634146341vw;height:1.4634146341vw;min-height:1.4634146341vw;max-height:1.4634146341vw;display:flex;justify-content:center;align-items:center;margin-right:.6097560976vw}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{width:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{width:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{min-width:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{min-width:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{max-width:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{max-width:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{height:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{height:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{min-height:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{min-height:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{max-height:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{max-height:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{margin-right:10px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{margin-right:1.2771392082vw}}.wizard-step-test div.step-contents ul.tests>li div.icon img{display:none;width:100%;height:auto}.wizard-step-test div.step-contents ul.tests>li.waiting div.icon{perspective:100em}.wizard-step-test div.step-contents ul.tests>li.waiting div.icon img.waiting{display:block;-webkit-animation:logo-rotate-z 1s;animation:logo-rotate-z 1s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.wizard-step-test div.step-contents ul.tests>li.error>div.icon>img.error,.wizard-step-test div.step-contents ul.tests>li.success>div.icon>img.success,.wizard-step-test div.step-contents ul.tests>li.warning>div.icon>img.warning{display:block}.wizard-step-test div.step-contents ul.tests>li div.description h3{margin:.243902439vw 0;padding:0;font-size:1.125em}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-top:4px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-top:0}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-bottom:4px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-bottom:0}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-left:0}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-left:0}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-right:0}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-right:0}}.wizard-step-test div.step-contents ul.tests>li div.description p{margin:0 0 4px;padding:0;font-size:1em}.wizard-step-test div.step-contents ul.tests>li div.description ul.errors{margin-left:1.2195121951vw;list-style:disc}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.description ul.errors{margin-left:20px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.description ul.errors{margin-left:1.2771392082vw}}.wizard-step-test div.step-contents ul.tests>li div.description ul.errors li{display:list-item;font-size:1em}.wizard-step-tutorial{justify-content:flex-start}.wizard-step-tutorial div.tutorial{padding-top:2.4390243902vw;margin-bottom:60px}@media (min-width:102.5em){.wizard-step-tutorial div.tutorial{padding-top:40px}}@media (max-width:48.9275em){.wizard-step-tutorial div.tutorial{padding-top:5.1085568327vw}}.wizard-step-tutorial div.tutorial h2,.wizard-step-tutorial div.tutorial h3{margin-top:2.4390243902vw}@media (min-width:102.5em){.wizard-step-tutorial div.tutorial h2,.wizard-step-tutorial div.tutorial h3{margin-top:40px}}@media (max-width:48.9275em){.wizard-step-tutorial div.tutorial h2,.wizard-step-tutorial div.tutorial h3{margin-top:2.5542784163vw}}.wizard-step-tutorial div.tutorial p{padding:0;margin:0 0 .6097560976vw;font-size:1.125em}@media (min-width:102.5em){.wizard-step-tutorial div.tutorial p{margin-bottom:10px}}@media (max-width:48.9275em){.wizard-step-tutorial div.tutorial p{margin-bottom:1.2771392082vw}}.wizard-step-tutorial div.tutorial p:last-of-type{margin-bottom:0}.wizard-step-tutorial div.tutorial figure{padding:0;margin:2.4390243902vw 0}@media (min-width:102.5em){.wizard-step-tutorial div.tutorial figure{margin-top:40px}}@media (max-width:48.9275em){.wizard-step-tutorial div.tutorial figure{margin-top:2.5542784163vw}}@media (min-width:102.5em){.wizard-step-tutorial div.tutorial figure{margin-bottom:40px}}@media (max-width:48.9275em){.wizard-step-tutorial div.tutorial figure{margin-bottom:2.5542784163vw}}.wizard-step-tutorial div.tutorial figure img{width:100%;height:auto}.wizard-step-tutorial div.tutorial ul{margin-left:20px;margin-bottom:30px;list-style:disc}.wizard-step-tutorial div.tutorial ul li{display:list-item}.wizard-modal.no-steps div.steps-background{opacity:0}.wizard-modal.no-steps div.wizard-content div.steps{transform:translateX(calc(100% + 1.95122vw));opacity:0}@media (min-width:102.5em){.wizard-modal.no-steps div.wizard-content div.steps{transform:translateX(calc(100% + 32px))}}.wizard-modal.no-steps div.wizard-content footer{margin-right:0}.wizard-modal.no-animations *{transition:none!important}.wizard-invisible .wizard-modal{transform:scale(.8);opacity:0}#s3-importer-progress{padding:24px;background:#ddd;border-radius:8px}#s3-importer-progress .button-whoa{background:#a42929!important;border-color:#e62a2a #a42929 #a42929!important;box-shadow:0 1px 0 #a42929!important;color:#fff!important;text-decoration:none!important;text-shadow:0 -1px 1px #a42929,1px 0 1px #a42929,0 1px 1px #a42929,-1px 0 1px #a42929!important}#s3-importer-progress>button{margin-top:20px}.s3-importer-progress-container{position:relative;width:100%;height:32px;background:#aaa;border-radius:16px;overflow:hidden;background-image:url(../img/candy-stripe.svg)}#s3-importer-progress-bar{background-color:#4f90c4;height:100%}.tool-disabled{padding:10px 15px;border:1px solid #df8403}.force-cancel-help{margin-top:20px}.wp-cli-callout{padding:24px;background:#ddd;margin-top:20px;border-radius:8px}.wp-cli-callout>h3{margin:0;padding:0;font-size:14px}.wp-cli-callout>code{background-color:#bbb;padding:10px 15px;margin-top:5px;display:inline-block}#s3-importer-options{padding:24px;background:#e7e7e7;margin-top:20px;border-radius:8px}#s3-importer-options h3{margin:0;padding:0;font-size:14px}#s3-importer-options ul{padding:0;display:flex;flex-direction:column;margin:20px 0 0}#s3-importer-options ul li{display:flex;margin-bottom:30px}#s3-importer-options ul li:last-of-type{margin-bottom:0}#s3-importer-options ul li>div:first-of-type{padding:10px 10px 20px 0;width:160px;min-width:160px;line-height:1.3;font-weight:600}#s3-importer-options ul li div.description{margin-top:8px}#s3-importer-options ul li div.option-ui{display:flex;align-items:center}#s3-importer-options ul li div.option-ui.option-ui-browser input[type=text]{width:40vw;margin-right:10px;padding:7px 11px;border-radius:4px}#s3-importer-options ul li div.option-ui.option-ui-browser input[type=text]:disabled{color:#000}#s3-timing-stats{display:none}#s3-importer-status-text{position:absolute;left:16px;top:0;bottom:0;right:16px;display:flex;align-items:center;color:#fff;font-weight:700}#s3-importer-thumbnails{position:relative;width:100%;height:150px;margin-bottom:15px}#s3-importer-thumbnails-container{position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;-webkit-mask-image:linear-gradient(90deg,#ddd 0,hsla(0,0%,86.7%,0) 90%);mask-image:linear-gradient(90deg,#ddd 0,hsla(0,0%,86.7%,0) 90%)}#s3-importer-thumbnails-container img{width:150px;height:150px;max-width:150px;max-height:150px;border-radius:4px}#s3-importer-thumbnails-container>img{margin-right:10px}#s3-importer-thumbnails-fade{background:linear-gradient(90deg,#ddd 0,hsla(0,0%,86.7%,0) 90%);position:absolute;left:150px;top:0;right:0;bottom:0}@supports ((-webkit-mask-image:linear-gradient(to left,rgba(221,221,221,0) 0%,#dddddd 95%,#dddddd 100%)) or (mask-image:linear-gradient(to left,rgba(221,221,221,0) 0%,#dddddd 95%,#dddddd 100%))){#s3-importer-thumbnails-fade{display:none}}#s3-importer-thumbnails-cloud{position:absolute;right:20px;top:50%;transform:translateY(-50%)}.s3-importer-thumb{position:absolute;left:0;top:0;width:150px;min-width:150px;max-width:150px;height:150px;min-height:150px;max-height:150px;background-size:cover;background-position:50%;background-repeat:no-repeat;margin-right:10px;border-radius:4px;background-color:#888;transition:opacity .25s linear,transform .25s linear}.s3-importer-thumb.ilab-hidden{opacity:0;transform:scale(.7)}.s3-importer-image-icon{position:absolute;left:0;top:0;position:relative;width:150px;min-width:150px;max-width:150px;height:150px;min-height:150px;max-height:150px;display:flex;align-items:center;justify-content:center;transition:opacity .25s linear,transform .25s linear}.s3-importer-image-icon.ilab-hidden{opacity:0;transform:scale(.8)}.s3-importer-info-warning{border:1px solid orange;padding:24px;background:rgba(255,165,0,.125);margin-top:20px;border-radius:8px}.s3-importer-info-warning h4{padding:0;font-size:14px;margin:0 0 8px} \ No newline at end of file +/*! mediabox v1.1.3 | (c) 2018 Pedro Rogerio | https://github.com/pinceladasdaweb/mediabox */.stop-scroll{height:100%;overflow:hidden}.mediabox-wrap{position:fixed;width:100%;height:100%;background-color:rgba(0,0,0,.8);top:0;left:0;opacity:0;z-index:999;-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:mediabox;animation-name:mediabox}@-webkit-keyframes mediabox{0%{opacity:0}to{opacity:1}}@keyframes mediabox{0%{opacity:0}to{opacity:1}}.mediabox-content{max-width:853px;display:block;margin:0 auto;height:100%;position:relative}.mediabox-content iframe{max-width:100%!important;width:100%!important;display:block!important;height:480px!important;border:none!important;position:absolute;top:0;bottom:0;margin:auto 0}.mediabox-hide{-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:mediaboxhide;animation-name:mediaboxhide}@-webkit-keyframes mediaboxhide{0%{opacity:1}to{opacity:0}}@keyframes mediaboxhide{0%{opacity:1}to{opacity:0}}.mediabox-close{position:absolute;top:0;cursor:pointer;bottom:528px;right:0;margin:auto 0;width:24px;height:24px;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAMvSURBVHja7Js9aBRBFMd/d1lPY6FiJVjY+Fkoxl7wA1Q0prQRS6tgoZV2MWIRRVHUUq3U+JnESrS2sBXBzipREWMlATXwt8gFznC5nd15M7Nn8uC45nZnfr/dY96+N1uTxFKOOks8lgUU/H2t4tJqIQUcAiaBGeBymcECRgO4B/wBPgJ9zkdKcvkclfRL/8ZtSTXH40N+GpLGF8zth6Q9Lse7DHCsDXxVJLSDLyQhb4B+Sb/VOVJJ6ATfKqGvrIDjDvCpJLjAz8d0JwmLDTBQAD62hIakiYJzm5a021VAfwn4WBLKwLdK2JUnIJP0XX4RSoIP/Hy8W3jeepv1dL3nmjwI3DLOExrAU2DA8zwb8xKhGeCuwYQtJTSAZwbwAHdcEqFM0mPZhO/foSHppdFcrraby2IDV0FCcPi8PCClhCjwLplgCgkrDeGv5I3pcjViSogK7yogloTo8EUEhJaQBL6oAGsJ9yVtkrRD0qsU8JKolagKZ8AD4ETFymFXgPOAQpXE5mMWOAk86XZ4n6pwlSSUhvcti1dBghe8RV8gpYQRX3irxkgKCSPABV94y85QTAlm8NatsRgSTOGBUnmAS57w3KiA0Ro3gHOW8KEEAOwE3hvfXWubFauu6A6vCND07OmW9viq5vpsGT3AtRAN2XoA+BfAwQBiTweoNpMZw48BRwKuAoPN7zNVWwZjwAfpO9S7DN5cQmYAPw4cTvAsYPJ3qHcpvNmdUO9ieBMJZQT0AhMVgfeWUC8BP87cjjHfuA6sATY0c4c0EgpUUHslvTaq3l5aUL1N1oarAnxSCVWBTyYhJvyw41XJJI3GkpAH/yYyfHQJi01gdUL4qBKqCh9NQrtBx4wGvGi0XS6T9MhoTkN5AtZVDN5awlTePsGfwDfPjGwYGDKu3s4Cp4BRz/N8cskED0iaqciVt7wTvkra5roKlJEQGt5HwhdJ24vmAUUkDEV+VyCT9NBxbp/bXXnXTNBFQmz4IhI6wrs+C+zvICEVvIuEKUlbrZ4G97WRkBq+k4RJSVusd4ntlfSheVudrQh8q4SbmntH6K2kzSF3if1Xsfzq7LKAJR5/BwCdAQBJn4egPgAAAABJRU5ErkJggg==") no-repeat;background-size:24px 24px}.mediabox-close:hover{opacity:.5}@media (max-width:768px){.mediabox-content{max-width:90%}}@media (max-width:600px){.mediabox-content iframe{height:320px!important}.mediabox-close{bottom:362px}}@media (max-width:480px){.mediabox-content iframe{height:220px!important}.mediabox-close{bottom:262px}}.mediabox-wrap{z-index:1000000}.mediabox-content{max-width:75vw}.mediabox-content iframe{height:42.1875vw!important}.mediabox-close{bottom:46vw}.ic-Super-toggle__label{box-sizing:border-box;margin:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ic-Form-group.ic-Form-group--horizontal .ic-Super-toggle__label{display:inline-flex;align-items:center}.ic-Super-toggle__input{opacity:0;position:absolute}.ic-Super-toggle__input:checked~label .ic-Super-toggle-switch:after{transform:translate3d(100%,0,0)}.ic-Super-toggle__input:checked~label .ic-Super-toggle__disabled-msg:before{content:attr(data-checked)}.ic-Super-toggle__input[disabled]{opacity:0!important}.ic-Super-toggle__input[disabled]~label .ic-Super-toggle-switch{opacity:.33}.ic-Super-toggle-switch{transition:background .1s,border-color .1s;position:relative;line-height:1;display:flex;align-items:center;background-clip:padding-box}.ic-Super-toggle-switch:after{transition:all .1s ease-in-out;content:"";position:absolute;top:0;left:0;transform:translateZ(0);border-radius:100%;box-shadow:0 3px 6px rgba(0,0,0,.3);background-image:url(https://cl.ly/320m31452k2X/handle.svg);background-position:50% 50%;background-repeat:no-repeat;background-size:20px}.ic-Super-toggle__disabled-msg{display:none}.ic-Super-toggle__disabled-msg:before{content:attr(data-unchecked);font-style:italic;opacity:.8}[class^=ic-Super-toggle-option-]{transition:all .2s ease-out;flex:0 0 50%;text-align:center;position:relative;z-index:1;text-transform:uppercase;font-weight:700;line-height:1;speak:none;box-sizing:border-box}.ic-Super-toggle__screenreader{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute}.ic-Super-toggle--on-off{display:inline-block;vertical-align:middle}.ic-Super-toggle--on-off .ic-Super-toggle__input:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #5b6c79,0 3px 6px rgba(0,0,0,.3)}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-switch{background:#4cace3;border-color:#4cace3}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT{color:#fff}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#fff}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT{color:#fff}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#fff}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #4cace3,0 3px 6px rgba(0,0,0,.3)}.ic-Super-toggle--on-off .ic-Super-toggle-switch{width:50px;height:25px;background:#5b6c79;border:2px solid #5b6c79;border-radius:14.5px}.ic-Form-group.ic-Form-group--horizontal .ic-Super-toggle--on-off .ic-Super-toggle-switch{flex:0 0 50px}.ic-Super-toggle--on-off .ic-Super-toggle-switch:after{background-color:#fff;width:25px;height:25px}.ic-Super-toggle--on-off .ic-Super-toggle-option-LEFT{color:#fff}.ic-Super-toggle--on-off .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#fff}.ic-Super-toggle--on-off .ic-Super-toggle-option-RIGHT{color:#fff}.ic-Super-toggle--on-off .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#fff}.ic-Super-toggle--on-off .ic-Super-toggle__svg{max-width:12.5px;max-height:12.5px}.ic-Super-toggle--on-off [class^=ic-Super-toggle-option-]{transition-delay:.1s}.ic-Super-toggle--on-off .ic-Super-toggle-option-LEFT{transform:scale(.1);opacity:0}.ic-Super-toggle--on-off .ic-Super-toggle-option-RIGHT,.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT{transform:scale(1);opacity:1}.ic-Super-toggle--on-off .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT{transform:scale(.1);opacity:0}.toggle-warning{display:inline-block;vertical-align:middle}.toggle-warning .ic-Super-toggle__input:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #5b6c79,0 3px 6px rgba(0,0,0,.3)}.toggle-warning .ic-Super-toggle__input:checked~label .ic-Super-toggle-switch{background:#ffaa10;border-color:#ffaa10}.toggle-warning .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT{color:#fff}.toggle-warning .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#fff}.toggle-warning .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT{color:#fff}.toggle-warning .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#fff}.toggle-warning .ic-Super-toggle__input:checked:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #ffaa10,0 3px 6px rgba(0,0,0,.3)}.toggle-warning .ic-Super-toggle-switch{width:50px;height:25px;background:#5b6c79;border:2px solid #5b6c79;border-radius:14.5px}.ic-Form-group.ic-Form-group--horizontal .toggle-warning .ic-Super-toggle-switch{flex:0 0 50px}.toggle-warning .ic-Super-toggle-switch:after{background-color:#fff;width:25px;height:25px}.toggle-warning .ic-Super-toggle-option-LEFT{color:#fff}.toggle-warning .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#fff}.toggle-warning .ic-Super-toggle-option-RIGHT{color:#fff}.toggle-warning .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#fff}.toggle-warning .ic-Super-toggle__svg{max-width:12.5px;max-height:12.5px}.ic-Super-toggle--ui-switch{display:inline-block;vertical-align:middle}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #5b6c79,0 3px 6px rgba(0,0,0,.3)}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-switch{background:#5b6c79;border-color:#5b6c79}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT{color:#888}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#888}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT{color:#08c}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#08c}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked:focus~label .ic-Super-toggle-switch:after{box-shadow:inset 0 0 0 1px #fff,inset 0 0 0 3px #5b6c79,0 3px 6px rgba(0,0,0,.3)}.ic-Super-toggle--ui-switch .ic-Super-toggle-switch{width:50px;height:25px;background:#5b6c79;border:2px solid #5b6c79;border-radius:14.5px}.ic-Form-group.ic-Form-group--horizontal .ic-Super-toggle--ui-switch .ic-Super-toggle-switch{flex:0 0 50px}.ic-Super-toggle--ui-switch .ic-Super-toggle-switch:after{background-color:#fff;width:25px;height:25px}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-LEFT{color:#08c}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-LEFT .ic-Super-toggle__svg>*{fill:#08c}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-RIGHT{color:#888}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-RIGHT .ic-Super-toggle__svg>*{fill:#888}.ic-Super-toggle--ui-switch .ic-Super-toggle__svg{max-width:12.5px;max-height:12.5px}.ic-Super-toggle--ui-switch .ic-Super-toggle__label{display:inline-flex;align-items:center}.ic-Super-toggle--ui-switch .ic-Super-toggle-switch{display:block}.ic-Super-toggle--ui-switch [class^=ic-Super-toggle-option-]{flex:none;min-width:24px}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-LEFT{text-align:left;transform:scale(1.1)}.ic-Super-toggle--ui-switch .ic-Super-toggle-option-RIGHT{text-align:right;transform:scale(.9)}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-LEFT{transform:scale(.9)}.ic-Super-toggle--ui-switch .ic-Super-toggle__input:checked~label .ic-Super-toggle-option-RIGHT{transform:scale(1.1)}.settings-container{font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;margin:0 0 64px -20px}.settings-container header{position:relative;background-image:url(../img/settings-bg-large.svg);background-position:0;background-repeat:no-repeat;background-size:cover;min-height:80px;width:100%;display:flex;align-items:center}.settings-container header>img{margin:0 20px;width:88px;max-width:88px}.settings-container header h1{margin-left:5px;font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;text-transform:uppercase;font-weight:400;font-size:1.5em;color:#777}.settings-container header .header-actions{position:absolute!important;right:40px;top:50%;transform:translateY(-50%)!important;display:flex}.settings-container header .header-actions a{margin-left:8px;display:flex;align-items:center}.settings-container header .header-actions a svg{height:16px;width:auto;margin-right:8px}.settings-container header .header-actions a svg>path,.settings-container header .header-actions a svg>rect{fill:#fff}.settings-container header .header-actions div.spacer{width:8px;min-width:8px}.settings-container header.all-settings{display:flex;flex-direction:column;align-items:flex-start}.settings-container header.all-settings div.contents{height:104px;display:flex;justify-content:space-between;align-items:center;width:100%}.settings-container header.all-settings div.contents img.logo{margin:0 0 0 23px;width:108px;max-width:108px}.settings-container header.all-settings div.contents div.settings-select-container{background-color:hsla(0,0%,100%,.6);padding:10px 14px;border-radius:8px;z-index:1000;margin-right:23px;display:none}@media (max-width:992px){.settings-container header.all-settings div.contents div.settings-select-container{display:unset}}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown{display:flex;align-items:center;z-index:1000}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown>div:first-of-type{font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;margin-right:10px;color:#777;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:1em}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown{position:relative;width:200px;height:36px;z-index:1000;cursor:pointer}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.current{font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;display:flex;align-items:center;position:absolute;left:0;top:0;right:0;bottom:0;background-color:#eee;border:1px solid #ddd;padding-left:10px;color:#777;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:13px;cursor:pointer;background-image:url(../img/icon-dropdown-arrow.svg);background-repeat:no-repeat;background-position:right 12px center;transition:background-color .15s linear}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.current:hover{background-color:#fff}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items{z-index:1001;position:absolute;top:0;left:0;right:0;transition:opacity .15s linear,transform .15s linear;opacity:0;pointer-events:none}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items.visible{pointer-events:auto;opacity:1}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul{margin:0;padding:0;box-shadow:0 0 8px 1px rgba(0,0,0,.125)}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li{position:relative;margin:0;padding:0;border:1px solid #ddd;border-top:0;align-items:center}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li:first-of-type{border:1px solid #ddd}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li a.tool{position:relative;display:flex;align-items:center;height:36px;background-color:#eee;padding-left:10px;color:#777;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:13px;transition:background-color .15s linear}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li a.tool:hover{background:#fff}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li a.tool-pin{display:block;position:absolute;top:0;width:36px;height:36px;right:0;background-image:url(../img/icon-pin-deselected.svg);background-repeat:no-repeat;background-position:50%}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li a.tool-pin.pinned{background-image:url(../img/icon-pin-selected.svg)}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown div.dropdown div.items ul li.active a.tool{background:#fff}.settings-container header.all-settings div.contents div.settings-select-container nav.dropdown.active div.dropdown div.current{background-color:#ddd}.settings-container header.all-settings div.mcloud-settings-tabs{position:relative;width:100%;padding-left:23px;border-bottom:1px solid #d1d1d1;width:calc(100% - 24px);margin-top:8px}@media (max-width:992px){.settings-container header.all-settings div.mcloud-settings-tabs{display:none}}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap{position:relative;overflow:hidden;transform:translateY(1px);width:100%;height:36px}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul{position:absolute;left:0;top:0;bottom:0;width:30000px;display:flex;align-items:center;padding:0;margin:0}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li{height:100%;display:flex;margin:0 3px 0 0;padding:0 11px 0 14px;background-color:#ddd;align-items:center;border:1px solid #d1d1d1;border-bottom:none;border-top-left-radius:4px;border-top-right-radius:4px}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li a.tool{display:flex;align-items:center;color:#777;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:11px;white-space:nowrap}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li a.tool-pin{display:block;width:20px;height:36px;margin-left:8px;background-image:url(../img/icon-pin-deselected.svg);background-repeat:no-repeat;background-position:50%;background-size:14px}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li a.tool-pin.pinned{background-image:url(../img/icon-pin-selected.svg)}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li.active{background-color:#f1f1f1}.settings-container header.all-settings div.mcloud-settings-tabs .navwrap ul li.active a{color:#000}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-nav{position:absolute;display:flex;align-items:center;justify-content:center;width:96px;top:-1px;bottom:0}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-nav span{font-size:0;line-height:0;display:block;width:20px;height:20px;background-repeat:no-repeat;background-position:50%;background-size:contain}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-nav.hidden{opacity:0;pointer-events:none}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-prev{left:0;justify-content:flex-start;background:linear-gradient(90deg,#f1f1f1 50%,hsla(0,0%,94.5%,0))}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-prev span{margin-left:10px;background-image:url(../img/ilab-icons-prev.svg)}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-next{right:0;justify-content:flex-end;background:linear-gradient(270deg,#f1f1f1 50%,hsla(0,0%,94.5%,0))}.settings-container header.all-settings div.mcloud-settings-tabs a.tabs-next span{margin-right:10px;background-image:url(../img/ilab-icons-next.svg)}.settings-container header.all-settings div.mcloud-settings-tabs.animated .navwrap ul{transition:transform .25s linear}.settings-container header.all-settings div.mcloud-settings-tabs.animated a.tabs-nav{transition:opacity .25s linear}.settings-container .settings-body{margin:20px}.settings-container .settings-body .settings-description{font-size:1.1em;text-align:center;background-color:#fafafa;padding:25px;border-radius:8px;margin-bottom:20px}.settings-container .settings-body.show-upgrade{display:flex}.settings-container .settings-body.show-upgrade>.settings-interior{flex:1;margin-right:20px}@media (max-width:64em){.settings-container .settings-body.show-upgrade>.settings-interior{order:1;margin-right:0}}@media (max-width:64em){.settings-container .settings-body.show-upgrade{flex-direction:column}}.settings-container .settings-body .upgrade-feature{background-color:#fafafa;border-radius:8px;padding:15px 20px}.settings-container .settings-body .upgrade-feature h2{padding:0;margin:0 0 30px;color:#46a4dd}.settings-container .settings-body .upgrade-feature ul{margin-left:20px;list-style:unset}.settings-container .settings-body .upgrade-feature ul li{list-style-type:square}.settings-container .settings-body .upgrade-feature div.button-container{text-align:right;padding:20px 0}.settings-container .settings-body .upgrade-feature div.button-container a{padding:10px;background-color:#46a4dd;border-radius:6px;color:#fff;text-decoration:none;font-weight:700;font-size:1.1em}.settings-container .settings-body .upgrade-promo{min-width:200px;max-width:320px;position:relative}.settings-container .settings-body .upgrade-promo .upgrade-interior{position:relative;background-color:#fafafa;border-radius:8px;padding:15px 20px}.settings-container .settings-body .upgrade-promo .upgrade-interior h2{padding:0;margin:0 0 30px;color:#46a4dd}.settings-container .settings-body .upgrade-promo .upgrade-interior ul{margin-left:20px}.settings-container .settings-body .upgrade-promo .upgrade-interior ul li{list-style-type:square}@media (max-width:64em){.settings-container .settings-body .upgrade-promo .upgrade-interior ul{display:flex;flex-wrap:wrap;width:100%}@supports (display:grid){.settings-container .settings-body .upgrade-promo .upgrade-interior ul{display:grid;grid-template-columns:1fr 1fr 1fr}}.settings-container .settings-body .upgrade-promo .upgrade-interior ul li{margin-right:30px}}@media (max-width:48.9275em){@supports (display:grid){.settings-container .settings-body .upgrade-promo .upgrade-interior ul{grid-template-columns:1fr 1fr}}}.settings-container .settings-body .upgrade-promo .upgrade-interior div.button-container{text-align:right;padding:20px 0}.settings-container .settings-body .upgrade-promo .upgrade-interior div.button-container a{padding:10px;background-color:#46a4dd;border-radius:6px;color:#fff;text-decoration:none;font-weight:700;font-size:1.1em}.settings-container .settings-body .upgrade-promo .upgrade-interior a.upgrade-close{display:none;position:absolute;top:15px;right:20px}@media (max-width:64em){.settings-container .settings-body .upgrade-promo .upgrade-interior a.upgrade-close{display:block}}@media (max-width:64em){.settings-container .settings-body .upgrade-promo{order:0;margin-bottom:20px;max-width:100%}}@media (max-width:64em){.settings-container .settings-body .upgrade-promo.hide-on-mobile{display:none}}.button-warning{background:#dd9000!important;border-color:#dd9000 #b97800 #b97800!important;box-shadow:0 1px 0 #b97800!important;color:#fff!important;text-decoration:none!important;text-shadow:0 -1px 1px #b97800,1px 0 1px #b97800,0 1px 1px #b97800,-1px 0 1px #b97800!important}.media-cloud-tool-description{padding:24px;background:#ddd;border-radius:8px;margin-bottom:20px}.media-cloud-tool-description h2{font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;text-transform:uppercase;margin:0 0 5px;padding:0}.media-cloud-tool-description p{margin-top:0;font-size:1.2em}.ilab-notification-container .notice{margin-left:0;margin-right:0;margin-bottom:10px}.ilab-notification-container .notice:last-of-type{margin-bottom:20px}.ilab-settings-section{background-color:#fafafa;padding:25px;border-radius:8px;margin-bottom:20px;overflow:hidden;border:1px solid #eaeaea}.ilab-settings-section h2{padding:10px 25px;background-color:#fff;margin:-25px -25px 0;border-bottom:1px solid #eaeaea;font-family:system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif;font-weight:700;font-size:13px;color:#50ade2!important;text-transform:uppercase;display:flex;align-items:center}.ilab-settings-section h2 a.help-beacon{margin:0 0 0 10px;padding:0;display:block;width:16px;height:16px;color:transparent;overflow:hidden;background-image:url(../img/mcloud-icon-help.svg);background-position:50%;background-size:contain;background-repeat:no-repeat}.ilab-settings-section .section-description{margin-top:20px;margin-bottom:15px;font-style:italic}.ilab-settings-section .checkbox-w-description{display:flex;align-items:center}.ilab-settings-section .checkbox-w-description label{margin-right:20px}.ilab-settings-section .checkbox-w-description>div>p{margin:0}.ilab-settings-toggle{padding:0 25px 5px}.ilab-settings-toggle table.form-table tr{display:flex;flex-direction:row;align-items:center}@media (max-width:48.9275em){.ilab-settings-toggle table.form-table tr{flex-direction:column;align-items:flex-start}}.ilab-settings-toggle table.form-table tr th{min-width:200px;max-width:200px}@media (max-width:48.9275em){.ilab-settings-toggle table.form-table tr{margin-bottom:20px}.ilab-settings-toggle table.form-table tr th{margin-bottom:10px}}.ilab-settings-features{padding:10px 25px 15px}.ilab-settings-features table.form-table tr{display:flex;flex-direction:row;align-items:center}.ilab-settings-features table.form-table tr td.toggle{display:flex;align-items:center;max-width:220px;min-width:220px}.ilab-settings-features table.form-table tr td.toggle div.title{display:flex;flex-direction:column;margin-left:30px;white-space:nowrap;font-weight:700;font-size:1.05em}.ilab-settings-features table.form-table tr td.toggle div.title div.tool-links{display:flex}.ilab-settings-features table.form-table tr td.toggle div.title div.tool-links a{margin-right:10px;margin-top:5px;font-size:.85em;font-weight:400}.ilab-settings-features table.form-table tr td.toggle div.title div.tool-links a:last-of-type{margin-right:0}.ilab-settings-features table.form-table tr td.description p{font-size:1.05em}@media (max-width:48.9275em){.ilab-settings-features table.form-table tr{flex-direction:column;align-items:flex-start;margin-bottom:30px}.ilab-settings-features table.form-table td.toggle div.title{font-size:1.2em!important}}.ilab-settings-button{margin-top:40px;display:flex;justify-content:center}.ilab-settings-button p{padding:0;margin:0}.ilab-settings-batch-tools{display:flex}.ilab-settings-batch-tools a.button{margin-right:10px}.ilab-settings-batch-tools.has-submit{padding-right:10px;margin-right:20px;border-right:1px solid #ccc}span.tool-indicator{background:#ccc;border:1px solid #979797;display:block;width:9px;height:9px;border-radius:9px;margin-right:6px}span.tool-indicator.tool-active{background:#6dd51b}span.tool-indicator.tool-env-active{background:#fdac00}div.ilab-section-doc-links{margin-top:10px}div.ilab-section-doc-links div.doc-links-setting{background-color:rgba(0,0,0,.04);width:100%;border-radius:6px;display:flex;align-items:center;justify-content:center;padding:12px 0}div.ilab-section-doc-links div.doc-links-setting a{margin:0 5px!important}.troubleshooter-info li{margin:0;padding:8px 0 8px 28px;list-style:none;background-repeat:no-repeat;background-position:left top 6px;background-size:20px}.troubleshooter-info li.info-warning{background-image:url(../img/icon-warning.svg)}.troubleshooter-info li.info-success{background-image:url(../img/icon-success.svg)}.troubleshooter-info li.info-error{background-image:url(../img/icon-error.svg)}.troubleshooter-wait{display:flex;align-items:center}.troubleshooter-wait.hidden{display:none}.troubleshooter-wait>img{margin-right:7px;height:18px}.upload-path-preview{margin:10px 0;padding:10px;font-style:italic;background-color:#fff;border:1px dashed #ddd;display:flex;line-height:1;align-items:center}.upload-path-preview span:first-of-type{text-transform:uppercase;color:#ccc;font-size:11px;font-style:normal;margin-right:10px}.settings-image-preview-container{display:flex;flex-direction:column;margin-bottom:10px}.settings-image-preview-container .settings-image-preview{display:block;border:1px solid #ddd;width:256px;min-width:256px;height:256px;min-height:256px;margin-bottom:10px;background-color:#eee;background-size:contain;background-position:50%;background-repeat:no-repeat}.subsite-setting-group{margin-bottom:20px}.subsite-setting-group:last-of-type{margin-bottom:0}.subsite-upload-path{display:flex;align-items:center}.subsite-upload-path label{min-width:100px}.presigned-url-container>div{display:flex;align-items:flex-start;margin-bottom:20px}.presigned-url-container>div:nth-of-type(2n){margin-bottom:40px}.presigned-url-container>div:last-of-type{margin-bottom:0}.presigned-url-container>div div.presigned-label{line-height:1.3;font-weight:600;margin-right:10px;margin-top:6px;min-width:175px}.privacy-container>div{display:flex;align-items:flex-start;margin-bottom:20px}.privacy-container>div:last-of-type{margin-bottom:0}.privacy-container>div div.privacy-label{line-height:1.3;font-weight:600;margin-right:10px;margin-top:6px;min-width:135px}#beacon-container iframe{z-index:200000!important}.ilab-popup{position:fixed;left:0;top:0;width:100%;height:100%;background-color:rgba(0,0,0,.85);pointer-events:all;z-index:100002;display:flex;align-items:center;justify-content:center;transition:opacity .25s linear;opacity:1}.ilab-popup .ilab-popup-body{position:relative}.ilab-popup .ilab-popup-body .ilab-popup-contents{width:80vw;height:80vh;min-width:80vw;min-height:80vh;max-width:80vw;max-height:80vh;background-color:#fff}.ilab-popup .ilab-popup-body .ilab-popup-close{position:absolute;right:38px;top:12px;font-size:0}.ilab-popup .ilab-popup-body .ilab-popup-close:after,.ilab-popup .ilab-popup-body .ilab-popup-close:before{position:absolute;left:13px;content:" ";height:25px;width:2px;background-color:#000}.ilab-popup .ilab-popup-body .ilab-popup-close:before{transform:rotate(45deg)}.ilab-popup .ilab-popup-body .ilab-popup-close:after{transform:rotate(-45deg)}.ilab-popup.hidden{pointer-events:none;opacity:0}.mcloud-inline-help-container{position:fixed;left:0;top:0;right:0;bottom:0;z-index:100002;transition:opacity .25s linear}.mcloud-inline-help-container .mcloud-inline-help{background-color:#fff;position:absolute;width:375px;height:425px;box-shadow:0 0 10px 1px rgba(0,0,0,.25);border-radius:8px;transform-origin:left center;transition:transform .25s ease-out}.mcloud-inline-help-container .mcloud-inline-help .mcloud-inline-help-arrow{right:100%;top:50%;content:" ";height:0;width:0;position:absolute;pointer-events:none;border:10px solid hsla(0,0%,100%,0);border-right-color:#fff;margin-top:-10px}.mcloud-inline-help-container .mcloud-inline-help .mcloud-inline-help-body{box-sizing:border-box;position:absolute;left:15px;top:15px;right:7.5px;bottom:15px;padding-right:15px;overflow:auto}.mcloud-inline-help-container.mcloud-invisible{opacity:0;pointer-events:none}.mcloud-inline-help-container.mcloud-invisible .mcloud-inline-help{transform:scale(.8)}.mcloud-sidebar-help-container{position:fixed;left:0;top:0;right:0;bottom:0;z-index:1000001}.mcloud-sidebar-help-container .mcloud-sidebar-help{position:absolute;right:0;top:0;bottom:0;width:450px;transition:transform .25s linear;background-color:#fff;box-shadow:0 0 10px 1px rgba(0,0,0,.25)}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-help-body{box-sizing:border-box;position:absolute;left:15px;top:0;right:7.5px;bottom:0;padding-top:15px;padding-right:22.5px;overflow:auto}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-help-body figure{margin-left:0;margin-right:0;padding-left:0;padding-right:0}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-help-body figure img{width:100%;height:auto}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-help-body div.code-block{overflow-x:auto}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-close{display:block;position:absolute;right:10px;top:10px;font-size:0;line-height:0;width:14px;height:14px}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-close:before{position:absolute;content:"";width:14px;height:2px;background-color:#aaa;transform:translateX(-50%) rotate(-45deg);left:50%;top:50%}.mcloud-sidebar-help-container .mcloud-sidebar-help .mcloud-sidebar-close:after{position:absolute;content:"";width:14px;height:2px;background-color:#aaa;transform:translateX(-50%) rotate(45deg);left:50%;top:50%}.mcloud-sidebar-help-container.mcloud-invisible{pointer-events:none}.mcloud-sidebar-help-container.mcloud-invisible .mcloud-sidebar-help{transform:translateX(100%)}body.modal-open #beacon-container{display:none!important}.BeaconContainer{right:10px!important;bottom:88px!important}.BeaconFabButtonFrame{right:10px!important;bottom:10px!important}.section-jumps{display:flex;align-items:center;justify-content:center;margin-top:30px;margin-bottom:35px}.section-jumps span.label{color:#777;text-decoration:none;text-transform:uppercase;font-weight:700;font-size:10px;margin-right:20px;margin-top:2px}.section-jumps a,.section-jumps span.label{display:block;line-height:1}.section-jumps span.sep{margin-left:10px;margin-right:10px;color:#777;font-weight:700;font-size:11px}.section-submit{display:flex;justify-content:center;border:1px solid #eaeaea;background-color:rgba(0,0,0,.04);width:100%;border-radius:6px;align-items:center;padding:12px 0;margin-top:20px}.section-submit p{margin:0;padding:0}.wizard-container{position:fixed;left:0;top:0;width:100%;height:100%;background-color:#000;z-index:100000;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:opacity .333s linear}.wizard-container *{font-family:SF Pro Text,SFProText,system-ui,-apple-system,BlinkMacSystemFont,Avenir Next,Avenir,Segoe UI,Lucida Grande,Helvetica Neue,Helvetica,Fira Sans,Roboto,Noto,Droid Sans,Cantarell,Oxygen,Ubuntu,Franklin Gothic Medium,Century Gothic,Liberation Sans,sans-serif}.wizard-container a{text-decoration:none}.wizard-container a:focus{outline:none;box-shadow:none}.wizard-container .wizard-modal{position:relative;width:87.8048780488vw;height:51.2195121951vw;transition:transform .333s linear,opacity .333s linear}@media (min-width:102.5em){.wizard-container .wizard-modal{width:1440px}}@media (max-width:48.9275em){.wizard-container .wizard-modal{width:94.5083014049vw}}@media (min-width:102.5em){.wizard-container .wizard-modal{height:840px}}@media (max-width:48.9275em){.wizard-container .wizard-modal{height:81.7369093231vw}}.wizard-container .wizard-modal div.steps-background{position:absolute;left:calc(100% - 320px);top:-100vh;width:100vw;height:300vh;background-color:rgba(58,86,116,.5);transition:transform .25s linear,opacity .25s linear}@media (max-width:48.9275em){.wizard-container .wizard-modal div.steps-background{left:calc(100% - 26.81992vw)}}@media (min-width:48.9375em) and (max-width:102.49em){.wizard-container .wizard-modal div.steps-background{left:calc(100% - 19.5122vw)}}.wizard-container .wizard-modal a.close-modal{position:absolute;left:.6097560976vw;top:.6097560976vw;width:1.7073170732vw;height:1.7073170732vw;background-image:url(../img/wizard-close-modal.svg);background-position:50%;background-repeat:no-repeat;background-size:contain;font-size:0;line-height:0}@media (min-width:102.5em){.wizard-container .wizard-modal a.close-modal{left:10px}}@media (max-width:48.9275em){.wizard-container .wizard-modal a.close-modal{left:1.2771392082vw}}@media (min-width:102.5em){.wizard-container .wizard-modal a.close-modal{top:10px}}@media (max-width:48.9275em){.wizard-container .wizard-modal a.close-modal{top:1.2771392082vw}}@media (min-width:102.5em){.wizard-container .wizard-modal a.close-modal{width:28px}}@media (max-width:48.9275em){.wizard-container .wizard-modal a.close-modal{width:3.5759897829vw}}@media (min-width:102.5em){.wizard-container .wizard-modal a.close-modal{height:28px}}@media (max-width:48.9275em){.wizard-container .wizard-modal a.close-modal{height:3.5759897829vw}}.wizard-content{position:absolute;left:0;top:0;right:0;bottom:0;font-size:.9756097561vw;border-radius:.7317073171vw;overflow:hidden;background-color:#fff;display:flex;flex-direction:column}@media (min-width:102.5em){.wizard-content{font-size:16px}}@media (max-width:48.9275em){.wizard-content{font-size:1.7879948914vw}}@media (min-width:102.5em){.wizard-content{border-radius:12px}}@media (max-width:48.9275em){.wizard-content{border-radius:1.5325670498vw}}.wizard-content div.sections{flex:1;position:relative;overflow:hidden}.wizard-content div.sections div.wizard-section{position:absolute;left:0;right:0;top:0;bottom:0;transform:translateX(87.8048780488vw);transition:transform .25s linear,opacity .25s linear,filter .25s linear,-webkit-filter .25s linear;overflow-x:hidden;opacity:0}.wizard-content div.sections div.wizard-section.current{opacity:1;transform:translateX(0)}.wizard-content div.sections div.wizard-section.past{transform:translateX(-87.8048780488vw)}@media (min-width:102.5em){.wizard-content div.sections div.wizard-section{transform:translateX(1440px)}.wizard-content div.sections div.wizard-section.past{transform:translateX(-1440px)}}.wizard-content div.sections div.wizard-section div.wizard-step{position:absolute;left:0;right:0;top:0;bottom:0;transform:translateX(100%);transition:transform .25s linear,opacity .25s linear;opacity:0}.wizard-content div.sections div.wizard-section div.wizard-step.current{opacity:1;transform:translateX(0)}.wizard-content div.sections div.wizard-section div.wizard-step.past{transform:translateX(-100%)}.wizard-content div.sections div.wizard-section[data-display-steps=true]{max-width:68.29268vw}@media (max-width:48.9275em){.wizard-content div.sections div.wizard-section[data-display-steps=true]{max-width:67.68838vw}}@media (min-width:102.5em){.wizard-content div.sections div.wizard-section[data-display-steps=true]{max-width:1120px}}.wizard-content div.sections div.wizard-section.section-tutorial[data-display-steps=true]{max-width:66.46341vw}@media (max-width:48.9275em){.wizard-content div.sections div.wizard-section.section-tutorial[data-display-steps=true]{max-width:61.30268vw}}@media (min-width:102.5em){.wizard-content div.sections div.wizard-section.section-tutorial[data-display-steps=true]{max-width:1090px}}.wizard-content div.steps{position:absolute;right:0;top:0;bottom:0;width:19.512195122vw;background-color:#3a5674;padding-top:2.9268292683vw;background-image:url(../img/wizard-steps-bg.svg);background-repeat:no-repeat;background-position:bottom;background-size:19.512195122vw;transition:transform .25s linear,opacity .25s linear}@media (min-width:102.5em){.wizard-content div.steps{width:320px}}@media (max-width:48.9275em){.wizard-content div.steps{width:26.8199233716vw}}@media (min-width:102.5em){.wizard-content div.steps{padding-top:48px}}@media (max-width:48.9275em){.wizard-content div.steps{padding-top:4.0868454662vw}}@media (min-width:102.5em){.wizard-content div.steps{background-size:320px}}@media (max-width:48.9275em){.wizard-content div.steps{background-size:26.8199233716vw}}.wizard-content div.steps ul{padding:0;margin:0}.wizard-content div.steps ul li{display:flex;align-items:flex-start;margin:0 0 2.9268292683vw;padding:0 1.4634146341vw 0 0;perspective:1000px}@media (min-width:102.5em){.wizard-content div.steps ul li{margin-bottom:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li{margin-bottom:3.0651340996vw}}@media (min-width:102.5em){.wizard-content div.steps ul li{padding-right:24px}}@media (max-width:48.9275em){.wizard-content div.steps ul li{padding-right:1.5325670498vw}}.wizard-content div.steps ul li input[type=checkbox]{display:none}.wizard-content div.steps ul li div.step-number{position:relative;width:3.9024390244vw;min-width:3.9024390244vw;max-width:3.9024390244vw;height:3.9024390244vw;min-height:3.9024390244vw;max-height:3.9024390244vw;margin-top:-.487804878vw;display:flex;align-items:center;justify-content:center;transform:translateX(-50%);transform-style:preserve-3d;transition:transform .5s linear}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{width:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{width:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{min-width:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{min-width:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{max-width:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{max-width:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{height:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{height:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{min-height:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{min-height:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{max-height:64px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{max-height:8.1736909323vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number{margin-top:-8px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number{margin-top:-1.0217113665vw}}.wizard-content div.steps ul li div.step-number span{position:absolute;left:.487804878vw;top:.487804878vw;width:2.9268292683vw;min-width:2.9268292683vw;max-width:2.9268292683vw;height:2.9268292683vw;min-height:2.9268292683vw;max-height:2.9268292683vw;border-radius:2.9268292683vw;border:.0609756098vw solid #e6e6e6;background-color:#fff;color:#50ade2;display:flex;align-items:center;justify-content:center;transition:border-width .25s linear,border-color .25s linear,transform .25s linear;-webkit-backface-visibility:hidden;backface-visibility:hidden}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{left:8px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{left:1.0217113665vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{top:8px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{top:1.0217113665vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{width:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{width:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{min-width:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{min-width:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{max-width:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{max-width:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{height:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{height:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{min-height:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{min-height:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{max-height:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{max-height:6.1302681992vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span{border-radius:48px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span{border-radius:6.1302681992vw}}.wizard-content div.steps ul li div.step-number span.back{transform:rotateY(180deg)}.wizard-content div.steps ul li div.step-number span.back img{width:.9756097561vw;min-width:.9756097561vw;max-width:.9756097561vw;height:auto}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span.back img{width:16px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span.back img{width:2.0434227331vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span.back img{min-width:16px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span.back img{min-width:2.0434227331vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.step-number span.back img{max-width:16px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.step-number span.back img{max-width:2.0434227331vw}}.wizard-content div.steps ul li.current div.step-number span{background:linear-gradient(135.29deg,#62c5f1 7.95%,#50ade2 101.07%);color:#fff;border:.487804878vw solid #fff;transform:translate(-12.5%,-12.5%)}.wizard-content div.steps ul li.current div.step-number span.back{transform:translate(-12.5%,-12.5%) rotateY(180deg)}@media (min-width:102.5em){.wizard-content div.steps ul li.current div.step-number span{border:8px solid #fff}}.wizard-content div.steps ul li.current div.description h3{color:#fff}.wizard-content div.steps ul li.complete div.step-number{transform:translateX(-50%) rotateY(180deg)}.wizard-content div.steps ul li.complete div.step-number span{background:linear-gradient(135.29deg,#62c5f1 7.95%,#50ade2 101.07%);color:#fff;border:0 solid hsla(0,0%,100%,0)}.wizard-content div.steps ul li div.description{margin-left:-.487804878vw}@media (min-width:102.5em){.wizard-content div.steps ul li div.description{margin-left:-8px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.description{margin-left:-2.5542784163vw}}.wizard-content div.steps ul li div.description h3{padding:0;color:hsla(0,0%,100%,.5);font-weight:700;font-size:1em;line-height:1.5em;margin:.7317073171vw 0 .487804878vw;transition:margin-top .25s linear}@media (min-width:102.5em){.wizard-content div.steps ul li div.description h3{margin-top:12px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.description h3{margin-top:1.5325670498vw}}@media (min-width:102.5em){.wizard-content div.steps ul li div.description h3{margin-bottom:8px}}@media (max-width:48.9275em){.wizard-content div.steps ul li div.description h3{margin-bottom:1.0217113665vw}}.wizard-content div.steps ul li div.description div.description-container{max-height:0;overflow:hidden;transition:max-height .25s linear}.wizard-content div.steps ul li div.description div.description-container p{opacity:0;margin:0;padding:0;font-size:.875em;color:hsla(0,0%,100%,.7);line-height:1.5em;transition:opacity .25s linear}.wizard-content div.steps ul li input[type=checkbox]:checked+div.description h3{margin-top:0}.wizard-content div.steps ul li input[type=checkbox]:checked+div.description div.description-container{max-height:8.5365853659vw}@media (min-width:102.5em){.wizard-content div.steps ul li input[type=checkbox]:checked+div.description div.description-container{max-height:140px}}@media (max-width:48.9275em){.wizard-content div.steps ul li input[type=checkbox]:checked+div.description div.description-container{max-height:17.8799489144vw}}.wizard-content div.steps ul li input[type=checkbox]:checked+div.description div.description-container p{opacity:1}.wizard-content footer{display:flex;height:5.8536585366vw;margin-right:19.512195122vw;padding:0 7.3170731707vw;align-items:center;justify-content:space-between;border-top:1px solid #e6e6e6;transition:margin-right .25s linear}@media (min-width:102.5em){.wizard-content footer{height:96px}}@media (max-width:48.9275em){.wizard-content footer{height:12.2605363985vw}}@media (min-width:102.5em){.wizard-content footer{margin-right:320px}}@media (max-width:48.9275em){.wizard-content footer{margin-right:26.8199233716vw}}@media (min-width:102.5em){.wizard-content footer{padding-bottom:0}}@media (max-width:48.9275em){.wizard-content footer{padding-bottom:0}}@media (min-width:102.5em){.wizard-content footer{padding-top:0}}@media (max-width:48.9275em){.wizard-content footer{padding-top:0}}@media (min-width:102.5em){.wizard-content footer{padding-left:120px}}@media (max-width:48.9275em){.wizard-content footer{padding-left:7.662835249vw}}@media (min-width:102.5em){.wizard-content footer{padding-right:120px}}@media (max-width:48.9275em){.wizard-content footer{padding-right:7.662835249vw}}.wizard-content footer img.logo{width:3.9024390244vw;height:auto}@media (min-width:102.5em){.wizard-content footer img.logo{width:64px}}@media (max-width:48.9275em){.wizard-content footer img.logo{width:8.1736909323vw}}.wizard-content footer a{font-style:normal;font-weight:500;font-size:1em;display:flex;align-items:center;justify-content:center;letter-spacing:.0457317073vw;text-transform:uppercase;text-decoration:none;color:#50abe0;transition:opacity .25s linear,background .25s linear}@media (min-width:102.5em){.wizard-content footer a{letter-spacing:.75px}}@media (max-width:48.9275em){.wizard-content footer a{letter-spacing:.0957854406vw}}.wizard-content footer a.disabled{color:#b3b3b3;pointer-events:none}.wizard-content footer a.invisible{opacity:0;pointer-events:none}.wizard-content footer nav{display:flex}.wizard-content footer nav a{margin-left:.6097560976vw;padding:.9146341463vw 2.1341463415vw}@media (min-width:102.5em){.wizard-content footer nav a{margin-left:10px}}@media (max-width:48.9275em){.wizard-content footer nav a{margin-left:1.2771392082vw}}@media (min-width:102.5em){.wizard-content footer nav a{padding-bottom:15px}}@media (max-width:48.9275em){.wizard-content footer nav a{padding-bottom:1.1494252874vw}}@media (min-width:102.5em){.wizard-content footer nav a{padding-top:15px}}@media (max-width:48.9275em){.wizard-content footer nav a{padding-top:1.1494252874vw}}@media (min-width:102.5em){.wizard-content footer nav a{padding-left:35px}}@media (max-width:48.9275em){.wizard-content footer nav a{padding-left:3.0651340996vw}}@media (min-width:102.5em){.wizard-content footer nav a{padding-right:35px}}@media (max-width:48.9275em){.wizard-content footer nav a{padding-right:3.0651340996vw}}.wizard-content footer nav a.hidden{display:none}.wizard-content footer nav a.next,.wizard-content footer nav a.return{color:#fff;border-radius:6.0975609756vw;background:linear-gradient(180deg,#62c5f1,#50ade2)}@media (min-width:102.5em){.wizard-content footer nav a.next,.wizard-content footer nav a.return{border-radius:100px}}@media (max-width:48.9275em){.wizard-content footer nav a.next,.wizard-content footer nav a.return{border-radius:6.3856960409vw}}.wizard-content footer nav a.next.disabled,.wizard-content footer nav a.return.disabled{background:linear-gradient(180deg,#f0f0f0,#e0e0e0)}.wizard-step{padding:0 7.3170731707vw;display:flex;flex-direction:column;justify-content:center;flex:1}@media (min-width:102.5em){.wizard-step{padding-bottom:0}}@media (max-width:48.9275em){.wizard-step{padding-bottom:0}}@media (min-width:102.5em){.wizard-step{padding-top:0}}@media (max-width:48.9275em){.wizard-step{padding-top:0}}@media (min-width:102.5em){.wizard-step{padding-left:120px}}@media (max-width:48.9275em){.wizard-step{padding-left:7.662835249vw}}@media (min-width:102.5em){.wizard-step{padding-right:120px}}@media (max-width:48.9275em){.wizard-step{padding-right:7.662835249vw}}.wizard-step .intro{margin-bottom:3.6585365854vw}.wizard-step .intro h1{line-height:1.2;margin-bottom:2.4390243902vw}@media (min-width:102.5em){.wizard-step .intro h1{margin-bottom:40px}}@media (max-width:48.9275em){.wizard-step .intro h1{margin-bottom:2.5542784163vw}}@media (min-width:102.5em){.wizard-step .intro{margin-bottom:60px}}@media (max-width:48.9275em){.wizard-step .intro{margin-bottom:3.8314176245vw}}.wizard-step .intro p{padding:0;margin:0 0 1.0975609756vw;font-size:1.125em;text-align:left}@media (min-width:102.5em){.wizard-step .intro p{margin-bottom:18px}}@media (max-width:48.9275em){.wizard-step .intro p{margin-bottom:1.1494252874vw}}.wizard-step .intro p:last-of-type{margin-bottom:0}div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding:0 5.487804878vw 0 7.3170731707vw}@media (min-width:102.5em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-left:120px}}@media (max-width:48.9275em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-left:7.662835249vw}}@media (min-width:102.5em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-top:0}}@media (max-width:48.9275em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-top:0}}@media (min-width:102.5em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-right:90px}}@media (max-width:48.9275em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-right:0}}@media (min-width:102.5em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-bottom:0}}@media (max-width:48.9275em){div.wizard-section.section-tutorial[data-display-steps=true] .wizard-step-tutorial{padding-bottom:0}}.wizard-step-select div.step-contents{margin-bottom:2.4390243902vw}@media (min-width:102.5em){.wizard-step-select div.step-contents{margin-bottom:40px}}@media (max-width:48.9275em){.wizard-step-select div.step-contents{margin-bottom:2.5542784163vw}}.wizard-step-select div.step-contents:last-of-type{margin-bottom:0}.wizard-step-select .intro{text-align:center}.wizard-step-select ul{display:flex;flex-wrap:wrap;padding:0;margin:0;justify-content:center;align-items:center}.wizard-step-select ul li{position:relative;display:block;padding:0;margin:1.8292682927vw 2.4390243902vw}@media (min-width:102.5em){.wizard-step-select ul li{margin-top:30px}}@media (max-width:48.9275em){.wizard-step-select ul li{margin-top:1.9157088123vw}}@media (min-width:102.5em){.wizard-step-select ul li{margin-bottom:30px}}@media (max-width:48.9275em){.wizard-step-select ul li{margin-bottom:1.9157088123vw}}@media (min-width:102.5em){.wizard-step-select ul li{margin-left:40px}}@media (max-width:48.9275em){.wizard-step-select ul li{margin-left:5.1085568327vw}}@media (min-width:102.5em){.wizard-step-select ul li{margin-right:40px}}@media (max-width:48.9275em){.wizard-step-select ul li{margin-right:5.1085568327vw}}.wizard-step-select ul li div.description{position:absolute;left:50%;transform:translate(-50%,24px) scale(.7);bottom:calc(100% + 30px);padding:1.4634146341vw;background-color:#3a5674;color:#fff;width:17.6829268293vw;display:flex;flex-direction:column;align-items:center;justify-content:center;border-radius:10px;box-shadow:0 0 10px 1px rgba(0,0,0,.25);opacity:0;pointer-events:none;transition:transform .125s linear,opacity .125s linear}@media (min-width:102.5em){.wizard-step-select ul li div.description{padding:24px}}@media (max-width:48.9275em){.wizard-step-select ul li div.description{padding:1.5325670498vw}}@media (min-width:102.5em){.wizard-step-select ul li div.description{width:290px}}@media (max-width:48.9275em){.wizard-step-select ul li div.description{width:32.5670498084vw}}.wizard-step-select ul li div.description div.arrow-down{position:absolute;bottom:-13px;width:0;height:0;left:calc(50% - 14px);border-left:14px solid transparent;border-right:14px solid transparent;border-top:14px solid #3a5674}.wizard-step-select ul li:hover div.description{opacity:1;transform:translate(-50%) scale(1)}ul.options.select-icons li:hover a img{transform:scale(1.2)}ul.options.select-icons li a{font-size:0}ul.options.select-icons li a img{transition:transform .2s linear;height:2.9268292683vw;width:auto}@media (min-width:102.5em){ul.options.select-icons li a img{height:48px}}@media (max-width:48.9275em){ul.options.select-icons li a img{height:3.0651340996vw}}ul.options.select-icons li a.select-s3 img{height:3.6585365854vw}@media (min-width:102.5em){ul.options.select-icons li a.select-s3 img{height:60px}}@media (max-width:48.9275em){ul.options.select-icons li a.select-s3 img{height:3.8314176245vw}}ul.options.select-buttons li{margin:1.8292682927vw .9146341463vw}@media (min-width:102.5em){ul.options.select-buttons li{margin-top:30px}}@media (max-width:48.9275em){ul.options.select-buttons li{margin-top:1.9157088123vw}}@media (min-width:102.5em){ul.options.select-buttons li{margin-bottom:30px}}@media (max-width:48.9275em){ul.options.select-buttons li{margin-bottom:1.9157088123vw}}@media (min-width:102.5em){ul.options.select-buttons li{margin-left:15px}}@media (max-width:48.9275em){ul.options.select-buttons li{margin-left:1.0217113665vw}}@media (min-width:102.5em){ul.options.select-buttons li{margin-right:15px}}@media (max-width:48.9275em){ul.options.select-buttons li{margin-right:1.0217113665vw}}ul.options.select-buttons li a{font-style:normal;font-weight:500;font-size:1em;display:flex;align-items:center;justify-content:center;letter-spacing:.0457317073vw;text-transform:uppercase;text-decoration:none;color:#fff;border-radius:6.0975609756vw;padding:.9146341463vw 2.1341463415vw;background:linear-gradient(180deg,#62c5f1,#50ade2)}@media (min-width:102.5em){ul.options.select-buttons li a{letter-spacing:.75px;padding:15px 35px}}ul.options.select-flat-buttons li{margin:1.8292682927vw .9146341463vw}@media (min-width:102.5em){ul.options.select-flat-buttons li{margin-top:30px}}@media (max-width:48.9275em){ul.options.select-flat-buttons li{margin-top:1.9157088123vw}}@media (min-width:102.5em){ul.options.select-flat-buttons li{margin-bottom:30px}}@media (max-width:48.9275em){ul.options.select-flat-buttons li{margin-bottom:1.9157088123vw}}@media (min-width:102.5em){ul.options.select-flat-buttons li{margin-left:15px}}@media (max-width:48.9275em){ul.options.select-flat-buttons li{margin-left:1.0217113665vw}}@media (min-width:102.5em){ul.options.select-flat-buttons li{margin-right:15px}}@media (max-width:48.9275em){ul.options.select-flat-buttons li{margin-right:1.0217113665vw}}ul.options.select-flat-buttons li a{font-style:normal;font-weight:500;font-size:1.5em;letter-spacing:.0457317073vw;text-transform:uppercase;text-decoration:none;border-bottom:1px dotted #50ade2;color:#50ade2}@media (min-width:102.5em){ul.options.select-flat-buttons li a{letter-spacing:.75px}}.wizard-step-video{padding:0}.wizard-step-video .step-contents .video,.wizard-step-video .step-contents .video iframe{position:absolute;top:0;left:0;width:100%;height:100%}@-webkit-keyframes logo-rotate{0%{transform:rotateY(0deg)}to{transform:rotateY(-1turn)}}@keyframes logo-rotate{0%{transform:rotateY(0deg)}to{transform:rotateY(-1turn)}}@-webkit-keyframes logo-rotate-x{0%{transform:rotateX(0deg)}to{transform:rotateX(-1turn)}}@keyframes logo-rotate-x{0%{transform:rotateX(0deg)}to{transform:rotateX(-1turn)}}@-webkit-keyframes logo-rotate-z{0%{transform:rotate(-1turn)}to{transform:rotate(0deg)}}@keyframes logo-rotate-z{0%{transform:rotate(-1turn)}to{transform:rotate(0deg)}}.wizard-step-form div.intro{margin-bottom:1.8292682927vw}@media (min-width:102.5em){.wizard-step-form div.intro{margin-bottom:30px}}@media (max-width:48.9275em){.wizard-step-form div.intro{margin-bottom:1.9157088123vw}}.wizard-step-form form{display:flex;flex-direction:column}.wizard-step-form form div.form-field{display:flex;flex-direction:column;border:1px solid #f3f3f3;background-color:#f3f3f3;padding:1.2195121951vw;border-radius:.9756097561vw;margin-bottom:1.2195121951vw}@media (min-width:102.5em){.wizard-step-form form div.form-field{padding:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field{padding:1.2771392082vw}}@media (min-width:102.5em){.wizard-step-form form div.form-field{border-radius:16px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field{border-radius:1.0217113665vw}}@media (min-width:102.5em){.wizard-step-form form div.form-field{margin-bottom:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field{margin-bottom:1.2771392082vw}}.wizard-step-form form div.form-field:last-of-type{margin-bottom:0}.wizard-step-form form div.form-field:focus-within{border:1px solid #50ade2}.wizard-step-form form div.form-field label{font-weight:500;font-size:.75em;line-height:1em;text-transform:uppercase;color:#3a5674;margin-bottom:.487804878vw}@media (min-width:102.5em){.wizard-step-form form div.form-field label{margin-bottom:8px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field label{margin-bottom:.5108556833vw}}.wizard-step-form form div.form-field input[type=password],.wizard-step-form form div.form-field input[type=text]{box-shadow:none;padding:0;border:0;background:none;font-size:1.125em}.wizard-step-form form div.form-field input[type=password]::-webkit-input-placeholder,.wizard-step-form form div.form-field input[type=text]::-webkit-input-placeholder{color:rgba(0,0,0,.125)}.wizard-step-form form div.form-field input[type=password]::-moz-placeholder,.wizard-step-form form div.form-field input[type=text]::-moz-placeholder{color:rgba(0,0,0,.125)}.wizard-step-form form div.form-field input[type=password]:-ms-input-placeholder,.wizard-step-form form div.form-field input[type=text]:-ms-input-placeholder{color:rgba(0,0,0,.125)}.wizard-step-form form div.form-field input[type=password]::-ms-input-placeholder,.wizard-step-form form div.form-field input[type=text]::-ms-input-placeholder{color:rgba(0,0,0,.125)}.wizard-step-form form div.form-field input[type=password]::placeholder,.wizard-step-form form div.form-field input[type=text]::placeholder{color:rgba(0,0,0,.125)}.wizard-step-form form div.form-field select{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;padding:0;background:transparent;outline:0;font-size:1.125em}.wizard-step-form form div.form-field select:focus{outline:0}.wizard-step-form form div.form-field.field-checkbox{background:none;flex-direction:row;align-items:flex-start;padding:1.2195121951vw 0;border:1px solid #fff}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox{padding-bottom:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox{padding-bottom:1.2771392082vw}}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox{padding-top:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox{padding-top:1.2771392082vw}}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox{padding-left:0}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox{padding-left:0}}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox{padding-right:0}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox{padding-right:0}}.wizard-step-form form div.form-field.field-checkbox div.checkbox{margin-right:1.2195121951vw}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox div.checkbox{margin-right:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox div.checkbox{margin-right:1.2771392082vw}}.wizard-step-form form div.form-field.field-checkbox div.title{padding-top:.3048780488vw;margin-right:1.2195121951vw;white-space:nowrap;font-size:1em;font-weight:500}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox div.title{padding-top:5px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox div.title{padding-top:.6385696041vw}}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox div.title{margin-right:20px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox div.title{margin-right:1.2771392082vw}}.wizard-step-form form div.form-field.field-checkbox div.description{padding-top:.3048780488vw;font-size:1em;font-weight:300}@media (min-width:102.5em){.wizard-step-form form div.form-field.field-checkbox div.description{padding-top:5px}}@media (max-width:48.9275em){.wizard-step-form form div.form-field.field-checkbox div.description{padding-top:.6385696041vw;display:none}}.wizard-step-form .progress{position:absolute;left:0;right:0;bottom:0;top:0;display:flex;flex-direction:column;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .25s linear;perspective:100em}.wizard-step-form .progress h3{color:#50abe0;margin-bottom:3.0487804878vw;font-size:1.625em}@media (min-width:102.5em){.wizard-step-form .progress h3{margin-bottom:50px}}.wizard-step-form .progress div.logo-spinner{-webkit-animation:logo-rotate 2s;animation:logo-rotate 2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.wizard-step-form .progress div.logo-spinner img{width:7.3170731707vw;height:auto}@media (min-width:102.5em){.wizard-step-form .progress div.logo-spinner img{width:120px}}.wizard-step-form.processing .progress{opacity:1}.wizard-step-form.processing div.step-contents{-webkit-filter:blur(5px);filter:blur(5px)}.wizard-step-test{justify-content:flex-start}.wizard-step-test div.step-contents{margin-top:3.0487804878vw}@media (min-width:102.5em){.wizard-step-test div.step-contents{margin-top:50px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents{margin-top:6.3856960409vw}}.wizard-step-test div.step-contents div.intro{margin-bottom:1.8292682927vw}@media (min-width:102.5em){.wizard-step-test div.step-contents div.intro{margin-bottom:30px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents div.intro{margin-bottom:1.9157088123vw}}.wizard-step-test div.step-contents div.start-buttons{display:flex;justify-content:center;margin-bottom:1.8292682927vw}@media (max-width:48.9275em){.wizard-step-test div.step-contents div.start-buttons{margin-bottom:1.9157088123vw}}.wizard-step-test div.step-contents div.start-buttons a{font-style:normal;font-weight:500;font-size:1em;display:flex;align-items:center;justify-content:center;letter-spacing:.0457317073vw;text-transform:uppercase;text-decoration:none;color:#fff;border-radius:6.0975609756vw;padding:.9146341463vw 2.1341463415vw;background:linear-gradient(180deg,#62c5f1,#50ade2)}@media (min-width:102.5em){.wizard-step-test div.step-contents div.start-buttons a{letter-spacing:.75px;padding:15px 35px}}@media (min-width:102.5em){.wizard-step-test div.step-contents div.start-buttons{margin-bottom:30px}}.wizard-step-test div.step-contents ul.tests>li{border:1px solid #f3f3f3;background-color:#f3f3f3;border-radius:.9756097561vw;padding:1.2195121951vw;margin-bottom:20xp;display:flex;transition:opacity .25s linear}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li{border-radius:16px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li{border-radius:1.0217113665vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li{padding:20px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li{padding:1.2771392082vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li{margin-bottom:20xp}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li{margin-bottom:1.2771392082vw}}.wizard-step-test div.step-contents ul.tests>li.hidden{opacity:0}.wizard-step-test div.step-contents ul.tests>li div.icon{width:1.4634146341vw;min-width:1.4634146341vw;max-width:1.4634146341vw;height:1.4634146341vw;min-height:1.4634146341vw;max-height:1.4634146341vw;display:flex;justify-content:center;align-items:center;margin-right:.6097560976vw}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{width:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{width:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{min-width:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{min-width:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{max-width:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{max-width:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{height:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{height:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{min-height:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{min-height:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{max-height:24px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{max-height:3.0651340996vw}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.icon{margin-right:10px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.icon{margin-right:1.2771392082vw}}.wizard-step-test div.step-contents ul.tests>li div.icon img{display:none;width:100%;height:auto}.wizard-step-test div.step-contents ul.tests>li.waiting div.icon{perspective:100em}.wizard-step-test div.step-contents ul.tests>li.waiting div.icon img.waiting{display:block;-webkit-animation:logo-rotate-z 1s;animation:logo-rotate-z 1s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.wizard-step-test div.step-contents ul.tests>li.error>div.icon>img.error,.wizard-step-test div.step-contents ul.tests>li.success>div.icon>img.success,.wizard-step-test div.step-contents ul.tests>li.warning>div.icon>img.warning{display:block}.wizard-step-test div.step-contents ul.tests>li div.description h3{margin:.243902439vw 0;padding:0;font-size:1.125em}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-top:4px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-top:0}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-bottom:4px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-bottom:0}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-left:0}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-left:0}}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-right:0}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.description h3{margin-right:0}}.wizard-step-test div.step-contents ul.tests>li div.description p{margin:0 0 4px;padding:0;font-size:1em}.wizard-step-test div.step-contents ul.tests>li div.description ul.errors{margin-left:1.2195121951vw;list-style:disc}@media (min-width:102.5em){.wizard-step-test div.step-contents ul.tests>li div.description ul.errors{margin-left:20px}}@media (max-width:48.9275em){.wizard-step-test div.step-contents ul.tests>li div.description ul.errors{margin-left:1.2771392082vw}}.wizard-step-test div.step-contents ul.tests>li div.description ul.errors li{display:list-item;font-size:1em}.wizard-step-tutorial{justify-content:flex-start}.wizard-step-tutorial div.tutorial{padding-top:2.4390243902vw;margin-bottom:60px}@media (min-width:102.5em){.wizard-step-tutorial div.tutorial{padding-top:40px}}@media (max-width:48.9275em){.wizard-step-tutorial div.tutorial{padding-top:5.1085568327vw}}.wizard-step-tutorial div.tutorial h2,.wizard-step-tutorial div.tutorial h3{margin-top:2.4390243902vw}@media (min-width:102.5em){.wizard-step-tutorial div.tutorial h2,.wizard-step-tutorial div.tutorial h3{margin-top:40px}}@media (max-width:48.9275em){.wizard-step-tutorial div.tutorial h2,.wizard-step-tutorial div.tutorial h3{margin-top:2.5542784163vw}}.wizard-step-tutorial div.tutorial p{padding:0;margin:0 0 .6097560976vw;font-size:1.125em}@media (min-width:102.5em){.wizard-step-tutorial div.tutorial p{margin-bottom:10px}}@media (max-width:48.9275em){.wizard-step-tutorial div.tutorial p{margin-bottom:1.2771392082vw}}.wizard-step-tutorial div.tutorial p:last-of-type{margin-bottom:0}.wizard-step-tutorial div.tutorial figure{padding:0;margin:2.4390243902vw 0}@media (min-width:102.5em){.wizard-step-tutorial div.tutorial figure{margin-top:40px}}@media (max-width:48.9275em){.wizard-step-tutorial div.tutorial figure{margin-top:2.5542784163vw}}@media (min-width:102.5em){.wizard-step-tutorial div.tutorial figure{margin-bottom:40px}}@media (max-width:48.9275em){.wizard-step-tutorial div.tutorial figure{margin-bottom:2.5542784163vw}}.wizard-step-tutorial div.tutorial figure img{width:100%;height:auto}.wizard-step-tutorial div.tutorial ul{margin-left:20px;margin-bottom:30px;list-style:disc}.wizard-step-tutorial div.tutorial ul li{display:list-item}.wizard-modal.no-steps div.steps-background{opacity:0}.wizard-modal.no-steps div.wizard-content div.steps{transform:translateX(calc(100% + 1.95122vw));opacity:0}@media (min-width:102.5em){.wizard-modal.no-steps div.wizard-content div.steps{transform:translateX(calc(100% + 32px))}}.wizard-modal.no-steps div.wizard-content footer{margin-right:0}.wizard-modal.no-animations *{transition:none!important}.wizard-invisible .wizard-modal{transform:scale(.8);opacity:0}#s3-importer-progress{padding:24px;background:#ddd;border-radius:8px}#s3-importer-progress .button-whoa{background:#a42929!important;border-color:#e62a2a #a42929 #a42929!important;box-shadow:0 1px 0 #a42929!important;color:#fff!important;text-decoration:none!important;text-shadow:0 -1px 1px #a42929,1px 0 1px #a42929,0 1px 1px #a42929,-1px 0 1px #a42929!important}#s3-importer-progress>button{margin-top:20px}.s3-importer-progress-container{position:relative;width:100%;height:32px;background:#aaa;border-radius:16px;overflow:hidden;background-image:url(../img/candy-stripe.svg)}#s3-importer-progress-bar{background-color:#4f90c4;height:100%}.tool-disabled{padding:10px 15px;border:1px solid #df8403}.force-cancel-help{margin-top:20px}.wp-cli-callout{padding:24px;background:#ddd;margin-top:20px;border-radius:8px}.wp-cli-callout>h3{margin:0;padding:0;font-size:14px}.wp-cli-callout>code{background-color:#bbb;padding:10px 15px;margin-top:5px;display:inline-block}#s3-importer-options{padding:24px;background:#e7e7e7;margin-top:20px;border-radius:8px}#s3-importer-options h3{margin:0;padding:0;font-size:14px}#s3-importer-options ul{padding:0;display:flex;flex-direction:column;margin:20px 0 0}#s3-importer-options ul li{display:flex;margin-bottom:30px}#s3-importer-options ul li:last-of-type{margin-bottom:0}#s3-importer-options ul li>div:first-of-type{padding:10px 10px 20px 0;width:160px;min-width:160px;line-height:1.3;font-weight:600}#s3-importer-options ul li div.description{margin-top:8px}#s3-importer-options ul li div.option-ui{display:flex;align-items:center}#s3-importer-options ul li div.option-ui.option-ui-browser input[type=text]{width:40vw;margin-right:10px;padding:7px 11px;border-radius:4px}#s3-importer-options ul li div.option-ui.option-ui-browser input[type=text]:disabled{color:#000}#s3-timing-stats{display:none}#s3-importer-status-text{position:absolute;left:16px;top:0;bottom:0;right:16px;display:flex;align-items:center;color:#fff;font-weight:700}#s3-importer-thumbnails{position:relative;width:100%;height:150px;margin-bottom:15px}#s3-importer-thumbnails-container{position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;-webkit-mask-image:linear-gradient(90deg,#ddd 0,hsla(0,0%,86.7%,0) 90%);mask-image:linear-gradient(90deg,#ddd 0,hsla(0,0%,86.7%,0) 90%)}#s3-importer-thumbnails-container img{width:150px;height:150px;max-width:150px;max-height:150px;border-radius:4px}#s3-importer-thumbnails-container>img{margin-right:10px}#s3-importer-thumbnails-fade{background:linear-gradient(90deg,#ddd 0,hsla(0,0%,86.7%,0) 90%);position:absolute;left:150px;top:0;right:0;bottom:0}@supports ((-webkit-mask-image:linear-gradient(to left,rgba(221,221,221,0) 0%,#dddddd 95%,#dddddd 100%)) or (mask-image:linear-gradient(to left,rgba(221,221,221,0) 0%,#dddddd 95%,#dddddd 100%))){#s3-importer-thumbnails-fade{display:none}}#s3-importer-thumbnails-cloud{position:absolute;right:20px;top:50%;transform:translateY(-50%)}.s3-importer-thumb{position:absolute;left:0;top:0;width:150px;min-width:150px;max-width:150px;height:150px;min-height:150px;max-height:150px;background-size:cover;background-position:50%;background-repeat:no-repeat;margin-right:10px;border-radius:4px;background-color:#888;transition:opacity .25s linear,transform .25s linear}.s3-importer-thumb.ilab-hidden{opacity:0;transform:scale(.7)}.s3-importer-image-icon{position:absolute;left:0;top:0;position:relative;width:150px;min-width:150px;max-width:150px;height:150px;min-height:150px;max-height:150px;display:flex;align-items:center;justify-content:center;transition:opacity .25s linear,transform .25s linear}.s3-importer-image-icon.ilab-hidden{opacity:0;transform:scale(.8)}.s3-importer-info-warning{border:1px solid orange;padding:24px;background:rgba(255,165,0,.125);margin-top:20px;border-radius:8px}.s3-importer-info-warning h4{padding:0;font-size:14px;margin:0 0 8px} \ No newline at end of file diff --git a/readme.txt b/readme.txt index 4f90c5c2..b3541e2e 100755 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Requires at least: 4.4 Tested up to: 5.3 License: GPLv3 or later License URI: http://www.gnu.org/licenses/gpl-3.0.html -Stable tag: 3.3.7 +Stable tag: 3.3.8 Requires PHP: 5.6.4 Automatically store media on Amazon S3, Google Cloud Storage, DigitalOcean Spaces + others. Serve CSS/JS assets through CDNs. Integrate with Imgix. @@ -108,6 +108,11 @@ No, I'm just one very enthusiastic customer. == Changelog == += 3.3.8 = + +* Fix for errors on Task Manager pages caused by a library conflict with other plugins. +* When using the post editor after migrating an existing site to cloud storage, images appeared broken if the original images were deleted from the server. This is now fixed. + = 3.3.7 = * Massive improvement to background tasks performance. Processing times reduced by 50 to 90% in most cases. diff --git a/views/base/fields/image.blade.php b/views/base/fields/image.blade.php new file mode 100755 index 00000000..6ef19e47 --- /dev/null +++ b/views/base/fields/image.blade.php @@ -0,0 +1,65 @@ +
+ +
+
+
+ +
+
+ @if ($description) +

{!! $description !!}

+ @endif + @if($conditions) + + @endif + + + +