/** * Returns best-matching Locale object based on the Accept-Language header * provided as parameter. System default locale will be returned if no * successful matches were done. * * @param string $acceptLanguageHeader The Accept-Language HTTP header * @return \TYPO3\FLOW3\I18n\Locale Best-matching existing Locale instance * @api */ public function detectLocaleFromHttpHeader($acceptLanguageHeader) { $acceptableLanguages = \TYPO3\FLOW3\I18n\Utility::parseAcceptLanguageHeader($acceptLanguageHeader); if ($acceptableLanguages === FALSE) { return $this->localizationService->getConfiguration()->getDefaultLocale(); } foreach ($acceptableLanguages as $languageIdentifier) { if ($languageIdentifier === '*') { return $this->localizationService->getConfiguration()->getDefaultLocale(); } try { $locale = new \TYPO3\FLOW3\I18n\Locale($languageIdentifier); } catch (\TYPO3\FLOW3\I18n\Exception\InvalidLocaleIdentifierException $exception) { continue; } $bestMatchingLocale = $this->localeCollection->findBestMatchingLocale($locale); if ($bestMatchingLocale !== NULL) { return $bestMatchingLocale; } } return $this->localizationService->getConfiguration()->getDefaultLocale(); }
/** * Finds all Locale objects representing locales available in the * FLOW3 installation. This is done by scanning all Private and Public * resource files of all active packages, in order to find localized files. * * Localized files have a locale identifier added before their extension * (or at the end of filename, if no extension exists). For example, a * localized file for foobar.png, can be foobar.en.png, fobar.en_GB.png, etc. * * Just one localized resource file causes the corresponding locale to be * regarded as available (installed, supported). * * Note: result of this method invocation is cached * * @return void */ protected function generateAvailableLocalesCollectionByScanningFilesystem() { foreach ($this->packageManager->getActivePackages() as $activePackage) { $packageResourcesPath = $this->localeBasePath . $activePackage->getPackageKey() . '/'; if (!is_dir($packageResourcesPath)) { continue; } $directoryIterator = new \RecursiveDirectoryIterator($packageResourcesPath, \RecursiveDirectoryIterator::UNIX_PATHS); $recursiveIteratorIterator = new \RecursiveIteratorIterator($directoryIterator, \RecursiveIteratorIterator::SELF_FIRST); foreach ($recursiveIteratorIterator as $fileOrDirectory) { if ($fileOrDirectory->isFile()) { $localeIdentifier = Utility::extractLocaleTagFromFilename($fileOrDirectory->getFilename()); if ($localeIdentifier !== FALSE) { $this->localeCollection->addLocale(new Locale($localeIdentifier)); } } } } }
/** * Parses number in lenient mode. * * Lenient parsing ignores everything that can be ignored, and tries to * extract number from the string, even if it's not well formed. * * Implementation is simple but should work more often than strict parsing. * * Algorithm: * 1. Find first digit * 2. Find last digit * 3. Find decimal separator between first and last digit (if any) * 4. Remove non-digits from integer part * 5. Remove non-digits from decimal part (optional) * 6. Try to match negative prefix before first digit * 7. Try to match negative suffix after last digit * * @param string $numberToParse Number to be parsed * @param array $parsedFormat Parsed format (from NumbersReader) * @param array $localizedSymbols An array with symbols to use * @return mixed Parsed float number or FALSE on failure */ protected function doParsingInLenientMode($numberToParse, array $parsedFormat, array $localizedSymbols) { $numberIsNegative = FALSE; $positionOfFirstDigit = NULL; $positionOfLastDigit = NULL; $charactersOfNumberString = str_split($numberToParse); foreach ($charactersOfNumberString as $position => $character) { if (ord($character) >= 48 && ord($character) <= 57) { $positionOfFirstDigit = $position; break; } } if ($positionOfFirstDigit === NULL) { return FALSE; } krsort($charactersOfNumberString); foreach ($charactersOfNumberString as $position => $character) { if (ord($character) >= 48 && ord($character) <= 57) { $positionOfLastDigit = $position; break; } } $positionOfDecimalSeparator = strrpos($numberToParse, $localizedSymbols['decimal'], $positionOfFirstDigit); if ($positionOfDecimalSeparator === FALSE) { $integerPart = substr($numberToParse, $positionOfFirstDigit, $positionOfLastDigit - $positionOfFirstDigit + 1); $decimalPart = FALSE; } else { $integerPart = substr($numberToParse, $positionOfFirstDigit, $positionOfDecimalSeparator - $positionOfFirstDigit); $decimalPart = substr($numberToParse, $positionOfDecimalSeparator + 1, $positionOfLastDigit - $positionOfDecimalSeparator); } $parsedNumber = (int) preg_replace(self::PATTERN_MATCH_NOT_DIGITS, '', $integerPart); if ($decimalPart !== FALSE) { $decimalPart = (int) preg_replace(self::PATTERN_MATCH_NOT_DIGITS, '', $decimalPart); $parsedNumber = (double) ($parsedNumber . '.' . $decimalPart); } $partBeforeNumber = substr($numberToParse, 0, $positionOfFirstDigit); $partAfterNumber = substr($numberToParse, -(strlen($numberToParse) - $positionOfLastDigit - 1)); if (!empty($parsedFormat['negativePrefix']) && !empty($parsedFormat['negativeSuffix'])) { if (\TYPO3\FLOW3\I18n\Utility::stringEndsWith($partBeforeNumber, $parsedFormat['negativePrefix']) && \TYPO3\FLOW3\I18n\Utility::stringBeginsWith($partAfterNumber, $parsedFormat['negativeSuffix'])) { $numberIsNegative = TRUE; } } elseif (!empty($parsedFormat['negativePrefix']) && \TYPO3\FLOW3\I18n\Utility::stringEndsWith($partBeforeNumber, $parsedFormat['negativePrefix'])) { $numberIsNegative = TRUE; } elseif (!empty($parsedFormat['negativeSuffix']) && \TYPO3\FLOW3\I18n\Utility::stringBeginsWith($partAfterNumber, $parsedFormat['negativeSuffix'])) { $numberIsNegative = TRUE; } $parsedNumber /= $parsedFormat['multiplier']; if ($numberIsNegative) { $parsedNumber = 0 - $parsedNumber; } return $parsedNumber; }
/** * Parses date and / or time in strict mode. * * @param string $datetimeToParse Date/time to be parsed * @param array $parsedFormat Format parsed by DatesReader * @param array $localizedLiterals Array of date / time literals from CLDR * @return array Array of parsed date and / or time elements, FALSE on failure * @throws \TYPO3\FLOW3\I18n\Exception\InvalidArgumentException When unexpected symbol found in format * @see \TYPO3\FLOW3\I18n\Cldr\Reader\DatesReader */ protected function doParsingInStrictMode($datetimeToParse, array $parsedFormat, array $localizedLiterals) { $datetimeElements = array('year' => NULL, 'month' => NULL, 'day' => NULL, 'hour' => NULL, 'minute' => NULL, 'second' => NULL, 'timezone' => NULL); $using12HourClock = FALSE; $timeIsPm = FALSE; try { foreach ($parsedFormat as $subformat) { if (is_array($subformat)) { // This is literal string, should match exactly if (\TYPO3\FLOW3\I18n\Utility::stringBeginsWith($datetimeToParse, $subformat[0])) { $datetimeToParse = substr_replace($datetimeToParse, '', 0, strlen($subformat[0])); continue; } else { return FALSE; } } $lengthOfSubformat = strlen($subformat); $numberOfCharactersToRemove = 0; switch ($subformat[0]) { case 'h': case 'K': $hour = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 1, 12); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $hour < 10 ? 1 : 2; if ($subformat[0] === 'h' && $hour === 12) { $hour = 0; } $datetimeElements['hour'] = $hour; $using12HourClock = TRUE; break; case 'k': case 'H': $hour = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 1, 24); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $hour < 10 ? 1 : 2; if ($subformat[0] === 'k' && $hour === 24) { $hour = 0; } $datetimeElements['hour'] = $hour; break; case 'a': $dayPeriods = $localizedLiterals['dayPeriods']['format']['wide']; if (\TYPO3\FLOW3\I18n\Utility::stringBeginsWith($datetimeToParse, $dayPeriods['am'])) { $numberOfCharactersToRemove = strlen($dayPeriods['am']); } elseif (\TYPO3\FLOW3\I18n\Utility::stringBeginsWith($datetimeToParse, $dayPeriods['pm'])) { $timeIsPm = TRUE; $numberOfCharactersToRemove = strlen($dayPeriods['pm']); } else { return FALSE; } break; case 'm': $minute = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 0, 59); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $minute < 10 ? 1 : 2; $datetimeElements['minute'] = $minute; break; case 's': $second = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 0, 59); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $second < 10 ? 1 : 2; $datetimeElements['second'] = $second; break; case 'd': $dayOfTheMonth = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 1, 31); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $dayOfTheMonth < 10 ? 1 : 2; $datetimeElements['day'] = $dayOfTheMonth; break; case 'M': case 'L': $typeOfLiteral = $subformat[0] === 'L' ? 'stand-alone' : 'format'; if ($lengthOfSubformat <= 2) { $month = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 1, 12); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $month < 10 ? 1 : 2; } else { if ($lengthOfSubformat <= 4) { $lengthOfLiteral = $lengthOfSubformat === 3 ? 'abbreviated' : 'wide'; $month = 0; foreach ($localizedLiterals['months'][$typeOfLiteral][$lengthOfLiteral] as $monthId => $monthName) { if (\TYPO3\FLOW3\I18n\Utility::stringBeginsWith($datetimeToParse, $monthName)) { $month = $monthId; break; } } } else { throw new \TYPO3\FLOW3\I18n\Exception\InvalidArgumentException('Cannot parse formats with narrow month pattern as it is not unique.', 1279965245); } } if ($month === 0) { return FALSE; } $datetimeElements['month'] = $month; break; case 'y': if ($lengthOfSubformat === 2) { /** @todo How should the XX date be returned? Like 19XX? **/ $year = substr($datetimeToParse, 0, 2); $numberOfCharactersToRemove = 2; } else { $year = substr($datetimeToParse, 0, $lengthOfSubformat); for ($i = $lengthOfSubformat; $i < strlen($datetimeToParse); ++$i) { if (is_numeric($datetimeToParse[$i])) { $year .= $datetimeToParse[$i]; } else { break; } } $numberOfCharactersToRemove = $i; } if (!is_numeric($year)) { return FALSE; } $year = (int) $year; $datetimeElements['year'] = $year; break; case 'v': case 'z': if ($lengthOfSubformat <= 3) { $pattern = self::PATTERN_MATCH_STRICT_TIMEZONE_ABBREVIATION; } else { $pattern = self::PATTERN_MATCH_STRICT_TIMEZONE_TZ; } if (preg_match($pattern, $datetimeToParse, $matches) !== 1) { return FALSE; } $datetimeElements['timezone'] = $matches[0]; break; case 'D': case 'F': case 'w': case 'W': case 'Q': case 'q': case 'G': case 'S': case 'E': case 'Y': case 'u': case 'l': case 'g': case 'e': case 'c': case 'A': case 'Z': case 'V': // Silently ignore unsupported formats or formats that there is no need to parse break; default: throw new \TYPO3\FLOW3\I18n\Exception\InvalidArgumentException('Unexpected format symbol, "' . $subformat[0] . '" detected for date / time parsing.', 1279965528); } if ($using12HourClock && $timeIsPm) { $datetimeElements['hour'] += 12; $timeIsPm = FALSE; } if ($numberOfCharactersToRemove > 0) { $datetimeToParse = substr_replace($datetimeToParse, '', 0, $numberOfCharactersToRemove); } } } catch (\TYPO3\FLOW3\I18n\Parser\Exception\InvalidParseStringException $exception) { // Method extractAndCheckNumber() throws exception when constraints in $datetimeToParse are not fulfilled return FALSE; } return $datetimeElements; }
/** * @test * @dataProvider sampleHaystackStringsAndNeedleStrings */ public function stringIsFoundAtEndingOfAnotherString($haystack, $needle, $comparison) { $expectedResult = $comparison === 'ending' || $comparison === 'both' ? TRUE : FALSE; $result = \TYPO3\FLOW3\I18n\Utility::stringEndsWith($haystack, $needle); $this->assertEquals($expectedResult, $result); }