diff --git a/LICENSE b/LICENSE
index 111088f4..a8635af3 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2020 Tobias Krebs
+Copyright (c) 2021 Tobias Krebs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 263cc67c..c35c59aa 100644
--- a/README.md
+++ b/README.md
@@ -24,10 +24,11 @@ data/docs/
## Architecture
-The system is based on the well-known LAMP stack (Linux, Apache 2+, MySQL 5+, PHP 5.6+) and the popular and powerful
+The system is based on the well-known LAMP stack (Linux, Apache 2, MySQL 5+, PHP 5.6+) and the powerful
[Zend Framework 2](http://framework.zend.com/) (2.5).
-It is compatible with PHP version up to and including 7.2 - bot not PHP 7.3+. We are currently working on an upgrade of the underlying Zend Framework to make it compatible with PHP 7.3+.
+It is compatible with PHP version up to and including 7.4. We are currently working on an upgrade of the underlying
+Zend Framework to make it compatible with PHP 8.0.
Dependencies are managed with [composer](https://getcomposer.org/).
@@ -43,7 +44,9 @@ where you can find stable and (latest) development releases.
## Version
-The current version (May, 2020) is 1.6.4.
+The current version is 1.7.0 from January, 2021.
+
+Version 1.7.0 provides compatibility with PHP 7.4 by overriding and fixing some of the Zend Framework 2 components.
Version 1.6.4 introduced some features required during the COVID-19 pandemic, including limits to active concurrent bookings and minimum booking ranges. It also includes minor bug fixes and improvements.
diff --git a/VERSION b/VERSION
index 2014d04b..6741d1e4 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.6.4
+1.7.0
diff --git a/composer.json b/composer.json
index fe861cdc..9e90ce58 100644
--- a/composer.json
+++ b/composer.json
@@ -5,8 +5,15 @@
"homepage": "http://bs.hbsys.de/",
"require": {
- "php": ">=5.4 <=7.2",
+ "php": ">=5.6 <=7.4",
"ext-intl": "*",
"zendframework/zendframework": "^2.5.3"
+ },
+
+ "autoload": {
+ "psr-4": {
+ "Zend\\I18n\\": "module/Zend/I18n/src/",
+ "Zend\\View\\": "module/Zend/View/src/"
+ }
}
}
diff --git a/data/docs/update.txt b/data/docs/update.txt
index 537dc1d7..40ad27b9 100644
--- a/data/docs/update.txt
+++ b/data/docs/update.txt
@@ -9,7 +9,16 @@ After every update you should delete all files within the
directory.
If you haven't made any changes to the core files, your configuration, customizations and data should stay intact
-after the update. Otherwise, it's clearly your fault! :)
+after the update.
+
+
+
+--- Update to 1.7.0 ---
+
+Replace the following directories:
+
+- module/
+- vendor/
diff --git a/module/Zend/I18n/LICENSE.md b/module/Zend/I18n/LICENSE.md
new file mode 100644
index 00000000..d44ab5dc
--- /dev/null
+++ b/module/Zend/I18n/LICENSE.md
@@ -0,0 +1,27 @@
+Copyright (c) 2005-2018, Zend Technologies USA, Inc.
+All 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.
+
+- Neither the name of Zend Technologies USA, Inc. nor the names of its
+ contributors may 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.
diff --git a/module/Zend/I18n/README.md b/module/Zend/I18n/README.md
new file mode 100644
index 00000000..53dedb68
--- /dev/null
+++ b/module/Zend/I18n/README.md
@@ -0,0 +1,16 @@
+# zend-i18n
+
+[![Build Status](https://secure.travis-ci.org/zendframework/zend-i18n.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-i18n)
+[![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-i18n/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-i18n?branch=master)
+
+`Zend\I18n` comes with a complete translation suite which supports all major
+formats and includes popular features like plural translations and text domains.
+The Translator component is mostly dependency free, except for the fallback to a
+default locale, where it relies on the Intl PHP extension.
+
+The translator itself is initialized without any parameters, as any configuration
+to it is optional. A translator without any translations will actually do nothing
+but just return the given message IDs.
+
+- File issues at https://github.com/zendframework/zend-i18n/issues
+- Documentation is at https://docs.zendframework.com/zend-i18n/
diff --git a/module/Zend/I18n/src/ConfigProvider.php b/module/Zend/I18n/src/ConfigProvider.php
new file mode 100644
index 00000000..8ac07869
--- /dev/null
+++ b/module/Zend/I18n/src/ConfigProvider.php
@@ -0,0 +1,159 @@
+ $this->getDependencyConfig(),
+ 'filters' => $this->getFilterConfig(),
+ 'validators' => $this->getValidatorConfig(),
+ 'view_helpers' => $this->getViewHelperConfig(),
+ ];
+ }
+
+ /**
+ * Return application-level dependency configuration.
+ *
+ * @return array
+ */
+ public function getDependencyConfig()
+ {
+ return [
+ 'aliases' => [
+ 'TranslatorPluginManager' => Translator\LoaderPluginManager::class,
+ ],
+ 'factories' => [
+ Translator\TranslatorInterface::class => Translator\TranslatorServiceFactory::class,
+ Translator\LoaderPluginManager::class => Translator\LoaderPluginManagerFactory::class,
+ ],
+ ];
+ }
+
+ /**
+ * Return zend-filter configuration.
+ *
+ * @return array
+ */
+ public function getFilterConfig()
+ {
+ return [
+ 'aliases' => [
+ 'alnum' => Filter\Alnum::class,
+ 'Alnum' => Filter\Alnum::class,
+ 'alpha' => Filter\Alpha::class,
+ 'Alpha' => Filter\Alpha::class,
+ 'numberformat' => Filter\NumberFormat::class,
+ 'numberFormat' => Filter\NumberFormat::class,
+ 'NumberFormat' => Filter\NumberFormat::class,
+ 'numberparse' => Filter\NumberParse::class,
+ 'numberParse' => Filter\NumberParse::class,
+ 'NumberParse' => Filter\NumberParse::class,
+ ],
+ 'factories' => [
+ Filter\Alnum::class => InvokableFactory::class,
+ Filter\Alpha::class => InvokableFactory::class,
+ Filter\NumberFormat::class => InvokableFactory::class,
+ Filter\NumberParse::class => InvokableFactory::class,
+ ],
+ ];
+ }
+
+ /**
+ * Return zend-validator configuration.
+ *
+ * @return array
+ */
+ public function getValidatorConfig()
+ {
+ return [
+ 'aliases' => [
+ 'alnum' => Validator\Alnum::class,
+ 'Alnum' => Validator\Alnum::class,
+ 'alpha' => Validator\Alpha::class,
+ 'Alpha' => Validator\Alpha::class,
+ 'datetime' => Validator\DateTime::class,
+ 'dateTime' => Validator\DateTime::class,
+ 'DateTime' => Validator\DateTime::class,
+ 'float' => Validator\IsFloat::class,
+ 'Float' => Validator\IsFloat::class,
+ 'int' => Validator\IsInt::class,
+ 'Int' => Validator\IsInt::class,
+ 'isfloat' => Validator\IsFloat::class,
+ 'isFloat' => Validator\IsFloat::class,
+ 'IsFloat' => Validator\IsFloat::class,
+ 'isint' => Validator\IsInt::class,
+ 'isInt' => Validator\IsInt::class,
+ 'IsInt' => Validator\IsInt::class,
+ 'phonenumber' => Validator\PhoneNumber::class,
+ 'phoneNumber' => Validator\PhoneNumber::class,
+ 'PhoneNumber' => Validator\PhoneNumber::class,
+ 'postcode' => Validator\PostCode::class,
+ 'postCode' => Validator\PostCode::class,
+ 'PostCode' => Validator\PostCode::class,
+ ],
+ 'factories' => [
+ Validator\Alnum::class => InvokableFactory::class,
+ Validator\Alpha::class => InvokableFactory::class,
+ Validator\DateTime::class => InvokableFactory::class,
+ Validator\IsFloat::class => InvokableFactory::class,
+ Validator\IsInt::class => InvokableFactory::class,
+ Validator\PhoneNumber::class => InvokableFactory::class,
+ Validator\PostCode::class => InvokableFactory::class,
+ ],
+ ];
+ }
+
+ /**
+ * Return zend-view helper configuration.
+ *
+ * Obsoletes View\HelperConfig.
+ *
+ * @return array
+ */
+ public function getViewHelperConfig()
+ {
+ return [
+ 'aliases' => [
+ 'currencyformat' => View\Helper\CurrencyFormat::class,
+ 'currencyFormat' => View\Helper\CurrencyFormat::class,
+ 'CurrencyFormat' => View\Helper\CurrencyFormat::class,
+ 'dateformat' => View\Helper\DateFormat::class,
+ 'dateFormat' => View\Helper\DateFormat::class,
+ 'DateFormat' => View\Helper\DateFormat::class,
+ 'numberformat' => View\Helper\NumberFormat::class,
+ 'numberFormat' => View\Helper\NumberFormat::class,
+ 'NumberFormat' => View\Helper\NumberFormat::class,
+ 'plural' => View\Helper\Plural::class,
+ 'Plural' => View\Helper\Plural::class,
+ 'translate' => View\Helper\Translate::class,
+ 'Translate' => View\Helper\Translate::class,
+ 'translateplural' => View\Helper\TranslatePlural::class,
+ 'translatePlural' => View\Helper\TranslatePlural::class,
+ 'TranslatePlural' => View\Helper\TranslatePlural::class,
+ ],
+ 'factories' => [
+ View\Helper\CurrencyFormat::class => InvokableFactory::class,
+ View\Helper\DateFormat::class => InvokableFactory::class,
+ View\Helper\NumberFormat::class => InvokableFactory::class,
+ View\Helper\Plural::class => InvokableFactory::class,
+ View\Helper\Translate::class => InvokableFactory::class,
+ View\Helper\TranslatePlural::class => InvokableFactory::class,
+ ],
+ ];
+ }
+}
diff --git a/module/Zend/I18n/src/Exception/ExceptionInterface.php b/module/Zend/I18n/src/Exception/ExceptionInterface.php
new file mode 100644
index 00000000..6c6cfcdf
--- /dev/null
+++ b/module/Zend/I18n/src/Exception/ExceptionInterface.php
@@ -0,0 +1,14 @@
+options['locale'] = $locale;
+ return $this;
+ }
+
+ /**
+ * Returns the locale option
+ *
+ * @return string
+ */
+ public function getLocale()
+ {
+ if (! isset($this->options['locale'])) {
+ $this->options['locale'] = Locale::getDefault();
+ }
+ return $this->options['locale'];
+ }
+}
diff --git a/module/Zend/I18n/src/Filter/Alnum.php b/module/Zend/I18n/src/Filter/Alnum.php
new file mode 100644
index 00000000..702c35e4
--- /dev/null
+++ b/module/Zend/I18n/src/Filter/Alnum.php
@@ -0,0 +1,96 @@
+ null,
+ 'allow_white_space' => false,
+ ];
+
+ /**
+ * Sets default option values for this instance
+ *
+ * @param array|Traversable|bool|null $allowWhiteSpaceOrOptions
+ * @param string|null $locale
+ */
+ public function __construct($allowWhiteSpaceOrOptions = null, $locale = null)
+ {
+ parent::__construct();
+ if ($allowWhiteSpaceOrOptions !== null) {
+ if (static::isOptions($allowWhiteSpaceOrOptions)) {
+ $this->setOptions($allowWhiteSpaceOrOptions);
+ } else {
+ $this->setAllowWhiteSpace($allowWhiteSpaceOrOptions);
+ $this->setLocale($locale);
+ }
+ }
+ }
+
+ /**
+ * Sets the allowWhiteSpace option
+ *
+ * @param bool $flag
+ * @return Alnum Provides a fluent interface
+ */
+ public function setAllowWhiteSpace($flag = true)
+ {
+ $this->options['allow_white_space'] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Whether white space is allowed
+ *
+ * @return bool
+ */
+ public function getAllowWhiteSpace()
+ {
+ return $this->options['allow_white_space'];
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Returns $value as string with all non-alphanumeric characters removed
+ *
+ * @param string|array $value
+ * @return string|array
+ */
+ public function filter($value)
+ {
+ if (! is_scalar($value) && ! is_array($value)) {
+ return $value;
+ }
+
+ $whiteSpace = $this->options['allow_white_space'] ? '\s' : '';
+ $language = Locale::getPrimaryLanguage($this->getLocale());
+
+ if (! static::hasPcreUnicodeSupport()) {
+ // POSIX named classes are not supported, use alternative a-zA-Z0-9 match
+ $pattern = '/[^a-zA-Z0-9' . $whiteSpace . ']/';
+ } elseif ($language == 'ja'|| $language == 'ko' || $language == 'zh') {
+ // Use english alphabet
+ $pattern = '/[^a-zA-Z0-9' . $whiteSpace . ']/u';
+ } else {
+ // Use native language alphabet
+ $pattern = '/[^\p{L}\p{N}' . $whiteSpace . ']/u';
+ }
+
+ return preg_replace($pattern, '', $value);
+ }
+}
diff --git a/module/Zend/I18n/src/Filter/Alpha.php b/module/Zend/I18n/src/Filter/Alpha.php
new file mode 100644
index 00000000..80175d03
--- /dev/null
+++ b/module/Zend/I18n/src/Filter/Alpha.php
@@ -0,0 +1,46 @@
+options['allow_white_space'] ? '\s' : '';
+ $language = Locale::getPrimaryLanguage($this->getLocale());
+
+ if (! static::hasPcreUnicodeSupport()) {
+ // POSIX named classes are not supported, use alternative [a-zA-Z] match
+ $pattern = '/[^a-zA-Z' . $whiteSpace . ']/';
+ } elseif ($language == 'ja' || $language == 'ko' || $language == 'zh') {
+ // Use english alphabet
+ $pattern = '/[^a-zA-Z' . $whiteSpace . ']/u';
+ } else {
+ // Use native language alphabet
+ $pattern = '/[^\p{L}' . $whiteSpace . ']/u';
+ }
+
+ return preg_replace($pattern, '', $value);
+ }
+}
diff --git a/module/Zend/I18n/src/Filter/NumberFormat.php b/module/Zend/I18n/src/Filter/NumberFormat.php
new file mode 100644
index 00000000..865483e7
--- /dev/null
+++ b/module/Zend/I18n/src/Filter/NumberFormat.php
@@ -0,0 +1,45 @@
+getFormatter()->format($value, $this->getType());
+
+ ErrorHandler::stop();
+ }
+
+ if (false !== $result) {
+ return $result;
+ }
+
+ return $value;
+ }
+}
diff --git a/module/Zend/I18n/src/Filter/NumberParse.php b/module/Zend/I18n/src/Filter/NumberParse.php
new file mode 100644
index 00000000..f8f43633
--- /dev/null
+++ b/module/Zend/I18n/src/Filter/NumberParse.php
@@ -0,0 +1,162 @@
+ null,
+ 'style' => NumberFormatter::DEFAULT_STYLE,
+ 'type' => NumberFormatter::TYPE_DOUBLE
+ ];
+
+ /**
+ * @var NumberFormatter
+ */
+ protected $formatter = null;
+
+ /**
+ * @param array|Traversable|string|null $localeOrOptions
+ * @param int $style
+ * @param int $type
+ */
+ public function __construct(
+ $localeOrOptions = null,
+ $style = NumberFormatter::DEFAULT_STYLE,
+ $type = NumberFormatter::TYPE_DOUBLE
+ ) {
+ parent::__construct();
+ if ($localeOrOptions !== null) {
+ if ($localeOrOptions instanceof Traversable) {
+ $localeOrOptions = iterator_to_array($localeOrOptions);
+ }
+
+ if (! is_array($localeOrOptions)) {
+ $this->setLocale($localeOrOptions);
+ $this->setStyle($style);
+ $this->setType($type);
+ } else {
+ $this->setOptions($localeOrOptions);
+ }
+ }
+ }
+
+ /**
+ * @param string|null $locale
+ * @return NumberFormat
+ */
+ public function setLocale($locale = null)
+ {
+ $this->options['locale'] = $locale;
+ $this->formatter = null;
+ return $this;
+ }
+
+ /**
+ * @param int $style
+ * @return NumberFormat
+ */
+ public function setStyle($style)
+ {
+ $this->options['style'] = (int) $style;
+ $this->formatter = null;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getStyle()
+ {
+ return $this->options['style'];
+ }
+
+ /**
+ * @param int $type
+ * @return NumberFormat
+ */
+ public function setType($type)
+ {
+ $this->options['type'] = (int) $type;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getType()
+ {
+ return $this->options['type'];
+ }
+
+ /**
+ * @param NumberFormatter $formatter
+ * @return NumberFormat
+ */
+ public function setFormatter(NumberFormatter $formatter)
+ {
+ $this->formatter = $formatter;
+ return $this;
+ }
+
+ /**
+ * @return NumberFormatter
+ * @throws Exception\RuntimeException
+ */
+ public function getFormatter()
+ {
+ if ($this->formatter === null) {
+ $formatter = NumberFormatter::create($this->getLocale(), $this->getStyle());
+ if (! $formatter) {
+ throw new Exception\RuntimeException(
+ 'Can not create NumberFormatter instance; ' . intl_get_error_message()
+ );
+ }
+
+ $this->formatter = $formatter;
+ }
+
+ return $this->formatter;
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * @see Zend\Filter\FilterInterface::filter()
+ * @param mixed $value
+ * @return mixed
+ */
+ public function filter($value)
+ {
+ if (! is_int($value)
+ && ! is_float($value)
+ ) {
+ ErrorHandler::start();
+
+ $result = $this->getFormatter()->parse(
+ $value,
+ $this->getType()
+ );
+
+ ErrorHandler::stop();
+
+ if (false !== $result) {
+ return $result;
+ }
+ }
+
+ return $value;
+ }
+}
diff --git a/module/Zend/I18n/src/Module.php b/module/Zend/I18n/src/Module.php
new file mode 100644
index 00000000..6bd2012a
--- /dev/null
+++ b/module/Zend/I18n/src/Module.php
@@ -0,0 +1,47 @@
+ $provider->getFilterConfig(),
+ 'service_manager' => $provider->getDependencyConfig(),
+ 'validators' => $provider->getValidatorConfig(),
+ 'view_helpers' => $provider->getViewHelperConfig(),
+ ];
+ }
+
+ /**
+ * Register a specification for the TranslatorPluginManager with the ServiceListener.
+ *
+ * @param \Zend\ModuleManager\ModuleManager $moduleManager
+ * @return void
+ */
+ public function init($moduleManager)
+ {
+ $event = $moduleManager->getEvent();
+ $container = $event->getParam('ServiceManager');
+ $serviceListener = $container->get('ServiceListener');
+
+ $serviceListener->addServiceManager(
+ 'TranslatorPluginManager',
+ 'translator_plugins',
+ 'Zend\ModuleManager\Feature\TranslatorPluginProviderInterface',
+ 'getTranslatorPluginConfig'
+ );
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/Loader/AbstractFileLoader.php b/module/Zend/I18n/src/Translator/Loader/AbstractFileLoader.php
new file mode 100644
index 00000000..661de8bf
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/Loader/AbstractFileLoader.php
@@ -0,0 +1,82 @@
+useIncludePath = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Are we using the include_path to resolve translation files?
+ *
+ * @return bool
+ */
+ public function useIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Resolve a translation file
+ *
+ * Checks if the file exists and is readable, returning a boolean false if not; if the "useIncludePath"
+ * flag is enabled, it will attempt to resolve the file from the
+ * include_path if the file does not exist on the current working path.
+ *
+ * @param string $filename
+ * @return string|false
+ */
+ protected function resolveFile($filename)
+ {
+ if (! is_file($filename) || ! is_readable($filename)) {
+ if (! $this->useIncludePath()) {
+ return false;
+ }
+ return $this->resolveViaIncludePath($filename);
+ }
+ return $filename;
+ }
+
+ /**
+ * Resolve a translation file via the include_path
+ *
+ * @param string $filename
+ * @return string|false
+ */
+ protected function resolveViaIncludePath($filename)
+ {
+ $resolvedIncludePath = stream_resolve_include_path($filename);
+ if (! $resolvedIncludePath || ! is_file($resolvedIncludePath) || ! is_readable($resolvedIncludePath)) {
+ return false;
+ }
+ return $resolvedIncludePath;
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/Loader/FileLoaderInterface.php b/module/Zend/I18n/src/Translator/Loader/FileLoaderInterface.php
new file mode 100644
index 00000000..e387aaea
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/Loader/FileLoaderInterface.php
@@ -0,0 +1,25 @@
+resolveFile($filename);
+ if (! $resolvedFile) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Could not find or open file %s for reading',
+ $filename
+ ));
+ }
+
+ $textDomain = new TextDomain();
+
+ ErrorHandler::start();
+ $this->file = fopen($resolvedFile, 'rb');
+ $error = ErrorHandler::stop();
+ if (false === $this->file) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Could not open file %s for reading',
+ $filename
+ ), 0, $error);
+ }
+
+ // Verify magic number
+ $magic = fread($this->file, 4);
+
+ if ($magic == "\x95\x04\x12\xde") {
+ $this->littleEndian = false;
+ } elseif ($magic == "\xde\x12\x04\x95") {
+ $this->littleEndian = true;
+ } else {
+ fclose($this->file);
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s is not a valid gettext file',
+ $filename
+ ));
+ }
+
+ // Verify major revision (only 0 and 1 supported)
+ $majorRevision = ($this->readInteger() >> 16);
+
+ if ($majorRevision !== 0 && $majorRevision !== 1) {
+ fclose($this->file);
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s has an unknown major revision',
+ $filename
+ ));
+ }
+
+ // Gather main information
+ $numStrings = $this->readInteger();
+ $originalStringTableOffset = $this->readInteger();
+ $translationStringTableOffset = $this->readInteger();
+
+ // Usually there follow size and offset of the hash table, but we have
+ // no need for it, so we skip them.
+ fseek($this->file, $originalStringTableOffset);
+ $originalStringTable = $this->readIntegerList(2 * $numStrings);
+
+ fseek($this->file, $translationStringTableOffset);
+ $translationStringTable = $this->readIntegerList(2 * $numStrings);
+
+ // Read in all translations
+ for ($current = 0; $current < $numStrings; $current++) {
+ $sizeKey = $current * 2 + 1;
+ $offsetKey = $current * 2 + 2;
+ $originalStringSize = $originalStringTable[$sizeKey];
+ $originalStringOffset = $originalStringTable[$offsetKey];
+ $translationStringSize = $translationStringTable[$sizeKey];
+ $translationStringOffset = $translationStringTable[$offsetKey];
+
+ $originalString = [''];
+ if ($originalStringSize > 0) {
+ fseek($this->file, $originalStringOffset);
+ $originalString = explode("\0", fread($this->file, $originalStringSize));
+ }
+
+ if ($translationStringSize > 0) {
+ fseek($this->file, $translationStringOffset);
+ $translationString = explode("\0", fread($this->file, $translationStringSize));
+
+ if (count($originalString) > 1 && count($translationString) > 1) {
+ $textDomain[$originalString[0]] = $translationString;
+
+ array_shift($originalString);
+
+ foreach ($originalString as $string) {
+ if (! isset($textDomain[$string])) {
+ $textDomain[$string] = '';
+ }
+ }
+ } else {
+ $textDomain[$originalString[0]] = $translationString[0];
+ }
+ }
+ }
+
+ // Read header entries
+ if (array_key_exists('', $textDomain)) {
+ $rawHeaders = explode("\n", trim($textDomain['']));
+
+ foreach ($rawHeaders as $rawHeader) {
+ list($header, $content) = explode(':', $rawHeader, 2);
+
+ if (trim(strtolower($header)) === 'plural-forms') {
+ $textDomain->setPluralRule(PluralRule::fromString($content));
+ }
+ }
+
+ unset($textDomain['']);
+ }
+
+ fclose($this->file);
+
+ return $textDomain;
+ }
+
+ /**
+ * Read a single integer from the current file.
+ *
+ * @return int
+ */
+ protected function readInteger()
+ {
+ if ($this->littleEndian) {
+ $result = unpack('Vint', fread($this->file, 4));
+ } else {
+ $result = unpack('Nint', fread($this->file, 4));
+ }
+
+ return $result['int'];
+ }
+
+ /**
+ * Read an integer from the current file.
+ *
+ * @param int $num
+ * @return int
+ */
+ protected function readIntegerList($num)
+ {
+ if ($this->littleEndian) {
+ return unpack('V' . $num, fread($this->file, 4 * $num));
+ }
+
+ return unpack('N' . $num, fread($this->file, 4 * $num));
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/Loader/Ini.php b/module/Zend/I18n/src/Translator/Loader/Ini.php
new file mode 100644
index 00000000..e23a731c
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/Loader/Ini.php
@@ -0,0 +1,83 @@
+fromFile($fromIncludePath);
+
+ $list = $messagesNamespaced;
+ if (isset($messagesNamespaced['translation'])) {
+ $list = $messagesNamespaced['translation'];
+ }
+
+ foreach ($list as $message) {
+ if (! is_array($message) || count($message) < 2) {
+ throw new Exception\InvalidArgumentException(
+ 'Each INI row must be an array with message and translation'
+ );
+ }
+ if (isset($message['message']) && isset($message['translation'])) {
+ $messages[$message['message']] = $message['translation'];
+ continue;
+ }
+ $messages[array_shift($message)] = array_shift($message);
+ }
+
+ if (! is_array($messages)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Expected an array, but received %s',
+ gettype($messages)
+ ));
+ }
+
+ $textDomain = new TextDomain($messages);
+
+ if (array_key_exists('plural', $messagesNamespaced)
+ && isset($messagesNamespaced['plural']['plural_forms'])
+ ) {
+ $textDomain->setPluralRule(
+ PluralRule::fromString($messagesNamespaced['plural']['plural_forms'])
+ );
+ }
+
+ return $textDomain;
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/Loader/PhpArray.php b/module/Zend/I18n/src/Translator/Loader/PhpArray.php
new file mode 100644
index 00000000..1d6b8513
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/Loader/PhpArray.php
@@ -0,0 +1,64 @@
+setPluralRule(
+ PluralRule::fromString($textDomain['']['plural_forms'])
+ );
+ }
+
+ unset($textDomain['']);
+ }
+
+ return $textDomain;
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/Loader/PhpMemoryArray.php b/module/Zend/I18n/src/Translator/Loader/PhpMemoryArray.php
new file mode 100644
index 00000000..1d7cd3d8
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/Loader/PhpMemoryArray.php
@@ -0,0 +1,74 @@
+messages = $messages;
+ }
+
+ /**
+ * Load translations from a remote source.
+ *
+ * @param string $locale
+ * @param string $textDomain
+ *
+ * @throws \Zend\I18n\Exception\InvalidArgumentException
+ * @return \Zend\I18n\Translator\TextDomain|null
+ */
+ public function load($locale, $textDomain)
+ {
+ if (! is_array($this->messages)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Expected an array, but received %s', gettype($this->messages))
+ );
+ }
+
+ if (! isset($this->messages[$textDomain])) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Expected textdomain "%s" to be an array, but it is not set', $textDomain)
+ );
+ }
+
+ if (! isset($this->messages[$textDomain][$locale])) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Expected locale "%s" to be an array, but it is not set', $locale)
+ );
+ }
+
+ $textDomain = new TextDomain($this->messages[$textDomain][$locale]);
+
+ if (array_key_exists('', $textDomain)) {
+ if (isset($textDomain['']['plural_forms'])) {
+ $textDomain->setPluralRule(
+ PluralRule::fromString($textDomain['']['plural_forms'])
+ );
+ }
+
+ unset($textDomain['']);
+ }
+
+ return $textDomain;
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/Loader/RemoteLoaderInterface.php b/module/Zend/I18n/src/Translator/Loader/RemoteLoaderInterface.php
new file mode 100644
index 00000000..8ed1d8fb
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/Loader/RemoteLoaderInterface.php
@@ -0,0 +1,25 @@
+
+ * function ($translators) {
+ * $adapter = new Gettext();
+ * $adapter->setUseIncludePath(true);
+ * return $adapter;
+ * }
+ *
+ *
+ * You may need to override the Translator service factory to make this happen
+ * more easily. That can be done by extending it:
+ *
+ *
+ * use Zend\I18n\Translator\TranslatorServiceFactory;
+ * // or Zend\Mvc\I18n\TranslatorServiceFactory
+ * use Zend\ServiceManager\ServiceLocatorInterface;
+ *
+ * class MyTranslatorServiceFactory extends TranslatorServiceFactory
+ * {
+ * public function createService(ServiceLocatorInterface $services)
+ * {
+ * $translator = parent::createService($services);
+ * $translator->getLoaderPluginManager()->setFactory(...);
+ * return $translator;
+ * }
+ * }
+ *
+ *
+ * You would then specify your custom factory in your service configuration.
+ */
+class LoaderPluginManager extends AbstractPluginManager
+{
+ protected $aliases = [
+ 'gettext' => Loader\Gettext::class,
+ 'getText' => Loader\Gettext::class,
+ 'GetText' => Loader\Gettext::class,
+ 'ini' => Loader\Ini::class,
+ 'phparray' => Loader\PhpArray::class,
+ 'phpArray' => Loader\PhpArray::class,
+ 'PhpArray' => Loader\PhpArray::class,
+ ];
+
+ protected $factories = [
+ Loader\Gettext::class => InvokableFactory::class,
+ Loader\Ini::class => InvokableFactory::class,
+ Loader\PhpArray::class => InvokableFactory::class,
+ // Legacy (v2) due to alias resolution; canonical form of resolved
+ // alias is used to look up the factory, while the non-normalized
+ // resolved alias is used as the requested name passed to the factory.
+ 'zendi18ntranslatorloadergettext' => InvokableFactory::class,
+ 'zendi18ntranslatorloaderini' => InvokableFactory::class,
+ 'zendi18ntranslatorloaderphparray' => InvokableFactory::class
+ ];
+
+ /**
+ * Validate the plugin.
+ *
+ * Checks that the filter loaded is an instance of
+ * Loader\FileLoaderInterface or Loader\RemoteLoaderInterface.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\RuntimeException if invalid
+ */
+ public function validate($plugin)
+ {
+ if ($plugin instanceof Loader\FileLoaderInterface || $plugin instanceof Loader\RemoteLoaderInterface) {
+ // we're okay
+ return;
+ }
+
+ throw new InvalidServiceException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Loader\FileLoaderInterface '
+ . 'or %s\Loader\RemoteLoaderInterface',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__,
+ __NAMESPACE__
+ ));
+ }
+
+ /**
+ * Validate the plugin is of the expected type (v2).
+ *
+ * Proxies to `validate()`.
+ *
+ * @param mixed $plugin
+ * @throws Exception\RuntimeException
+ */
+ public function validatePlugin($plugin)
+ {
+ try {
+ $this->validate($plugin);
+ } catch (InvalidServiceException $e) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Loader\FileLoaderInterface '
+ . 'or %s\Loader\RemoteLoaderInterface',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__,
+ __NAMESPACE__
+ ));
+ }
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/LoaderPluginManagerFactory.php b/module/Zend/I18n/src/Translator/LoaderPluginManagerFactory.php
new file mode 100644
index 00000000..b995dbcb
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/LoaderPluginManagerFactory.php
@@ -0,0 +1,82 @@
+has('ServiceListener')) {
+ return $pluginManager;
+ }
+
+ // If we do not have a config service, nothing more to do
+ if (! $container->has('config')) {
+ return $pluginManager;
+ }
+
+ $config = $container->get('config');
+
+ // If we do not have translator_plugins configuration, nothing more to do
+ if (! isset($config['translator_plugins']) || ! is_array($config['translator_plugins'])) {
+ return $pluginManager;
+ }
+
+ // Wire service configuration for translator_plugins
+ (new Config($config['translator_plugins']))->configureServiceManager($pluginManager);
+
+ return $pluginManager;
+ }
+
+ /**
+ * zend-servicemanager v2 factory to return LoaderPluginManager
+ *
+ * @param ServiceLocatorInterface $container
+ * @return LoaderPluginManager
+ */
+ public function createService(ServiceLocatorInterface $container)
+ {
+ return $this($container, 'TranslatorPluginManager', $this->creationOptions);
+ }
+
+ /**
+ * v2 support for instance creation options.
+ *
+ * @param array $options
+ * @return void
+ */
+ public function setCreationOptions(array $options)
+ {
+ $this->creationOptions = $options;
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/Plural/Parser.php b/module/Zend/I18n/src/Translator/Plural/Parser.php
new file mode 100644
index 00000000..7b6830de
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/Plural/Parser.php
@@ -0,0 +1,363 @@
+.
+ */
+class Parser
+{
+ /**
+ * String to parse.
+ *
+ * @var string
+ */
+ protected $string;
+
+ /**
+ * Current lexer position in the string.
+ *
+ * @var int
+ */
+ protected $currentPos;
+
+ /**
+ * Current token.
+ *
+ * @var Symbol
+ */
+ protected $currentToken;
+
+ /**
+ * Table of symbols.
+ *
+ * @var array
+ */
+ protected $symbolTable = [];
+
+ /**
+ * Create a new plural parser.
+ *
+ */
+ public function __construct()
+ {
+ $this->populateSymbolTable();
+ }
+
+ /**
+ * Populate the symbol table.
+ *
+ * @return void
+ */
+ protected function populateSymbolTable()
+ {
+ // Ternary operators
+ $this->registerSymbol('?', 20)->setLeftDenotationGetter(
+ function (Symbol $self, Symbol $left) {
+ $self->first = $left;
+ $self->second = $self->parser->expression();
+ $self->parser->advance(':');
+ $self->third = $self->parser->expression();
+ return $self;
+ }
+ );
+ $this->registerSymbol(':');
+
+ // Boolean operators
+ $this->registerLeftInfixSymbol('||', 30);
+ $this->registerLeftInfixSymbol('&&', 40);
+
+ // Equal operators
+ $this->registerLeftInfixSymbol('==', 50);
+ $this->registerLeftInfixSymbol('!=', 50);
+
+ // Compare operators
+ $this->registerLeftInfixSymbol('>', 50);
+ $this->registerLeftInfixSymbol('<', 50);
+ $this->registerLeftInfixSymbol('>=', 50);
+ $this->registerLeftInfixSymbol('<=', 50);
+
+ // Add operators
+ $this->registerLeftInfixSymbol('-', 60);
+ $this->registerLeftInfixSymbol('+', 60);
+
+ // Multiply operators
+ $this->registerLeftInfixSymbol('*', 70);
+ $this->registerLeftInfixSymbol('/', 70);
+ $this->registerLeftInfixSymbol('%', 70);
+
+ // Not operator
+ $this->registerPrefixSymbol('!', 80);
+
+ // Literals
+ $this->registerSymbol('n')->setNullDenotationGetter(
+ function (Symbol $self) {
+ return $self;
+ }
+ );
+ $this->registerSymbol('number')->setNullDenotationGetter(
+ function (Symbol $self) {
+ return $self;
+ }
+ );
+
+ // Parentheses
+ $this->registerSymbol('(')->setNullDenotationGetter(
+ function (Symbol $self) {
+ $expression = $self->parser->expression();
+ $self->parser->advance(')');
+ return $expression;
+ }
+ );
+ $this->registerSymbol(')');
+
+ // Eof
+ $this->registerSymbol('eof');
+ }
+
+ /**
+ * Register a left infix symbol.
+ *
+ * @param string $id
+ * @param int $leftBindingPower
+ * @return void
+ */
+ protected function registerLeftInfixSymbol($id, $leftBindingPower)
+ {
+ $this->registerSymbol($id, $leftBindingPower)->setLeftDenotationGetter(
+ function (Symbol $self, Symbol $left) use ($leftBindingPower) {
+ $self->first = $left;
+ $self->second = $self->parser->expression($leftBindingPower);
+ return $self;
+ }
+ );
+ }
+
+ /**
+ * Register a right infix symbol.
+ *
+ * @param string $id
+ * @param int $leftBindingPower
+ * @return void
+ */
+ protected function registerRightInfixSymbol($id, $leftBindingPower)
+ {
+ $this->registerSymbol($id, $leftBindingPower)->setLeftDenotationGetter(
+ function (Symbol $self, Symbol $left) use ($leftBindingPower) {
+ $self->first = $left;
+ $self->second = $self->parser->expression($leftBindingPower - 1);
+ return $self;
+ }
+ );
+ }
+
+ /**
+ * Register a prefix symbol.
+ *
+ * @param string $id
+ * @param int $leftBindingPower
+ * @return void
+ */
+ protected function registerPrefixSymbol($id, $leftBindingPower)
+ {
+ $this->registerSymbol($id, $leftBindingPower)->setNullDenotationGetter(
+ function (Symbol $self) use ($leftBindingPower) {
+ $self->first = $self->parser->expression($leftBindingPower);
+ $self->second = null;
+ return $self;
+ }
+ );
+ }
+
+ /**
+ * Register a symbol.
+ *
+ * @param string $id
+ * @param int $leftBindingPower
+ * @return Symbol
+ */
+ protected function registerSymbol($id, $leftBindingPower = 0)
+ {
+ if (isset($this->symbolTable[$id])) {
+ $symbol = $this->symbolTable[$id];
+ $symbol->leftBindingPower = max(
+ $symbol->leftBindingPower,
+ $leftBindingPower
+ );
+ } else {
+ $symbol = new Symbol($this, $id, $leftBindingPower);
+ $this->symbolTable[$id] = $symbol;
+ }
+
+ return $symbol;
+ }
+
+ /**
+ * Get a new symbol.
+ *
+ * @param string $id
+ */
+ protected function getSymbol($id)
+ {
+ if (! isset($this->symbolTable[$id])) {
+ // Unknown symbol exception
+ }
+
+ return clone $this->symbolTable[$id];
+ }
+
+ /**
+ * Parse a string.
+ *
+ * @param string $string
+ * @return Symbol
+ */
+ public function parse($string)
+ {
+ $this->string = $string . "\0";
+ $this->currentPos = 0;
+ $this->currentToken = $this->getNextToken();
+
+ return $this->expression();
+ }
+
+ /**
+ * Parse an expression.
+ *
+ * @param int $rightBindingPower
+ * @return Symbol
+ */
+ public function expression($rightBindingPower = 0)
+ {
+ $token = $this->currentToken;
+ $this->currentToken = $this->getNextToken();
+ $left = $token->getNullDenotation();
+
+ while ($rightBindingPower < $this->currentToken->leftBindingPower) {
+ $token = $this->currentToken;
+ $this->currentToken = $this->getNextToken();
+ $left = $token->getLeftDenotation($left);
+ }
+
+ return $left;
+ }
+
+ /**
+ * Advance the current token and optionally check the old token id.
+ *
+ * @param string $id
+ * @return void
+ * @throws Exception\ParseException
+ */
+ public function advance($id = null)
+ {
+ if ($id !== null && $this->currentToken->id !== $id) {
+ throw new Exception\ParseException(
+ sprintf('Expected token with id %s but received %s', $id, $this->currentToken->id)
+ );
+ }
+
+ $this->currentToken = $this->getNextToken();
+ }
+
+ /**
+ * Get the next token.
+ *
+ * @return array
+ * @throws Exception\ParseException
+ */
+ protected function getNextToken()
+ {
+ while ($this->string[$this->currentPos] === ' ' || $this->string[$this->currentPos] === "\t") {
+ $this->currentPos++;
+ }
+
+ $result = $this->string[$this->currentPos++];
+ $value = null;
+
+ switch ($result) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ while (ctype_digit($this->string[$this->currentPos])) {
+ $result .= $this->string[$this->currentPos++];
+ }
+
+ $id = 'number';
+ $value = (int) $result;
+ break;
+
+ case '=':
+ case '&':
+ case '|':
+ if ($this->string[$this->currentPos] === $result) {
+ $this->currentPos++;
+ $id = $result . $result;
+ } else {
+ // Yield error
+ }
+ break;
+
+ case '!':
+ case '<':
+ case '>':
+ if ($this->string[$this->currentPos] === '=') {
+ $this->currentPos++;
+ $result .= '=';
+ }
+
+ $id = $result;
+ break;
+
+ case '*':
+ case '/':
+ case '%':
+ case '+':
+ case '-':
+ case 'n':
+ case '?':
+ case ':':
+ case '(':
+ case ')':
+ $id = $result;
+ break;
+
+ case ';':
+ case "\n":
+ case "\0":
+ $id = 'eof';
+ $this->currentPos--;
+ break;
+
+ default:
+ throw new Exception\ParseException(sprintf(
+ 'Found invalid character "%s" in input stream',
+ $result
+ ));
+ }
+
+ $token = $this->getSymbol($id);
+ $token->value = $value;
+
+ return $token;
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/Plural/Rule.php b/module/Zend/I18n/src/Translator/Plural/Rule.php
new file mode 100644
index 00000000..ecb3d9ee
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/Plural/Rule.php
@@ -0,0 +1,254 @@
+numPlurals = $numPlurals;
+ $this->ast = $ast;
+ }
+
+ /**
+ * Evaluate a number and return the plural index.
+ *
+ * @param int $number
+ * @return int
+ * @throws Exception\RangeException
+ */
+ public function evaluate($number)
+ {
+ $result = $this->evaluateAstPart($this->ast, abs((int) $number));
+
+ if ($result < 0 || $result >= $this->numPlurals) {
+ throw new Exception\RangeException(
+ sprintf('Calculated result %s is between 0 and %d', $result, ($this->numPlurals - 1))
+ );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get number of possible plural forms.
+ *
+ * @return int
+ */
+ public function getNumPlurals()
+ {
+ return $this->numPlurals;
+ }
+
+ /**
+ * Evaluate a part of an ast.
+ *
+ * @param array $ast
+ * @param int $number
+ * @return int
+ * @throws Exception\ParseException
+ */
+ protected function evaluateAstPart(array $ast, $number)
+ {
+ switch ($ast['id']) {
+ case 'number':
+ return $ast['arguments'][0];
+
+ case 'n':
+ return $number;
+
+ case '+':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ + $this->evaluateAstPart($ast['arguments'][1], $number);
+
+ case '-':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ - $this->evaluateAstPart($ast['arguments'][1], $number);
+
+ case '/':
+ // Integer division
+ return floor(
+ $this->evaluateAstPart($ast['arguments'][0], $number)
+ / $this->evaluateAstPart($ast['arguments'][1], $number)
+ );
+
+ case '*':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ * $this->evaluateAstPart($ast['arguments'][1], $number);
+
+ case '%':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ % $this->evaluateAstPart($ast['arguments'][1], $number);
+
+ case '>':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ > $this->evaluateAstPart($ast['arguments'][1], $number)
+ ? 1 : 0;
+
+ case '>=':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ >= $this->evaluateAstPart($ast['arguments'][1], $number)
+ ? 1 : 0;
+
+ case '<':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ < $this->evaluateAstPart($ast['arguments'][1], $number)
+ ? 1 : 0;
+
+ case '<=':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ <= $this->evaluateAstPart($ast['arguments'][1], $number)
+ ? 1 : 0;
+
+ case '==':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ == $this->evaluateAstPart($ast['arguments'][1], $number)
+ ? 1 : 0;
+
+ case '!=':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ != $this->evaluateAstPart($ast['arguments'][1], $number)
+ ? 1 : 0;
+
+ case '&&':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ && $this->evaluateAstPart($ast['arguments'][1], $number)
+ ? 1 : 0;
+
+ case '||':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ || $this->evaluateAstPart($ast['arguments'][1], $number)
+ ? 1 : 0;
+
+ case '!':
+ return ! $this->evaluateAstPart($ast['arguments'][0], $number)
+ ? 1 : 0;
+
+ case '?':
+ return $this->evaluateAstPart($ast['arguments'][0], $number)
+ ? $this->evaluateAstPart($ast['arguments'][1], $number)
+ : $this->evaluateAstPart($ast['arguments'][2], $number);
+
+ default:
+ throw new Exception\ParseException(sprintf(
+ 'Unknown token: %s',
+ $ast['id']
+ ));
+ }
+ }
+
+ /**
+ * Create a new rule from a string.
+ *
+ * @param string $string
+ * @throws Exception\ParseException
+ * @return Rule
+ */
+ public static function fromString($string)
+ {
+ if (static::$parser === null) {
+ static::$parser = new Parser();
+ }
+
+ if (! preg_match('(nplurals=(?P\d+))', $string, $match)) {
+ throw new Exception\ParseException(sprintf(
+ 'Unknown or invalid parser rule: %s',
+ $string
+ ));
+ }
+
+ $numPlurals = (int) $match['nplurals'];
+
+ if (! preg_match('(plural=(?P[^;\n]+))', $string, $match)) {
+ throw new Exception\ParseException(sprintf(
+ 'Unknown or invalid parser rule: %s',
+ $string
+ ));
+ }
+
+ $tree = static::$parser->parse($match['plural']);
+ $ast = static::createAst($tree);
+
+ return new static($numPlurals, $ast);
+ }
+
+ /**
+ * Create an AST from a tree.
+ *
+ * Theoretically we could just use the given Symbol, but that one is not
+ * so easy to serialize and also takes up more memory.
+ *
+ * @param Symbol $symbol
+ * @return array
+ */
+ protected static function createAst(Symbol $symbol)
+ {
+ $ast = ['id' => $symbol->id, 'arguments' => []];
+
+ switch ($symbol->id) {
+ case 'n':
+ break;
+
+ case 'number':
+ $ast['arguments'][] = $symbol->value;
+ break;
+
+ case '!':
+ $ast['arguments'][] = static::createAst($symbol->first);
+ break;
+
+ case '?':
+ $ast['arguments'][] = static::createAst($symbol->first);
+ $ast['arguments'][] = static::createAst($symbol->second);
+ $ast['arguments'][] = static::createAst($symbol->third);
+ break;
+
+ default:
+ $ast['arguments'][] = static::createAst($symbol->first);
+ $ast['arguments'][] = static::createAst($symbol->second);
+ break;
+ }
+
+ return $ast;
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/Plural/Symbol.php b/module/Zend/I18n/src/Translator/Plural/Symbol.php
new file mode 100644
index 00000000..7b33419e
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/Plural/Symbol.php
@@ -0,0 +1,160 @@
+parser = $parser;
+ $this->id = $id;
+ $this->leftBindingPower = $leftBindingPower;
+ }
+
+ /**
+ * Set the null denotation getter.
+ *
+ * @param Closure $getter
+ * @return Symbol
+ */
+ public function setNullDenotationGetter(Closure $getter)
+ {
+ $this->nullDenotationGetter = $getter;
+ return $this;
+ }
+
+ /**
+ * Set the left denotation getter.
+ *
+ * @param Closure $getter
+ * @return Symbol
+ */
+ public function setLeftDenotationGetter(Closure $getter)
+ {
+ $this->leftDenotationGetter = $getter;
+ return $this;
+ }
+
+ /**
+ * Get null denotation.
+ *
+ * @throws Exception\ParseException
+ * @return Symbol
+ */
+ public function getNullDenotation()
+ {
+ if ($this->nullDenotationGetter === null) {
+ throw new Exception\ParseException(sprintf('Syntax error: %s', $this->id));
+ }
+
+ /** @var callable $function */
+ $function = $this->nullDenotationGetter;
+ return $function($this);
+ }
+
+ /**
+ * Get left denotation.
+ *
+ * @param Symbol $left
+ * @throws Exception\ParseException
+ * @return Symbol
+ */
+ public function getLeftDenotation($left)
+ {
+ if ($this->leftDenotationGetter === null) {
+ throw new Exception\ParseException(sprintf('Unknown operator: %s', $this->id));
+ }
+
+ /** @var callable $function */
+ $function = $this->leftDenotationGetter;
+ return $function($this, $left);
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/TextDomain.php b/module/Zend/I18n/src/Translator/TextDomain.php
new file mode 100644
index 00000000..d74549ca
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/TextDomain.php
@@ -0,0 +1,118 @@
+pluralRule = $rule;
+ return $this;
+ }
+
+ /**
+ * Get the plural rule.
+ *
+ * @param bool $fallbackToDefaultRule
+ * @return PluralRule|null
+ */
+ public function getPluralRule($fallbackToDefaultRule = true)
+ {
+ if ($this->pluralRule === null && $fallbackToDefaultRule) {
+ return static::getDefaultPluralRule();
+ }
+
+ return $this->pluralRule;
+ }
+
+ /**
+ * Checks whether the text domain has a plural rule.
+ *
+ * @return bool
+ */
+ public function hasPluralRule()
+ {
+ return ($this->pluralRule !== null);
+ }
+
+ /**
+ * Returns a shared default plural rule.
+ *
+ * @return PluralRule
+ */
+ public static function getDefaultPluralRule()
+ {
+ if (static::$defaultPluralRule === null) {
+ static::$defaultPluralRule = PluralRule::fromString('nplurals=2; plural=n != 1;');
+ }
+
+ return static::$defaultPluralRule;
+ }
+
+ /**
+ * Merge another text domain with the current one.
+ *
+ * The plural rule of both text domains must be compatible for a successful
+ * merge. We are only validating the number of plural forms though, as the
+ * same rule could be made up with different expression.
+ *
+ * @param TextDomain $textDomain
+ * @return TextDomain
+ * @throws Exception\RuntimeException
+ */
+ public function merge(TextDomain $textDomain)
+ {
+ if ($this->hasPluralRule() && $textDomain->hasPluralRule()) {
+ if ($this->getPluralRule()->getNumPlurals() !== $textDomain->getPluralRule()->getNumPlurals()) {
+ throw new Exception\RuntimeException(
+ 'Plural rule of merging text domain is not compatible with the current one'
+ );
+ }
+ } elseif ($textDomain->hasPluralRule()) {
+ $this->setPluralRule($textDomain->getPluralRule());
+ }
+
+ $this->exchangeArray(
+ array_replace(
+ $this->getArrayCopy(),
+ $textDomain->getArrayCopy()
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/Translator.php b/module/Zend/I18n/src/Translator/Translator.php
new file mode 100644
index 00000000..578387af
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/Translator.php
@@ -0,0 +1,840 @@
+setLocale(array_shift($locales));
+ if (count($locales) > 0) {
+ $translator->setFallbackLocale(array_shift($locales));
+ }
+ }
+
+ // file patterns
+ if (isset($options['translation_file_patterns'])) {
+ if (! is_array($options['translation_file_patterns'])) {
+ throw new Exception\InvalidArgumentException(
+ '"translation_file_patterns" should be an array'
+ );
+ }
+
+ $requiredKeys = ['type', 'base_dir', 'pattern'];
+ foreach ($options['translation_file_patterns'] as $pattern) {
+ foreach ($requiredKeys as $key) {
+ if (! isset($pattern[$key])) {
+ throw new Exception\InvalidArgumentException(
+ "'{$key}' is missing for translation pattern options"
+ );
+ }
+ }
+
+ $translator->addTranslationFilePattern(
+ $pattern['type'],
+ $pattern['base_dir'],
+ $pattern['pattern'],
+ isset($pattern['text_domain']) ? $pattern['text_domain'] : 'default'
+ );
+ }
+ }
+
+ // files
+ if (isset($options['translation_files'])) {
+ if (! is_array($options['translation_files'])) {
+ throw new Exception\InvalidArgumentException(
+ '"translation_files" should be an array'
+ );
+ }
+
+ $requiredKeys = ['type', 'filename'];
+ foreach ($options['translation_files'] as $file) {
+ foreach ($requiredKeys as $key) {
+ if (! isset($file[$key])) {
+ throw new Exception\InvalidArgumentException(
+ "'{$key}' is missing for translation file options"
+ );
+ }
+ }
+
+ $translator->addTranslationFile(
+ $file['type'],
+ $file['filename'],
+ isset($file['text_domain']) ? $file['text_domain'] : 'default',
+ isset($file['locale']) ? $file['locale'] : null
+ );
+ }
+ }
+
+ // remote
+ if (isset($options['remote_translation'])) {
+ if (! is_array($options['remote_translation'])) {
+ throw new Exception\InvalidArgumentException(
+ '"remote_translation" should be an array'
+ );
+ }
+
+ $requiredKeys = ['type'];
+ foreach ($options['remote_translation'] as $remote) {
+ foreach ($requiredKeys as $key) {
+ if (! isset($remote[$key])) {
+ throw new Exception\InvalidArgumentException(
+ "'{$key}' is missing for remote translation options"
+ );
+ }
+ }
+
+ $translator->addRemoteTranslations(
+ $remote['type'],
+ isset($remote['text_domain']) ? $remote['text_domain'] : 'default'
+ );
+ }
+ }
+
+ // cache
+ if (isset($options['cache'])) {
+ if ($options['cache'] instanceof CacheStorage) {
+ $translator->setCache($options['cache']);
+ } else {
+ $translator->setCache(Cache\StorageFactory::factory($options['cache']));
+ }
+ }
+
+ // event manager enabled
+ if (isset($options['event_manager_enabled']) && $options['event_manager_enabled']) {
+ $translator->enableEventManager();
+ }
+
+ return $translator;
+ }
+
+ /**
+ * Set the default locale.
+ *
+ * @param string $locale
+ * @return Translator
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = $locale;
+
+ return $this;
+ }
+
+ /**
+ * Get the default locale.
+ *
+ * @return string
+ * @throws Exception\ExtensionNotLoadedException if ext/intl is not present and no locale set
+ */
+ public function getLocale()
+ {
+ if ($this->locale === null) {
+ if (! extension_loaded('intl')) {
+ throw new Exception\ExtensionNotLoadedException(sprintf(
+ '%s component requires the intl PHP extension',
+ __NAMESPACE__
+ ));
+ }
+ $this->locale = Locale::getDefault();
+ }
+
+ return $this->locale;
+ }
+
+ /**
+ * Set the fallback locale.
+ *
+ * @param string $locale
+ * @return Translator
+ */
+ public function setFallbackLocale($locale)
+ {
+ $this->fallbackLocale = $locale;
+
+ return $this;
+ }
+
+ /**
+ * Get the fallback locale.
+ *
+ * @return string
+ */
+ public function getFallbackLocale()
+ {
+ return $this->fallbackLocale;
+ }
+
+ /**
+ * Sets a cache
+ *
+ * @param CacheStorage $cache
+ * @return Translator
+ */
+ public function setCache(CacheStorage $cache = null)
+ {
+ $this->cache = $cache;
+
+ return $this;
+ }
+
+ /**
+ * Returns the set cache
+ *
+ * @return CacheStorage The set cache
+ */
+ public function getCache()
+ {
+ return $this->cache;
+ }
+
+ /**
+ * Set the plugin manager for translation loaders
+ *
+ * @param LoaderPluginManager $pluginManager
+ * @return Translator
+ */
+ public function setPluginManager(LoaderPluginManager $pluginManager)
+ {
+ $this->pluginManager = $pluginManager;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the plugin manager for translation loaders.
+ *
+ * Lazy loads an instance if none currently set.
+ *
+ * @return LoaderPluginManager
+ */
+ public function getPluginManager()
+ {
+ if (! $this->pluginManager instanceof LoaderPluginManager) {
+ $this->setPluginManager(new LoaderPluginManager(new ServiceManager));
+ }
+
+ return $this->pluginManager;
+ }
+
+ /**
+ * Translate a message.
+ *
+ * @param string $message
+ * @param string $textDomain
+ * @param string $locale
+ * @return string
+ */
+ public function translate($message, $textDomain = 'default', $locale = null)
+ {
+ $locale = ($locale ?: $this->getLocale());
+ $translation = $this->getTranslatedMessage($message, $locale, $textDomain);
+
+ if ($translation !== null && $translation !== '') {
+ return $translation;
+ }
+
+ if (null !== ($fallbackLocale = $this->getFallbackLocale())
+ && $locale !== $fallbackLocale
+ ) {
+ return $this->translate($message, $textDomain, $fallbackLocale);
+ }
+
+ return $message;
+ }
+
+ /**
+ * Translate a plural message.
+ *
+ * @param string $singular
+ * @param string $plural
+ * @param int $number
+ * @param string $textDomain
+ * @param string|null $locale
+ * @return string
+ * @throws Exception\OutOfBoundsException
+ */
+ public function translatePlural(
+ $singular,
+ $plural,
+ $number,
+ $textDomain = 'default',
+ $locale = null
+ ) {
+ $locale = $locale ?: $this->getLocale();
+ $translation = $this->getTranslatedMessage($singular, $locale, $textDomain);
+
+ if ($translation === null || $translation === '') {
+ if (null !== ($fallbackLocale = $this->getFallbackLocale())
+ && $locale !== $fallbackLocale
+ ) {
+ return $this->translatePlural(
+ $singular,
+ $plural,
+ $number,
+ $textDomain,
+ $fallbackLocale
+ );
+ }
+
+ return ($number == 1 ? $singular : $plural);
+ } elseif (is_string($translation)) {
+ $translation = [$translation];
+ }
+
+ $index = $this->messages[$textDomain][$locale]
+ ->getPluralRule()
+ ->evaluate($number);
+
+ if (! isset($translation[$index])) {
+ throw new Exception\OutOfBoundsException(
+ sprintf('Provided index %d does not exist in plural array', $index)
+ );
+ }
+
+ return $translation[$index];
+ }
+
+ /**
+ * Get a translated message.
+ *
+ * @triggers getTranslatedMessage.missing-translation
+ * @param string $message
+ * @param string $locale
+ * @param string $textDomain
+ * @return string|null
+ */
+ protected function getTranslatedMessage(
+ $message,
+ $locale,
+ $textDomain = 'default'
+ ) {
+ if ($message === '' || $message === null) {
+ return '';
+ }
+
+ if (! isset($this->messages[$textDomain][$locale])) {
+ $this->loadMessages($textDomain, $locale);
+ }
+
+ if (isset($this->messages[$textDomain][$locale][$message])) {
+ return $this->messages[$textDomain][$locale][$message];
+ }
+
+
+ /**
+ * issue https://github.com/zendframework/zend-i18n/issues/53
+ *
+ * storage: array:8 [▼
+ * "default\x04Welcome" => "Cześć"
+ * "default\x04Top %s Product" => array:3 [▼
+ * 0 => "Top %s Produkt"
+ * 1 => "Top %s Produkty"
+ * 2 => "Top %s Produktów"
+ * ]
+ * "Top %s Products" => ""
+ * ]
+ */
+ if (isset($this->messages[$textDomain][$locale][$textDomain . "\x04" . $message])) {
+ return $this->messages[$textDomain][$locale][$textDomain . "\x04" . $message];
+ }
+
+ if ($this->isEventManagerEnabled()) {
+ $until = function ($r) {
+ return is_string($r);
+ };
+
+ $event = new Event(self::EVENT_MISSING_TRANSLATION, $this, [
+ 'message' => $message,
+ 'locale' => $locale,
+ 'text_domain' => $textDomain,
+ ]);
+
+ $results = $this->getEventManager()->triggerEventUntil($until, $event);
+
+ $last = $results->last();
+ if (is_string($last)) {
+ return $last;
+ }
+ }
+
+ return;
+ }
+
+ /**
+ * Add a translation file.
+ *
+ * @param string $type
+ * @param string $filename
+ * @param string $textDomain
+ * @param string $locale
+ * @return Translator
+ */
+ public function addTranslationFile(
+ $type,
+ $filename,
+ $textDomain = 'default',
+ $locale = null
+ ) {
+ $locale = $locale ?: '*';
+
+ if (! isset($this->files[$textDomain])) {
+ $this->files[$textDomain] = [];
+ }
+
+ $this->files[$textDomain][$locale][] = [
+ 'type' => $type,
+ 'filename' => $filename,
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Add multiple translations with a file pattern.
+ *
+ * @param string $type
+ * @param string $baseDir
+ * @param string $pattern
+ * @param string $textDomain
+ * @return Translator
+ */
+ public function addTranslationFilePattern(
+ $type,
+ $baseDir,
+ $pattern,
+ $textDomain = 'default'
+ ) {
+ if (! isset($this->patterns[$textDomain])) {
+ $this->patterns[$textDomain] = [];
+ }
+
+ $this->patterns[$textDomain][] = [
+ 'type' => $type,
+ 'baseDir' => rtrim($baseDir, '/'),
+ 'pattern' => $pattern,
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Add remote translations.
+ *
+ * @param string $type
+ * @param string $textDomain
+ * @return Translator
+ */
+ public function addRemoteTranslations($type, $textDomain = 'default')
+ {
+ if (! isset($this->remote[$textDomain])) {
+ $this->remote[$textDomain] = [];
+ }
+
+ $this->remote[$textDomain][] = $type;
+
+ return $this;
+ }
+
+ /**
+ * Get the cache identifier for a specific textDomain and locale.
+ *
+ * @param string $textDomain
+ * @param string $locale
+ * @return string
+ */
+ public function getCacheId($textDomain, $locale)
+ {
+ return 'Zend_I18n_Translator_Messages_' . md5($textDomain . $locale);
+ }
+
+ /**
+ * Clears the cache for a specific textDomain and locale.
+ *
+ * @param string $textDomain
+ * @param string $locale
+ * @return bool
+ */
+ public function clearCache($textDomain, $locale)
+ {
+ if (null === ($cache = $this->getCache())) {
+ return false;
+ }
+ return $cache->removeItem($this->getCacheId($textDomain, $locale));
+ }
+
+ /**
+ * Load messages for a given language and domain.
+ *
+ * @triggers loadMessages.no-messages-loaded
+ * @param string $textDomain
+ * @param string $locale
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ protected function loadMessages($textDomain, $locale)
+ {
+ if (! isset($this->messages[$textDomain])) {
+ $this->messages[$textDomain] = [];
+ }
+
+ if (null !== ($cache = $this->getCache())) {
+ $cacheId = $this->getCacheId($textDomain, $locale);
+
+ if (null !== ($result = $cache->getItem($cacheId))) {
+ $this->messages[$textDomain][$locale] = $result;
+
+ return;
+ }
+ }
+
+ $messagesLoaded = false;
+ $messagesLoaded |= $this->loadMessagesFromRemote($textDomain, $locale);
+ $messagesLoaded |= $this->loadMessagesFromPatterns($textDomain, $locale);
+ $messagesLoaded |= $this->loadMessagesFromFiles($textDomain, $locale);
+
+ if (! $messagesLoaded) {
+ $discoveredTextDomain = null;
+ if ($this->isEventManagerEnabled()) {
+ $until = function ($r) {
+ return ($r instanceof TextDomain);
+ };
+
+ $event = new Event(self::EVENT_NO_MESSAGES_LOADED, $this, [
+ 'locale' => $locale,
+ 'text_domain' => $textDomain,
+ ]);
+
+ $results = $this->getEventManager()->triggerEventUntil($until, $event);
+
+ $last = $results->last();
+ if ($last instanceof TextDomain) {
+ $discoveredTextDomain = $last;
+ }
+ }
+
+ $this->messages[$textDomain][$locale] = $discoveredTextDomain;
+ $messagesLoaded = true;
+ }
+
+ if ($messagesLoaded && $cache !== null) {
+ $cache->setItem($cacheId, $this->messages[$textDomain][$locale]);
+ }
+ }
+
+ /**
+ * Load messages from remote sources.
+ *
+ * @param string $textDomain
+ * @param string $locale
+ * @return bool
+ * @throws Exception\RuntimeException When specified loader is not a remote loader
+ */
+ protected function loadMessagesFromRemote($textDomain, $locale)
+ {
+ $messagesLoaded = false;
+
+ if (isset($this->remote[$textDomain])) {
+ foreach ($this->remote[$textDomain] as $loaderType) {
+ $loader = $this->getPluginManager()->get($loaderType);
+
+ if (! $loader instanceof RemoteLoaderInterface) {
+ throw new Exception\RuntimeException('Specified loader is not a remote loader');
+ }
+
+ if (isset($this->messages[$textDomain][$locale])) {
+ $this->messages[$textDomain][$locale]->merge($loader->load($locale, $textDomain));
+ } else {
+ $this->messages[$textDomain][$locale] = $loader->load($locale, $textDomain);
+ }
+
+ $messagesLoaded = true;
+ }
+ }
+
+ return $messagesLoaded;
+ }
+
+ /**
+ * Load messages from patterns.
+ *
+ * @param string $textDomain
+ * @param string $locale
+ * @return bool
+ * @throws Exception\RuntimeException When specified loader is not a file loader
+ */
+ protected function loadMessagesFromPatterns($textDomain, $locale)
+ {
+ $messagesLoaded = false;
+
+ if (isset($this->patterns[$textDomain])) {
+ foreach ($this->patterns[$textDomain] as $pattern) {
+ $filename = $pattern['baseDir'] . '/' . sprintf($pattern['pattern'], $locale);
+
+ if (is_file($filename)) {
+ $loader = $this->getPluginManager()->get($pattern['type']);
+
+ if (! $loader instanceof FileLoaderInterface) {
+ throw new Exception\RuntimeException('Specified loader is not a file loader');
+ }
+
+ if (isset($this->messages[$textDomain][$locale])) {
+ $this->messages[$textDomain][$locale]->merge($loader->load($locale, $filename));
+ } else {
+ $this->messages[$textDomain][$locale] = $loader->load($locale, $filename);
+ }
+
+ $messagesLoaded = true;
+ }
+ }
+ }
+
+ return $messagesLoaded;
+ }
+
+ /**
+ * Load messages from files.
+ *
+ * @param string $textDomain
+ * @param string $locale
+ * @return bool
+ * @throws Exception\RuntimeException When specified loader is not a file loader
+ */
+ protected function loadMessagesFromFiles($textDomain, $locale)
+ {
+ $messagesLoaded = false;
+
+ foreach ([$locale, '*'] as $currentLocale) {
+ if (! isset($this->files[$textDomain][$currentLocale])) {
+ continue;
+ }
+
+ foreach ($this->files[$textDomain][$currentLocale] as $file) {
+ $loader = $this->getPluginManager()->get($file['type']);
+
+ if (! $loader instanceof FileLoaderInterface) {
+ throw new Exception\RuntimeException('Specified loader is not a file loader');
+ }
+
+ if (isset($this->messages[$textDomain][$locale])) {
+ $this->messages[$textDomain][$locale]->merge($loader->load($locale, $file['filename']));
+ } else {
+ $this->messages[$textDomain][$locale] = $loader->load($locale, $file['filename']);
+ }
+
+ $messagesLoaded = true;
+ }
+
+ unset($this->files[$textDomain][$currentLocale]);
+ }
+
+ return $messagesLoaded;
+ }
+
+ /**
+ * Return all the messages.
+ *
+ * @param string $textDomain
+ * @param null $locale
+ *
+ * @return mixed
+ */
+ public function getAllMessages($textDomain = 'default', $locale = null)
+ {
+ $locale = $locale ?: $this->getLocale();
+
+ if (! isset($this->messages[$textDomain][$locale])) {
+ $this->loadMessages($textDomain, $locale);
+ }
+
+ return $this->messages[$textDomain][$locale];
+ }
+
+ /**
+ * Get the event manager.
+ *
+ * @return EventManagerInterface|null
+ */
+ public function getEventManager()
+ {
+ if (! $this->events instanceof EventManagerInterface) {
+ $this->setEventManager(new EventManager());
+ }
+
+ return $this->events;
+ }
+
+ /**
+ * Set the event manager instance used by this translator.
+ *
+ * @param EventManagerInterface $events
+ * @return Translator
+ */
+ public function setEventManager(EventManagerInterface $events)
+ {
+ $events->setIdentifiers([
+ __CLASS__,
+ get_class($this),
+ 'translator',
+ ]);
+ $this->events = $events;
+ return $this;
+ }
+
+ /**
+ * Check whether the event manager is enabled.
+ *
+ * @return boolean
+ */
+ public function isEventManagerEnabled()
+ {
+ return $this->eventsEnabled;
+ }
+
+ /**
+ * Enable the event manager.
+ *
+ * @return Translator
+ */
+ public function enableEventManager()
+ {
+ $this->eventsEnabled = true;
+ return $this;
+ }
+
+ /**
+ * Disable the event manager.
+ *
+ * @return Translator
+ */
+ public function disableEventManager()
+ {
+ $this->eventsEnabled = false;
+ return $this;
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/TranslatorAwareInterface.php b/module/Zend/I18n/src/Translator/TranslatorAwareInterface.php
new file mode 100644
index 00000000..aaebf616
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/TranslatorAwareInterface.php
@@ -0,0 +1,69 @@
+translator = $translator;
+
+ if (null !== $textDomain) {
+ $this->setTranslatorTextDomain($textDomain);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns translator used in object
+ *
+ * @return TranslatorInterface
+ */
+ public function getTranslator()
+ {
+ return $this->translator;
+ }
+
+ /**
+ * Checks if the object has a translator
+ *
+ * @return bool
+ */
+ public function hasTranslator()
+ {
+ return (null !== $this->translator);
+ }
+
+ /**
+ * Sets whether translator is enabled and should be used
+ *
+ * @param bool $enabled
+ * @return mixed
+ */
+ public function setTranslatorEnabled($enabled = true)
+ {
+ $this->translatorEnabled = $enabled;
+
+ return $this;
+ }
+
+ /**
+ * Returns whether translator is enabled and should be used
+ *
+ * @return bool
+ */
+ public function isTranslatorEnabled()
+ {
+ return $this->translatorEnabled;
+ }
+
+ /**
+ * Set translation text domain
+ *
+ * @param string $textDomain
+ * @return mixed
+ */
+ public function setTranslatorTextDomain($textDomain = 'default')
+ {
+ $this->translatorTextDomain = $textDomain;
+
+ return $this;
+ }
+
+ /**
+ * Return the translation text domain
+ *
+ * @return string
+ */
+ public function getTranslatorTextDomain()
+ {
+ return $this->translatorTextDomain;
+ }
+}
diff --git a/module/Zend/I18n/src/Translator/TranslatorInterface.php b/module/Zend/I18n/src/Translator/TranslatorInterface.php
new file mode 100644
index 00000000..43986b6b
--- /dev/null
+++ b/module/Zend/I18n/src/Translator/TranslatorInterface.php
@@ -0,0 +1,44 @@
+get('config');
+ $trConfig = isset($config['translator']) ? $config['translator'] : [];
+ $translator = Translator::factory($trConfig);
+ if ($container->has('TranslatorPluginManager')) {
+ $translator->setPluginManager($container->get('TranslatorPluginManager'));
+ }
+ return $translator;
+ }
+
+ /**
+ * zend-servicemanager v2 factory for creating Translator instance.
+ *
+ * Proxies to `__invoke()`.
+ *
+ * @param ServiceLocatorInterface $serviceLocator
+ * @return Translator
+ */
+ public function createService(ServiceLocatorInterface $serviceLocator)
+ {
+ return $this($serviceLocator, Translator::class);
+ }
+}
diff --git a/module/Zend/I18n/src/Validator/Alnum.php b/module/Zend/I18n/src/Validator/Alnum.php
new file mode 100644
index 00000000..ac10df96
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/Alnum.php
@@ -0,0 +1,117 @@
+ "Invalid type given. String, integer or float expected",
+ self::NOT_ALNUM => "The input contains characters which are non alphabetic and no digits",
+ self::STRING_EMPTY => "The input is an empty string",
+ ];
+
+ /**
+ * Options for this validator
+ *
+ * @var array
+ */
+ protected $options = [
+ 'allowWhiteSpace' => false, // Whether to allow white space characters; off by default
+ ];
+
+ /**
+ * Sets default option values for this instance
+ *
+ * @param bool $allowWhiteSpace
+ */
+ public function __construct($allowWhiteSpace = false)
+ {
+ $options = is_array($allowWhiteSpace) ? $allowWhiteSpace : null;
+ parent::__construct($options);
+
+ if (is_scalar($allowWhiteSpace)) {
+ $this->options['allowWhiteSpace'] = (bool) $allowWhiteSpace;
+ }
+ }
+
+ /**
+ * Returns the allowWhiteSpace option
+ *
+ * @return bool
+ */
+ public function getAllowWhiteSpace()
+ {
+ return $this->options['allowWhiteSpace'];
+ }
+
+ /**
+ * Sets the allowWhiteSpace option
+ *
+ * @param bool $allowWhiteSpace
+ * @return AlnumFilter Provides a fluent interface
+ */
+ public function setAllowWhiteSpace($allowWhiteSpace)
+ {
+ $this->options['allowWhiteSpace'] = (bool) $allowWhiteSpace;
+ return $this;
+ }
+
+ /**
+ * Returns true if and only if $value contains only alphabetic and digit characters
+ *
+ * @param string $value
+ * @return bool
+ */
+ public function isValid($value)
+ {
+ if (! is_string($value) && ! is_int($value) && ! is_float($value)) {
+ $this->error(self::INVALID);
+ return false;
+ }
+
+ $this->setValue($value);
+ if ('' === $value) {
+ $this->error(self::STRING_EMPTY);
+ return false;
+ }
+
+ if (null === static::$filter) {
+ static::$filter = new AlnumFilter();
+ }
+
+ static::$filter->setAllowWhiteSpace($this->options['allowWhiteSpace']);
+
+ if ($value != static::$filter->filter($value)) {
+ $this->error(self::NOT_ALNUM);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/module/Zend/I18n/src/Validator/Alpha.php b/module/Zend/I18n/src/Validator/Alpha.php
new file mode 100644
index 00000000..c5e2f6a3
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/Alpha.php
@@ -0,0 +1,81 @@
+ "Invalid type given. String expected",
+ self::NOT_ALPHA => "The input contains non alphabetic characters",
+ self::STRING_EMPTY => "The input is an empty string"
+ ];
+
+ /**
+ * Options for this validator
+ *
+ * @var array
+ */
+ protected $options = [
+ 'allowWhiteSpace' => false, // Whether to allow white space characters; off by default
+ ];
+
+ /**
+ * Returns true if and only if $value contains only alphabetic characters
+ *
+ * @param string $value
+ * @return bool
+ */
+ public function isValid($value)
+ {
+ if (! is_string($value)) {
+ $this->error(self::INVALID);
+ return false;
+ }
+
+ $this->setValue($value);
+
+ if ('' === $value) {
+ $this->error(self::STRING_EMPTY);
+ return false;
+ }
+
+ if (null === static::$filter) {
+ static::$filter = new AlphaFilter();
+ }
+
+ //static::$filter->setAllowWhiteSpace($this->allowWhiteSpace);
+ static::$filter->setAllowWhiteSpace($this->options['allowWhiteSpace']);
+
+ if ($value !== static::$filter->filter($value)) {
+ $this->error(self::NOT_ALPHA);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/module/Zend/I18n/src/Validator/DateTime.php b/module/Zend/I18n/src/Validator/DateTime.php
new file mode 100644
index 00000000..4eba6ceb
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/DateTime.php
@@ -0,0 +1,335 @@
+ "Invalid type given. String expected",
+ self::INVALID_DATETIME => "The input does not appear to be a valid datetime",
+ ];
+
+ /**
+ * Optional locale
+ *
+ * @var string|null
+ */
+ protected $locale;
+
+ /**
+ * @var int
+ */
+ protected $dateType;
+
+ /**
+ * @var int
+ */
+ protected $timeType;
+
+ /**
+ * Optional timezone
+ *
+ * @var string
+ */
+ protected $timezone;
+
+ /**
+ * @var string
+ */
+ protected $pattern;
+
+ /**
+ * @var int
+ */
+ protected $calendar;
+
+ /**
+ * @var IntlDateFormatter
+ */
+ protected $formatter;
+
+ /**
+ * Is the formatter invalidated
+ * Invalidation occurs when immutable properties are changed
+ *
+ * @var bool
+ */
+ protected $invalidateFormatter = false;
+
+ /**
+ * Constructor for the Date validator
+ *
+ * @param array|Traversable $options
+ * @throws I18nException\ExtensionNotLoadedException if ext/intl is not present
+ */
+ public function __construct($options = [])
+ {
+ if (! extension_loaded('intl')) {
+ throw new I18nException\ExtensionNotLoadedException(
+ sprintf('%s component requires the intl PHP extension', __NAMESPACE__)
+ );
+ }
+
+ // Delaying initialization until we know ext/intl is available
+ $this->dateType = IntlDateFormatter::NONE;
+ $this->timeType = IntlDateFormatter::NONE;
+ $this->calendar = IntlDateFormatter::GREGORIAN;
+
+ parent::__construct($options);
+
+ if (null === $this->locale) {
+ $this->locale = Locale::getDefault();
+ }
+ if (null === $this->timezone) {
+ $this->timezone = date_default_timezone_get();
+ }
+ }
+
+ /**
+ * Sets the calendar to be used by the IntlDateFormatter
+ *
+ * @param int|null $calendar
+ * @return DateTime provides fluent interface
+ */
+ public function setCalendar($calendar)
+ {
+ $this->calendar = $calendar;
+
+ return $this;
+ }
+
+ /**
+ * Returns the calendar to by the IntlDateFormatter
+ *
+ * @return int
+ */
+ public function getCalendar()
+ {
+ if ($this->formatter && ! $this->invalidateFormatter) {
+ return $this->getIntlDateFormatter()->getCalendar();
+ } else {
+ return $this->calendar;
+ }
+ }
+
+ /**
+ * Sets the date format to be used by the IntlDateFormatter
+ *
+ * @param int|null $dateType
+ * @return DateTime provides fluent interface
+ */
+ public function setDateType($dateType)
+ {
+ $this->dateType = $dateType;
+ $this->invalidateFormatter = true;
+
+ return $this;
+ }
+
+ /**
+ * Returns the date format used by the IntlDateFormatter
+ *
+ * @return int
+ */
+ public function getDateType()
+ {
+ return $this->dateType;
+ }
+
+ /**
+ * Sets the pattern to be used by the IntlDateFormatter
+ *
+ * @param string|null $pattern
+ * @return DateTime provides fluent interface
+ */
+ public function setPattern($pattern)
+ {
+ $this->pattern = $pattern;
+
+ return $this;
+ }
+
+ /**
+ * Returns the pattern used by the IntlDateFormatter
+ *
+ * @return string
+ */
+ public function getPattern()
+ {
+ if ($this->formatter && ! $this->invalidateFormatter) {
+ return $this->getIntlDateFormatter()->getPattern();
+ } else {
+ return $this->pattern;
+ }
+ }
+
+ /**
+ * Sets the time format to be used by the IntlDateFormatter
+ *
+ * @param int|null $timeType
+ * @return DateTime provides fluent interface
+ */
+ public function setTimeType($timeType)
+ {
+ $this->timeType = $timeType;
+ $this->invalidateFormatter = true;
+
+ return $this;
+ }
+
+ /**
+ * Returns the time format used by the IntlDateFormatter
+ *
+ * @return int
+ */
+ public function getTimeType()
+ {
+ return $this->timeType;
+ }
+
+ /**
+ * Sets the timezone to be used by the IntlDateFormatter
+ *
+ * @param string|null $timezone
+ * @return DateTime provides fluent interface
+ */
+ public function setTimezone($timezone)
+ {
+ $this->timezone = $timezone;
+
+ return $this;
+ }
+
+ /**
+ * Returns the timezone used by the IntlDateFormatter or the system default if none given
+ *
+ * @return string
+ */
+ public function getTimezone()
+ {
+ if ($this->formatter && ! $this->invalidateFormatter) {
+ return $this->getIntlDateFormatter()->getTimeZoneId();
+ } else {
+ return $this->timezone;
+ }
+ }
+
+ /**
+ * Sets the locale to be used by the IntlDateFormatter
+ *
+ * @param string|null $locale
+ * @return DateTime provides fluent interface
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = $locale;
+ $this->invalidateFormatter = true;
+
+ return $this;
+ }
+
+ /**
+ * Returns the locale used by the IntlDateFormatter or the system default if none given
+ *
+ * @return string
+ */
+ public function getLocale()
+ {
+ return $this->locale;
+ }
+
+ /**
+ * Returns true if and only if $value is a floating-point value
+ *
+ * @param string $value
+ * @return bool
+ * @throws ValidatorException\InvalidArgumentException
+ */
+ public function isValid($value)
+ {
+ if (! is_string($value)) {
+ $this->error(self::INVALID);
+
+ return false;
+ }
+
+ $this->setValue($value);
+
+ try {
+ $formatter = $this->getIntlDateFormatter();
+
+ if (intl_is_failure($formatter->getErrorCode())) {
+ throw new ValidatorException\InvalidArgumentException($formatter->getErrorMessage());
+ }
+ } catch (IntlException $intlException) {
+ throw new ValidatorException\InvalidArgumentException($intlException->getMessage(), 0, $intlException);
+ }
+
+ try {
+ $timestamp = $formatter->parse($value);
+
+ if (intl_is_failure($formatter->getErrorCode()) || $timestamp === false) {
+ $this->error(self::INVALID_DATETIME);
+ $this->invalidateFormatter = true;
+ return false;
+ }
+ } catch (IntlException $intlException) {
+ $this->error(self::INVALID_DATETIME);
+ $this->invalidateFormatter = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a non lenient configured IntlDateFormatter
+ *
+ * @return IntlDateFormatter
+ */
+ protected function getIntlDateFormatter()
+ {
+ if ($this->formatter === null || $this->invalidateFormatter) {
+ $this->formatter = new IntlDateFormatter(
+ $this->getLocale(),
+ $this->getDateType(),
+ $this->getTimeType(),
+ $this->timezone,
+ $this->calendar,
+ $this->pattern
+ );
+
+ $this->formatter->setLenient(false);
+
+ $this->setTimezone($this->formatter->getTimezone());
+ $this->setCalendar($this->formatter->getCalendar());
+ $this->setPattern($this->formatter->getPattern());
+
+ $this->invalidateFormatter = false;
+ }
+
+ return $this->formatter;
+ }
+}
diff --git a/module/Zend/I18n/src/Validator/Float.php b/module/Zend/I18n/src/Validator/Float.php
new file mode 100644
index 00000000..46f048d6
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/Float.php
@@ -0,0 +1,43 @@
+ "Invalid type given. String, integer or float expected",
+ self::NOT_FLOAT => "The input does not appear to be a float",
+ ];
+
+ /**
+ * Optional locale
+ *
+ * @var string|null
+ */
+ protected $locale;
+
+ /**
+ * UTF-8 compatible wrapper for string functions
+ *
+ * @var StringWrapperInterface
+ */
+ protected $wrapper;
+
+ /**
+ * Constructor for the integer validator
+ *
+ * @param array|Traversable $options
+ * @throws Exception\ExtensionNotLoadedException if ext/intl is not present
+ */
+ public function __construct($options = [])
+ {
+ if (! extension_loaded('intl')) {
+ throw new I18nException\ExtensionNotLoadedException(
+ sprintf('%s component requires the intl PHP extension', __NAMESPACE__)
+ );
+ }
+
+ $this->wrapper = StringUtils::getWrapper();
+
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (array_key_exists('locale', $options)) {
+ $this->setLocale($options['locale']);
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set locale
+ *
+ * @return string
+ */
+ public function getLocale()
+ {
+ if (null === $this->locale) {
+ $this->locale = Locale::getDefault();
+ }
+ return $this->locale;
+ }
+
+ /**
+ * Sets the locale to use
+ *
+ * @param string|null $locale
+ * @return Float
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = $locale;
+ return $this;
+ }
+
+ /**
+ * Returns true if and only if $value is a floating-point value. Uses the formal definition of a float as described
+ * in the PHP manual: {@link http://www.php.net/float}
+ *
+ * @param string $value
+ * @return bool
+ * @throws Exception\InvalidArgumentException
+ */
+ public function isValid($value)
+ {
+ if (! is_scalar($value) || is_bool($value)) {
+ $this->error(self::INVALID);
+ return false;
+ }
+
+ $this->setValue($value);
+
+ if (is_float($value) || is_int($value)) {
+ return true;
+ }
+
+ // Need to check if this is scientific formatted string. If not, switch to decimal.
+ $formatter = new NumberFormatter($this->getLocale(), NumberFormatter::SCIENTIFIC);
+
+ try {
+ if (intl_is_failure($formatter->getErrorCode())) {
+ throw new Exception\InvalidArgumentException($formatter->getErrorMessage());
+ }
+ } catch (IntlException $intlException) {
+ throw new Exception\InvalidArgumentException($intlException->getMessage(), 0, $intlException);
+ }
+
+ if (StringUtils::hasPcreUnicodeSupport()) {
+ $exponentialSymbols = '[Ee' . $formatter->getSymbol(NumberFormatter::EXPONENTIAL_SYMBOL) . ']+';
+ $search = '/' . $exponentialSymbols . '/u';
+ } else {
+ $exponentialSymbols = '[Ee]';
+ $search = '/' . $exponentialSymbols . '/';
+ }
+
+ if (! preg_match($search, $value)) {
+ $formatter = new NumberFormatter($this->getLocale(), NumberFormatter::DECIMAL);
+ }
+
+ /**
+ * @desc There are separator "look-alikes" for decimal and group separators that are more commonly used than the
+ * official unicode character. We need to replace those with the real thing - or remove it.
+ */
+ $groupSeparator = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
+ $decSeparator = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
+
+ //NO-BREAK SPACE and ARABIC THOUSANDS SEPARATOR
+ if ($groupSeparator == "\xC2\xA0") {
+ $value = str_replace(' ', $groupSeparator, $value);
+ } elseif ($groupSeparator == "\xD9\xAC") {
+ //NumberFormatter doesn't have grouping at all for Arabic-Indic
+ $value = str_replace(['\'', $groupSeparator], '', $value);
+ }
+
+ //ARABIC DECIMAL SEPARATOR
+ if ($decSeparator == "\xD9\xAB") {
+ $value = str_replace(',', $decSeparator, $value);
+ }
+
+ $groupSeparatorPosition = $this->wrapper->strpos($value, $groupSeparator);
+ $decSeparatorPosition = $this->wrapper->strpos($value, $decSeparator);
+
+ //We have separators, and they are flipped. i.e. 2.000,000 for en-US
+ if ($groupSeparatorPosition && $decSeparatorPosition && $groupSeparatorPosition > $decSeparatorPosition) {
+ $this->error(self::NOT_FLOAT);
+
+ return false;
+ }
+
+ //If we have Unicode support, we can use the real graphemes, otherwise, just the ASCII characters
+ $decimal = '['. preg_quote($decSeparator, '/') . ']';
+ $prefix = '[+-]';
+ $exp = $exponentialSymbols;
+ $numberRange = '0-9';
+ $useUnicode = '';
+ $suffix = '';
+
+ if (StringUtils::hasPcreUnicodeSupport()) {
+ $prefix = '['
+ . preg_quote(
+ $formatter->getTextAttribute(NumberFormatter::POSITIVE_PREFIX)
+ . $formatter->getTextAttribute(NumberFormatter::NEGATIVE_PREFIX)
+ . $formatter->getSymbol(NumberFormatter::PLUS_SIGN_SYMBOL)
+ . $formatter->getSymbol(NumberFormatter::MINUS_SIGN_SYMBOL),
+ '/'
+ )
+ . ']{0,3}';
+ $suffix = ($formatter->getTextAttribute(NumberFormatter::NEGATIVE_SUFFIX))
+ ? '['
+ . preg_quote(
+ $formatter->getTextAttribute(NumberFormatter::POSITIVE_SUFFIX)
+ . $formatter->getTextAttribute(NumberFormatter::NEGATIVE_SUFFIX)
+ . $formatter->getSymbol(NumberFormatter::PLUS_SIGN_SYMBOL)
+ . $formatter->getSymbol(NumberFormatter::MINUS_SIGN_SYMBOL),
+ '/'
+ )
+ . ']{0,3}'
+ : '';
+ $numberRange = '\p{N}';
+ $useUnicode = 'u';
+ }
+
+ /**
+ * @desc Match against the formal definition of a float. The
+ * exponential number check is modified for RTL non-Latin number
+ * systems (Arabic-Indic numbering). I'm also switching out the period
+ * for the decimal separator. The formal definition leaves out +- from
+ * the integer and decimal notations so add that. This also checks
+ * that a grouping sperator is not in the last GROUPING_SIZE graphemes
+ * of the string - i.e. 10,6 is not valid for en-US.
+ * @see http://www.php.net/float
+ */
+
+ $lnum = '[' . $numberRange . ']+';
+ $dnum = '(([' . $numberRange . ']*' . $decimal . $lnum . ')|('
+ . $lnum . $decimal . '[' . $numberRange . ']*))';
+ $expDnum = '((' . $prefix . '((' . $lnum . '|' . $dnum . ')' . $exp . $prefix . $lnum . ')' . $suffix . ')|'
+ . '(' . $suffix . '(' . $lnum . $prefix . $exp . '(' . $dnum . '|' . $lnum . '))' . $prefix . '))';
+
+ // LEFT-TO-RIGHT MARK (U+200E) is messing up everything for the handful
+ // of locales that have it
+ $lnumSearch = str_replace("\xE2\x80\x8E", '', '/^' .$prefix . $lnum . $suffix . '$/' . $useUnicode);
+ $dnumSearch = str_replace("\xE2\x80\x8E", '', '/^' .$prefix . $dnum . $suffix . '$/' . $useUnicode);
+ $expDnumSearch = str_replace("\xE2\x80\x8E", '', '/^' . $expDnum . '$/' . $useUnicode);
+ $value = str_replace("\xE2\x80\x8E", '', $value);
+ $unGroupedValue = str_replace($groupSeparator, '', $value);
+
+ // No strrpos() in wrappers yet. ICU 4.x doesn't have grouping size for
+ // everything. ICU 52 has 3 for ALL locales.
+ $groupSize = ($formatter->getAttribute(NumberFormatter::GROUPING_SIZE))
+ ? $formatter->getAttribute(NumberFormatter::GROUPING_SIZE)
+ : 3;
+ $lastStringGroup = $this->wrapper->substr($value, -$groupSize);
+
+ if ((preg_match($lnumSearch, $unGroupedValue)
+ || preg_match($dnumSearch, $unGroupedValue)
+ || preg_match($expDnumSearch, $unGroupedValue))
+ && false === $this->wrapper->strpos($lastStringGroup, $groupSeparator)
+ ) {
+ return true;
+ }
+
+ $this->error(self::NOT_FLOAT);
+
+ return false;
+ }
+}
diff --git a/module/Zend/I18n/src/Validator/IsInt.php b/module/Zend/I18n/src/Validator/IsInt.php
new file mode 100644
index 00000000..da9343e0
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/IsInt.php
@@ -0,0 +1,190 @@
+ "Invalid type given. String or integer expected",
+ self::NOT_INT => "The input does not appear to be an integer",
+ self::NOT_INT_STRICT => "The input is not strictly an integer",
+ ];
+
+ /**
+ * Optional locale
+ *
+ * @var string|null
+ */
+ protected $locale;
+
+ /**
+ * Data type is not enforced by default, so the string '123' is considered an integer.
+ * Setting strict to true will enforce the integer data type.
+ *
+ * @var bool
+ */
+ protected $strict = false;
+
+ /**
+ * Constructor for the integer validator
+ *
+ * @param array|Traversable $options
+ * @throws Exception\ExtensionNotLoadedException if ext/intl is not present
+ */
+ public function __construct($options = [])
+ {
+ if (! extension_loaded('intl')) {
+ throw new I18nException\ExtensionNotLoadedException(sprintf(
+ '%s component requires the intl PHP extension',
+ __NAMESPACE__
+ ));
+ }
+
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (array_key_exists('locale', $options)) {
+ $this->setLocale($options['locale']);
+ }
+
+ if (array_key_exists('strict', $options)) {
+ $this->setStrict($options['strict']);
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set locale
+ */
+ public function getLocale()
+ {
+ if (null === $this->locale) {
+ $this->locale = Locale::getDefault();
+ }
+ return $this->locale;
+ }
+
+ /**
+ * Sets the locale to use
+ *
+ * @param string $locale
+ * @return Int
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = $locale;
+ return $this;
+ }
+
+ /**
+ * Returns the strict option
+ *
+ * @return bool
+ */
+ public function getStrict()
+ {
+ return $this->strict;
+ }
+
+ /**
+ * Sets the strict option mode
+ *
+ * @param bool $strict
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setStrict($strict)
+ {
+ if (! is_bool($strict)) {
+ throw new Exception\InvalidArgumentException('Strict option must be a boolean');
+ }
+
+ $this->strict = $strict;
+ return $this;
+ }
+
+ /**
+ * Returns true if and only if $value is a valid integer
+ *
+ * @param string|int $value
+ * @return bool
+ * @throws Exception\InvalidArgumentException
+ */
+ public function isValid($value)
+ {
+ if (! is_string($value) && ! is_int($value) && ! is_float($value)) {
+ $this->error(self::INVALID);
+ return false;
+ }
+
+ if (is_int($value)) {
+ return true;
+ }
+
+ if ($this->strict) {
+ $this->error(self::NOT_INT_STRICT);
+ return false;
+ }
+
+ $this->setValue($value);
+
+ $locale = $this->getLocale();
+ try {
+ $format = new NumberFormatter($locale, NumberFormatter::DECIMAL);
+ if (intl_is_failure($format->getErrorCode())) {
+ throw new Exception\InvalidArgumentException("Invalid locale string given");
+ }
+ } catch (IntlException $intlException) {
+ throw new Exception\InvalidArgumentException("Invalid locale string given", 0, $intlException);
+ }
+
+ try {
+ $parsedInt = $format->parse($value, NumberFormatter::TYPE_INT64);
+ if (intl_is_failure($format->getErrorCode())) {
+ $this->error(self::NOT_INT);
+ return false;
+ }
+ } catch (IntlException $intlException) {
+ $this->error(self::NOT_INT);
+ return false;
+ }
+
+ $decimalSep = $format->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
+ $groupingSep = $format->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
+
+ $valueFiltered = str_replace($groupingSep, '', $value);
+ $valueFiltered = str_replace($decimalSep, '.', $valueFiltered);
+
+ if (strval($parsedInt) !== $valueFiltered) {
+ $this->error(self::NOT_INT);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber.php b/module/Zend/I18n/src/Validator/PhoneNumber.php
new file mode 100644
index 00000000..5fde4d8c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber.php
@@ -0,0 +1,264 @@
+ 'The input does not match a phone number format',
+ self::UNSUPPORTED => 'The country provided is currently unsupported',
+ self::INVALID => 'Invalid type given. String expected',
+ ];
+
+ /**
+ * Phone Number Patterns
+ *
+ * @link http://code.google.com/p/libphonenumber/source/browse/trunk/resources/PhoneNumberMetadata.xml
+ * @var array
+ */
+ protected static $phone = [];
+
+ /**
+ * ISO 3611 Country Code
+ *
+ * @var string
+ */
+ protected $country;
+
+ /**
+ * Allow Possible Matches
+ *
+ * @var bool
+ */
+ protected $allowPossible = false;
+
+ /**
+ * Allowed Types
+ *
+ * @var array
+ */
+ protected $allowedTypes = [
+ 'general',
+ 'fixed',
+ 'tollfree',
+ 'personal',
+ 'mobile',
+ 'voip',
+ 'uan',
+ ];
+
+ /**
+ * Constructor for the PhoneNumber validator
+ *
+ * Options
+ * - country | string | field or value
+ * - allowed_types | array | array of allowed types
+ * - allow_possible | boolean | allow possible matches aka non-strict
+ *
+ * @param array|Traversable $options
+ */
+ public function __construct($options = [])
+ {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (array_key_exists('country', $options)) {
+ $this->setCountry($options['country']);
+ } else {
+ $country = Locale::getRegion(Locale::getDefault());
+ $this->setCountry($country);
+ }
+
+ if (array_key_exists('allowed_types', $options)) {
+ $this->allowedTypes($options['allowed_types']);
+ }
+
+ if (array_key_exists('allow_possible', $options)) {
+ $this->allowPossible($options['allow_possible']);
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Allowed Types
+ *
+ * @param array|null $types
+ * @return self|array
+ */
+ public function allowedTypes(array $types = null)
+ {
+ if (null !== $types) {
+ $this->allowedTypes = $types;
+
+ return $this;
+ }
+
+ return $this->allowedTypes;
+ }
+
+ /**
+ * Allow Possible
+ *
+ * @param bool|null $possible
+ * @return self|bool
+ */
+ public function allowPossible($possible = null)
+ {
+ if (null !== $possible) {
+ $this->allowPossible = (bool) $possible;
+
+ return $this;
+ }
+
+ return $this->allowPossible;
+ }
+
+ /**
+ * Get Country
+ *
+ * @return string
+ */
+ public function getCountry()
+ {
+ return $this->country;
+ }
+
+ /**
+ * Set Country
+ *
+ * @param string $country
+ * @return self
+ */
+ public function setCountry($country)
+ {
+ $this->country = $country;
+
+ return $this;
+ }
+
+ /**
+ * Load Pattern
+ *
+ * @param string $code
+ * @return array[]|false
+ */
+ protected function loadPattern($code)
+ {
+ if (! isset(static::$phone[$code])) {
+ if (! preg_match('/^[A-Z]{2}$/D', $code)) {
+ return false;
+ }
+
+ $file = __DIR__ . '/PhoneNumber/' . $code . '.php';
+ if (! file_exists($file)) {
+ return false;
+ }
+
+ static::$phone[$code] = include $file;
+ }
+
+ return static::$phone[$code];
+ }
+
+ /**
+ * Returns true if and only if $value matches phone number format
+ *
+ * @param string $value
+ * @param array $context
+ * @return bool
+ */
+ public function isValid($value = null, $context = null)
+ {
+ if (! is_scalar($value)) {
+ $this->error(self::INVALID);
+
+ return false;
+ }
+ $this->setValue($value);
+
+ $country = $this->getCountry();
+
+ if (! $countryPattern = $this->loadPattern(strtoupper($country))) {
+ if (isset($context[$country])) {
+ $country = $context[$country];
+ }
+
+ if (! $countryPattern = $this->loadPattern(strtoupper($country))) {
+ $this->error(self::UNSUPPORTED);
+
+ return false;
+ }
+ }
+
+ $codeLength = strlen($countryPattern['code']);
+
+ /*
+ * Check for existence of either:
+ * 1) E.123/E.164 international prefix
+ * 2) International double-O prefix
+ * 3) Bare country prefix
+ */
+ if (0 === strpos($value, '+' . $countryPattern['code'])) {
+ $valueNoCountry = substr($value, $codeLength + 1);
+ } elseif (0 === strpos($value, '00' . $countryPattern['code'])) {
+ $valueNoCountry = substr($value, $codeLength + 2);
+ } elseif (0 === strpos($value, $countryPattern['code'])) {
+ $valueNoCountry = substr($value, $codeLength);
+ }
+
+ // check against allowed types strict match:
+ foreach ($countryPattern['patterns']['national'] as $type => $pattern) {
+ if (in_array($type, $this->allowedTypes)) {
+ // check pattern:
+ if (preg_match($pattern, $value)) {
+ return true;
+ } elseif (isset($valueNoCountry) && preg_match($pattern, $valueNoCountry)) {
+ // this handles conditions where the country code and prefix are the same
+ return true;
+ }
+ }
+ }
+
+ // check for possible match:
+ if ($this->allowPossible()) {
+ foreach ($countryPattern['patterns']['possible'] as $type => $pattern) {
+ if (in_array($type, $this->allowedTypes)) {
+ // check pattern:
+ if (preg_match($pattern, $value)) {
+ return true;
+ } elseif (isset($valueNoCountry) && preg_match($pattern, $valueNoCountry)) {
+ // this handles conditions where the country code and prefix are the same
+ return true;
+ }
+ }
+ }
+ }
+
+ $this->error(self::NO_MATCH);
+
+ return false;
+ }
+}
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AC.php b/module/Zend/I18n/src/Validator/PhoneNumber/AC.php
new file mode 100644
index 00000000..1a86cf4c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AC.php
@@ -0,0 +1,24 @@
+ '247',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-467]\d{3}$/',
+ 'fixed' => '/^(?:[267]\d|3[0-5]|4[4-69])\d{2}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{4}$/',
+ 'fixed' => '/^\d{4}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AD.php b/module/Zend/I18n/src/Validator/PhoneNumber/AD.php
new file mode 100644
index 00000000..c4a13093
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AD.php
@@ -0,0 +1,30 @@
+ '376',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^(?:[346-9]|180)\d{5}$/',
+ 'fixed' => '/^[78]\d{5}$/',
+ 'mobile' => '/^[346]\d{5}$/',
+ 'tollfree' => '/^180[02]\d{4}$/',
+ 'premium' => '/^9\d{5}$/',
+ 'emergency' => '/^11[0268]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{6,8}$/',
+ 'fixed' => '/^\d{6}$/',
+ 'mobile' => '/^\d{6}$/',
+ 'tollfree' => '/^\d{8}$/',
+ 'premium' => '/^\d{6}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AE.php b/module/Zend/I18n/src/Validator/PhoneNumber/AE.php
new file mode 100644
index 00000000..cc53210b
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AE.php
@@ -0,0 +1,34 @@
+ '971',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-79]\d{7,8}|800\d{2,9}$/',
+ 'fixed' => '/^[2-4679][2-8]\d{6}$/',
+ 'mobile' => '/^5[0256]\d{7}$/',
+ 'tollfree' => '/^400\d{6}|800\d{2,9}$/',
+ 'premium' => '/^900[02]\d{5}$/',
+ 'shared' => '/^700[05]\d{5}$/',
+ 'uan' => '/^600[25]\d{5}$/',
+ 'emergency' => '/^112|99[789]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{5,12}$/',
+ 'fixed' => '/^\d{7,8}$/',
+ 'mobile' => '/^\d{9}$/',
+ 'tollfree' => '/^\d{5,12}$/',
+ 'premium' => '/^\d{9}$/',
+ 'shared' => '/^\d{9}$/',
+ 'uan' => '/^\d{9}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AF.php b/module/Zend/I18n/src/Validator/PhoneNumber/AF.php
new file mode 100644
index 00000000..4830c48d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AF.php
@@ -0,0 +1,25 @@
+ '93',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-7]\d{8}$/',
+ 'fixed' => '/^(?:[25][0-8]|[34][0-4]|6[0-5])[2-9]\d{6}$/',
+ 'mobile' => '/^7[057-9]\d{7}$/',
+ 'emergency' => '/^1(?:02|19)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{7,9}$/',
+ 'mobile' => '/^\d{9}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AG.php b/module/Zend/I18n/src/Validator/PhoneNumber/AG.php
new file mode 100644
index 00000000..d9692d0c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AG.php
@@ -0,0 +1,35 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2589]\d{9}$/',
+ 'fixed' => '/^268(?:4(?:6[0-38]|84)|56[0-2])\d{4}$/',
+ 'mobile' => '/^268(?:464|7(?:2[0-9]|64|7[0-689]|8[02-68]))\d{4}$/',
+ 'pager' => '/^26840[69]\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\d{6}$/',
+ 'premium' => '/^900[2-9]\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\d{6}$/',
+ 'voip' => '/^26848[01]\d{4}$/',
+ 'emergency' => '/^9(?:11|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{7}(?:\d{3})?$/',
+ 'mobile' => '/^\d{10}$/',
+ 'pager' => '/^\d{10}$/',
+ 'tollfree' => '/^\d{10}$/',
+ 'premium' => '/^\d{10}$/',
+ 'personal' => '/^\d{10}$/',
+ 'voip' => '/^\d{10}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AI.php b/module/Zend/I18n/src/Validator/PhoneNumber/AI.php
new file mode 100644
index 00000000..2aadb706
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AI.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2589]\d{9}$/',
+ 'fixed' => '/^2644(?:6[12]|9[78])\d{4}$/',
+ 'mobile' => '/^264(?:235|476|5(?:3[6-9]|8[1-4])|7(?:29|72))\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\d{6}$/',
+ 'premium' => '/^900[2-9]\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\d{6}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{7}(?:\d{3})?$/',
+ 'mobile' => '/^\d{10}$/',
+ 'tollfree' => '/^\d{10}$/',
+ 'premium' => '/^\d{10}$/',
+ 'personal' => '/^\d{10}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AL.php b/module/Zend/I18n/src/Validator/PhoneNumber/AL.php
new file mode 100644
index 00000000..b9889e53
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AL.php
@@ -0,0 +1,34 @@
+ '355',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-57]\d{7}|6\d{8}|8\d{5,7}|9\d{5}$/',
+ 'fixed' => '/^(?:2(?:[168][1-9]|[247]\d|9[1-7])|3(?:1[1-3]|[2-6]\d|[79][1-8]|8[1-9])|4\d{2}|5(?:1[1-4]|[2-578]\d|6[1-5]|9[1-7])|8(?:[19][1-5]|[2-6]\d|[78][1-7]))\d{5}$/',
+ 'mobile' => '/^6[6-9]\d{7}$/',
+ 'tollfree' => '/^800\d{4}$/',
+ 'premium' => '/^900\d{3}$/',
+ 'shared' => '/^808\d{3}$/',
+ 'personal' => '/^700\d{5}$/',
+ 'emergency' => '/^12[789]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{5,9}$/',
+ 'fixed' => '/^\d{5,8}$/',
+ 'mobile' => '/^\d{9}$/',
+ 'tollfree' => '/^\d{7}$/',
+ 'premium' => '/^\d{6}$/',
+ 'shared' => '/^\d{6}$/',
+ 'personal' => '/^\d{8}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AM.php b/module/Zend/I18n/src/Validator/PhoneNumber/AM.php
new file mode 100644
index 00000000..5c106bca
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AM.php
@@ -0,0 +1,35 @@
+ '374',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\d{7}$/',
+ 'fixed' => '/^(?:10\d|2(?:2[2-46]|3[1-8]|4[2-69]|5[2-7]|6[1-9]|8[1-7])|3[12]2|47\d)\d{5}$/',
+ 'mobile' => '/^(?:55|77|9[1-9])\d{6}$/',
+ 'tollfree' => '/^800\d{5}$/',
+ 'premium' => '/^90[016]\d{5}$/',
+ 'shared' => '/^80[1-4]\d{5}$/',
+ 'voip' => '/^60[2-6]\d{5}$/',
+ 'shortcode' => '/^8[1-7]\d{2}|1(?:0[04-9]|[1-9]\d)$/',
+ 'emergency' => '/^10[123]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{5,8}$/',
+ 'mobile' => '/^\d{8}$/',
+ 'tollfree' => '/^\d{8}$/',
+ 'premium' => '/^\d{8}$/',
+ 'shared' => '/^\d{8}$/',
+ 'voip' => '/^\d{8}$/',
+ 'shortcode' => '/^\d{3,4}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AO.php b/module/Zend/I18n/src/Validator/PhoneNumber/AO.php
new file mode 100644
index 00000000..29be27ee
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AO.php
@@ -0,0 +1,24 @@
+ '244',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[29]\d{8}$/',
+ 'fixed' => '/^2\d(?:[26-9]\d|\d[26-9])\d{5}$/',
+ 'mobile' => '/^9[1-4]\d{7}$/',
+ 'emergency' => '/^11[235]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{9}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AR.php b/module/Zend/I18n/src/Validator/PhoneNumber/AR.php
new file mode 100644
index 00000000..b899a490
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AR.php
@@ -0,0 +1,34 @@
+ '54',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-368]\d{9}|9\d{10}$/',
+ 'fixed' => '/^11\d{8}|(?:2(?:2(?:[013]\d|2[13-79]|4[1-6]|5[2457]|6[124-8]|7[1-4]|8[13-6]|9[1267])|3(?:1[467]|2[03-6]|3[13-8]|[49][2-6]|5[2-8]|[067]\d)|4(?:7[3-8]|9\d)|6(?:[01346]\d|2[24-6]|5[15-8])|80\d|9(?:[0124789]\d|3[1-6]|5[234]|6[2-46]))|3(?:3(?:2[79]|6\d|8[2578])|4(?:[78]\d|0[0124-9]|[1-35]\d|4[24-7]|6[02-9]|9[123678])|5(?:[138]\d|2[1245]|4[1-9]|6[2-4]|7[1-6])|6[24]\d|7(?:[0469]\d|1[1568]|2[013-9]|3[145]|5[14-8]|7[2-57]|8[0-24-9])|8(?:[013578]\d|2[15-7]|4[13-6]|6[1-357-9]|9[124]))|670\d)\d{6}$/',
+ 'mobile' => '/^675\d{7}|9(?:11[2-9]\d{7}|(?:2(?:2[013]|3[067]|49|6[01346]|80|9[147-9])|3(?:36|4[12358]|5[138]|6[24]|7[069]|8[013578]))[2-9]\d{6}|\d{4}[2-9]\d{5})$/',
+ 'tollfree' => '/^800\d{7}$/',
+ 'premium' => '/^60[04579]\d{7}$/',
+ 'uan' => '/^810\d{7}$/',
+ 'shortcode' => '/^1(?:0[2356]|1[02-5]|21)$/',
+ 'emergency' => '/^1(?:0[017]|28)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{6,11}$/',
+ 'fixed' => '/^\d{6,10}$/',
+ 'mobile' => '/^\d{6,11}$/',
+ 'tollfree' => '/^\d{10}$/',
+ 'premium' => '/^\d{10}$/',
+ 'uan' => '/^\d{10}$/',
+ 'shortcode' => '/^\d{3}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AS.php b/module/Zend/I18n/src/Validator/PhoneNumber/AS.php
new file mode 100644
index 00000000..f8041d58
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AS.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5689]\d{9}$/',
+ 'fixed' => '/^6846(?:22|33|44|55|77|88|9[19])\d{4}$/',
+ 'mobile' => '/^684(?:733|258)\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\d{6}$/',
+ 'premium' => '/^900[2-9]\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\d{6}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{7}(?:\d{3})?$/',
+ 'mobile' => '/^\d{10}$/',
+ 'tollfree' => '/^\d{10}$/',
+ 'premium' => '/^\d{10}$/',
+ 'personal' => '/^\d{10}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AT.php b/module/Zend/I18n/src/Validator/PhoneNumber/AT.php
new file mode 100644
index 00000000..613d49e0
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AT.php
@@ -0,0 +1,35 @@
+ '43',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\d{3,12}$/',
+ 'fixed' => '/^1\d{3,12}|(?:2(?:1[467]|2[13-8]|5[2357]|6[1-46-8]|7[1-8]|8[124-7]|9[1458])|3(?:1[1-8]|3[23568]|4[5-7]|5[1378]|6[1-38]|8[3-68])|4(?:2[1-8]|35|63|7[1368]|8[2457])|5(?:12|2[1-8]|3[357]|4[147]|5[12578]|6[37])|6(?:13|2[1-47]|4[1-35-8]|5[468]|62)|7(?:2[1-8]|3[25]|4[13478]|5[68]|6[16-8]|7[1-6]|9[45]))\d{3,10}$/',
+ 'mobile' => '/^6(?:44|5[0-3579]|6[013-9]|[7-9]\d)\d{4,10}$/',
+ 'tollfree' => '/^80[02]\d{6,10}$/',
+ 'premium' => '/^(?:711|9(?:0[01]|3[019]))\d{6,10}$/',
+ 'shared' => '/^8(?:10|2[018])\d{6,10}$/',
+ 'voip' => '/^780\d{6,10}$/',
+ 'uan' => '/^5(?:(?:0[1-9]|17)\d{2,10}|[79]\d{3,11})|720\d{6,10}$/',
+ 'emergency' => '/^1(?:[12]2|33|44)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{3,13}$/',
+ 'mobile' => '/^\d{7,13}$/',
+ 'tollfree' => '/^\d{9,13}$/',
+ 'premium' => '/^\d{9,13}$/',
+ 'shared' => '/^\d{9,13}$/',
+ 'voip' => '/^\d{9,13}$/',
+ 'uan' => '/^\d{5,13}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AU.php b/module/Zend/I18n/src/Validator/PhoneNumber/AU.php
new file mode 100644
index 00000000..fefa4c0f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AU.php
@@ -0,0 +1,38 @@
+ '61',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-578]\d{5,9}$/',
+ 'fixed' => '/^[237]\d{8}|8(?:[68]\d{3}|7[0-69]\d{2}|9(?:[02-9]\d{2}|1(?:[0-57-9]\d|6[0135-9])))\d{4}$/',
+ 'mobile' => '/^14(?:5\d|71)\d{5}|4(?:[0-2]\d|3[0-57-9]|4[47-9]|5[0-35-9]|6[6-9]|[79][07-9]|8[17-9])\d{6}$/',
+ 'pager' => '/^16\d{3,7}$/',
+ 'tollfree' => '/^180(?:0\d{3}|2)\d{3}$/',
+ 'premium' => '/^19(?:0[0126]\d{6}|[13-5]\d{3}|[679]\d{5})$/',
+ 'shared' => '/^13(?:00\d{2})?\d{4}$/',
+ 'personal' => '/^500\d{6}$/',
+ 'voip' => '/^550\d{6}$/',
+ 'emergency' => '/^000|112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\d{6,10}$/',
+ 'fixed' => '/^\d{8,9}$/',
+ 'mobile' => '/^\d{9}$/',
+ 'pager' => '/^\d{5,9}$/',
+ 'tollfree' => '/^\d{7,10}$/',
+ 'premium' => '/^\d{6,10}$/',
+ 'shared' => '/^\d{6,10}$/',
+ 'personal' => '/^\d{9}$/',
+ 'voip' => '/^\d{9}$/',
+ 'emergency' => '/^\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AW.php b/module/Zend/I18n/src/Validator/PhoneNumber/AW.php
new file mode 100644
index 00000000..ebafa817
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AW.php
@@ -0,0 +1,27 @@
+ '297',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[25-9]\\d{6}$/',
+ 'fixed' => '/^5(?:2\\d|8[1-9])\\d{4}$/',
+ 'mobile' => '/^(?:5(?:6\\d|9[2-478])|6(?:[039]0|22|4[01]|6[0-2])|7[34]\\d|9(?:6[45]|9[4-8]))\\d{4}$/',
+ 'tollfree' => '/^800\\d{4}$/',
+ 'premium' => '/^900\\d{4}$/',
+ 'voip' => '/^28\\d{5}|501\\d{4}$/',
+ 'emergency' => '/^100|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AX.php b/module/Zend/I18n/src/Validator/PhoneNumber/AX.php
new file mode 100644
index 00000000..cc710ea3
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AX.php
@@ -0,0 +1,32 @@
+ '358',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[135]\\d{5,9}|[27]\\d{4,9}|4\\d{5,10}|6\\d{7,8}|8\\d{6,9}$/',
+ 'fixed' => '/^18[1-8]\\d{3,9}$/',
+ 'mobile' => '/^4\\d{5,10}|50\\d{4,8}$/',
+ 'tollfree' => '/^800\\d{4,7}$/',
+ 'premium' => '/^[67]00\\d{5,6}$/',
+ 'uan' => '/^[13]0\\d{4,8}|2(?:0(?:[016-8]\\d{3,7}|[2-59]\\d{2,7})|9\\d{4,8})|60(?:[12]\\d{5,6}|6\\d{7})|7(?:1\\d{7}|3\\d{8}|5[03-9]\\d{2,7})$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,12}$/',
+ 'fixed' => '/^\\d{6,12}$/',
+ 'mobile' => '/^\\d{6,11}$/',
+ 'tollfree' => '/^\\d{7,10}$/',
+ 'premium' => '/^\\d{8,9}$/',
+ 'uan' => '/^\\d{5,10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/AZ.php b/module/Zend/I18n/src/Validator/PhoneNumber/AZ.php
new file mode 100644
index 00000000..f036f681
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/AZ.php
@@ -0,0 +1,29 @@
+ '994',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{8}$/',
+ 'fixed' => '/^(?:1[28]\\d|2(?:02|1[24]|2[2-4]|33|[45]2|6[23])|365)\\d{6}$/',
+ 'mobile' => '/^(?:4[04]|5[015]|60|7[07])\\d{7}$/',
+ 'tollfree' => '/^88\\d{7}$/',
+ 'premium' => '/^900200\\d{3}$/',
+ 'emergency' => '/^1(?:0[123]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BA.php b/module/Zend/I18n/src/Validator/PhoneNumber/BA.php
new file mode 100644
index 00000000..bfa44656
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BA.php
@@ -0,0 +1,34 @@
+ '387',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[3-9]\\d{7,8}$/',
+ 'fixed' => '/^(?:[35]\\d|49)\\d{6}$/',
+ 'mobile' => '/^6(?:03|44|71|[1-356])\\d{6}$/',
+ 'tollfree' => '/^8[08]\\d{6}$/',
+ 'premium' => '/^9[0246]\\d{6}$/',
+ 'shared' => '/^8[12]\\d{6}$/',
+ 'uan' => '/^70[23]\\d{5}$/',
+ 'emergency' => '/^12[234]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,9}$/',
+ 'fixed' => '/^\\d{6,8}$/',
+ 'mobile' => '/^\\d{8,9}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'shared' => '/^\\d{8}$/',
+ 'uan' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BB.php b/module/Zend/I18n/src/Validator/PhoneNumber/BB.php
new file mode 100644
index 00000000..5e5c25bc
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BB.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2589]\\d{9}$/',
+ 'fixed' => '/^246[2-9]\\d{6}$/',
+ 'mobile' => '/^246(?:(?:2[346]|45|82)\\d|25[0-4])\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^[235]11$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BD.php b/module/Zend/I18n/src/Validator/PhoneNumber/BD.php
new file mode 100644
index 00000000..2eab2902
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BD.php
@@ -0,0 +1,32 @@
+ '880',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-79]\\d{5,9}|1\\d{9}|8[0-7]\\d{4,8}$/',
+ 'fixed' => '/^2(?:7(?:1[0-267]|2[0-289]|3[0-29]|[46][01]|5[1-3]|7[017]|91)|8(?:0[125]|[139][1-6]|2[0157-9]|6[1-35]|7[1-5]|8[1-8])|9(?:0[0-2]|1[1-4]|2[568]|3[3-6]|5[5-7]|6[0167]|7[15]|8[016-8]))\\d{4}|3(?:12?[5-7]\\d{2}|0(?:2(?:[025-79]\\d|[348]\\d{1,2})|3(?:[2-4]\\d|[56]\\d?))|2(?:1\\d{2}|2(?:[12]\\d|[35]\\d{1,2}|4\\d?))|3(?:1\\d{2}|2(?:[2356]\\d|4\\d{1,2}))|4(?:1\\d{2}|2(?:2\\d{1,2}|[47]|5\\d{2}))|5(?:1\\d{2}|29)|[67]1\\d{2}|8(?:1\\d{2}|2(?:2\\d{2}|3|4\\d))|)\\d{3}|4(?:0(?:2(?:[09]\\d|7)|33\\d{2})|1\\d{3}|2(?:1\\d{2}|2(?:[25]\\d?|[348]\\d|[67]\\d{1,2}))|3(?:1\\d{2}(?:\\d{2})?|2(?:[045]\\d|[236-9]\\d{1,2})|32\\d{2})|4(?:[18]\\d{2}|2(?:[2-46]\\d{2}|3)|5[25]\\d{2})|5(?:1\\d{2}|2(?:3\\d|5))|6(?:[18]\\d{2}|2(?:3(?:\\d{2})?|[46]\\d{1,2}|5\\d{2}|7\\d)|5(?:3\\d?|4\\d|[57]\\d{1,2}|6\\d{2}|8))|71\\d{2}|8(?:[18]\\d{2}|23\\d{2}|54\\d{2})|9(?:[18]\\d{2}|2[2-5]\\d{2}|53\\d{1,2}))\\d{3}|5(?:02[03489]\\d{2}|1\\d{2}|2(?:1\\d{2}|2(?:2(?:\\d{2})?|[457]\\d{2}))|3(?:1\\d{2}|2(?:[37](?:\\d{2})?|[569]\\d{2}))|4(?:1\\d{2}|2[46]\\d{2})|5(?:1\\d{2}|26\\d{1,2})|6(?:[18]\\d{2}|2|53\\d{2})|7(?:1|24)\\d{2}|8(?:1|26)\\d{2}|91\\d{2})\\d{3}|6(?:0(?:1\\d{2}|2(?:3\\d{2}|4\\d{1,2}))|2(?:2[2-5]\\d{2}|5(?:[3-5]\\d{2}|7)|8\\d{2})|3(?:1|2[3478])\\d{2}|4(?:1|2[34])\\d{2}|5(?:1|2[47])\\d{2}|6(?:[18]\\d{2}|6(?:2(?:2\\d|[34]\\d{2})|5(?:[24]\\d{2}|3\\d|5\\d{1,2})))|72[2-5]\\d{2}|8(?:1\\d{2}|2[2-5]\\d{2})|9(?:1\\d{2}|2[2-6]\\d{2}))\\d{3}|7(?:(?:02|[3-589]1|6[12]|72[24])\\d{2}|21\\d{3}|32)\\d{3}|8(?:(?:4[12]|[5-7]2|1\\d?)|(?:0|3[12]|[5-7]1|217)\\d)\\d{4}|9(?:[35]1|(?:[024]2|81)\\d|(?:1|[24]1)\\d{2})\\d{3}$/',
+ 'mobile' => '/^(?:1[13-9]\\d|(?:3[78]|44)[02-9]|6(?:44|6[02-9]))\\d{7}$/',
+ 'tollfree' => '/^80[03]\\d{7}$/',
+ 'voip' => '/^96(?:0[49]|1[0-4]|6[69])\\d{6}$/',
+ 'shortcode' => '/^1(?:0(?:[39]|5(?:0\\d|[1-4])|6\\d{2}|7[0-4]|8[0-29])|1[6-9]|2(?:2[0-5]|[34])|3(?:1\\d?|3\\d|6[3-6])|4(?:0\\d|1\\d{2})|5[2-9])$/',
+ 'emergency' => '/^10[0-2]|999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'fixed' => '/^\\d{6,9}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'shortcode' => '/^\\d{3,5}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BE.php b/module/Zend/I18n/src/Validator/PhoneNumber/BE.php
new file mode 100644
index 00000000..154a1ead
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BE.php
@@ -0,0 +1,32 @@
+ '32',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{7,8}$/',
+ 'fixed' => '/^(?:1[0-69]|[23][2-8]|[49][23]|5\\d|6[013-57-9]|71)\\d{6}|8(?:0[1-9]|[1-79]\\d)\\d{5}$/',
+ 'mobile' => '/^4(?:[679]\\d|8[03-9])\\d{6}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'premium' => '/^(?:90|7[07])\\d{6}$/',
+ 'uan' => '/^78\\d{6}$/',
+ 'emergency' => '/^1(?:0[01]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,9}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'uan' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BF.php b/module/Zend/I18n/src/Validator/PhoneNumber/BF.php
new file mode 100644
index 00000000..5f891ee1
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BF.php
@@ -0,0 +1,24 @@
+ '226',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[24-7]\\d{7}$/',
+ 'fixed' => '/^(?:20(?:49|5[23]|9[016-9])|40(?:4[56]|5[4-6]|7[0179])|50[34]\\d)\\d{4}$/',
+ 'mobile' => '/^(?:6(?:[056]\\d|1[0-3]|8[0-2]|90)|7(?:[02-68]\\d|1[0-4689]|7[0-69]|9[0-689]))\\d{5}$/',
+ 'emergency' => '/^1[78]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BG.php b/module/Zend/I18n/src/Validator/PhoneNumber/BG.php
new file mode 100644
index 00000000..474e870a
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BG.php
@@ -0,0 +1,31 @@
+ '359',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[23567]\\d{5,7}|[489]\\d{6,8}$/',
+ 'fixed' => '/^2(?:[0-8]\\d{5,6}|9\\d{4,6})|(?:[36]\\d|5[1-9]|8[1-6]|9[1-7])\\d{5,6}|(?:4(?:[124-7]\\d|3[1-6])|7(?:0[1-9]|[1-9]\\d))\\d{4,5}$/',
+ 'mobile' => '/^(?:8[7-9]|98)\\d{7}|4(?:3[0789]|8\\d)\\d{5}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'premium' => '/^90\\d{6}$/',
+ 'personal' => '/^700\\d{5}$/',
+ 'emergency' => '/^1(?:12|50|6[06])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,9}$/',
+ 'fixed' => '/^\\d{5,8}$/',
+ 'mobile' => '/^\\d{8,9}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BH.php b/module/Zend/I18n/src/Validator/PhoneNumber/BH.php
new file mode 100644
index 00000000..59de4810
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BH.php
@@ -0,0 +1,27 @@
+ '973',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[136-9]\\d{7}$/',
+ 'fixed' => '/^(?:1(?:3[3-6]|6[0156]|7\\d)\\d|6(?:1[16]\\d|6(?:0\\d|3[12]|44)|9(?:69|9[6-9]))|77\\d{2})\\d{4}$/',
+ 'mobile' => '/^(?:3(?:[23469]\\d|5[35]|77|8[348])\\d|6(?:1[16]\\d|6(?:[06]\\d|3[03-9]|44)|9(?:69|9[6-9]))|77\\d{2})\\d{4}$/',
+ 'tollfree' => '/^80\\d{6}$/',
+ 'premium' => '/^(?:87|9[014578])\\d{6}$/',
+ 'shared' => '/^84\\d{6}$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BI.php b/module/Zend/I18n/src/Validator/PhoneNumber/BI.php
new file mode 100644
index 00000000..ad36456d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BI.php
@@ -0,0 +1,24 @@
+ '257',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[27]\\d{7}$/',
+ 'fixed' => '/^22(?:2[0-7]|[3-5]0)\\d{4}$/',
+ 'mobile' => '/^(?:29\\d|7(?:1[1-3]|[4-9]\\d))\\d{5}$/',
+ 'emergency' => '/^11[78]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BJ.php b/module/Zend/I18n/src/Validator/PhoneNumber/BJ.php
new file mode 100644
index 00000000..bbd0dfcf
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BJ.php
@@ -0,0 +1,32 @@
+ '229',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2689]\\d{7}|7\\d{3}$/',
+ 'fixed' => '/^2(?:02|1[037]|2[45]|3[68])\\d{5}$/',
+ 'mobile' => '/^(?:6[46]|9[03-8])\\d{6}$/',
+ 'tollfree' => '/^7[3-5]\\d{2}$/',
+ 'voip' => '/^857[58]\\d{4}$/',
+ 'uan' => '/^81\\d{6}$/',
+ 'emergency' => '/^11[78]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4,8}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{4}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'uan' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BL.php b/module/Zend/I18n/src/Validator/PhoneNumber/BL.php
new file mode 100644
index 00000000..8bc8fc99
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BL.php
@@ -0,0 +1,24 @@
+ '590',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[56]\\d{8}$/',
+ 'fixed' => '/^590(?:2[7-9]|5[12]|87)\\d{4}$/',
+ 'mobile' => '/^690(?:10|2[27]|66|77|8[78])\\d{4}$/',
+ 'emergency' => '/^18$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BM.php b/module/Zend/I18n/src/Validator/PhoneNumber/BM.php
new file mode 100644
index 00000000..eac7d93d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BM.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[4589]\\d{9}$/',
+ 'fixed' => '/^441(?:2(?:02|23|61|[3479]\\d)|[46]\\d{2}|5(?:4\\d|60|89)|824)\\d{4}$/',
+ 'mobile' => '/^441(?:[37]\\d|5[0-39])\\d{5}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BN.php b/module/Zend/I18n/src/Validator/PhoneNumber/BN.php
new file mode 100644
index 00000000..9f6f347f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BN.php
@@ -0,0 +1,24 @@
+ '673',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-578]\\d{6}$/',
+ 'fixed' => '/^[2-5]\\d{6}$/',
+ 'mobile' => '/^[78]\\d{6}$/',
+ 'emergency' => '/^99[135]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BO.php b/module/Zend/I18n/src/Validator/PhoneNumber/BO.php
new file mode 100644
index 00000000..4339ee28
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BO.php
@@ -0,0 +1,26 @@
+ '591',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[23467]\\d{7}$/',
+ 'fixed' => '/^(?:2(?:2\\d{2}|5(?:11|[258]\\d|9[67])|6(?:12|2\\d|9[34])|8(?:2[34]|39|62))|3(?:3\\d{2}|4(?:6\\d|8[24])|8(?:25|42|5[257]|86|9[25])|9(?:2\\d|3[234]|4[248]|5[24]|6[2-6]|7\\d))|4(?:4\\d{2}|6(?:11|[24689]\\d|72)))\\d{4}$/',
+ 'mobile' => '/^[67]\\d{7}$/',
+ 'emergency' => '/^11[089]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'fixed' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BQ.php b/module/Zend/I18n/src/Validator/PhoneNumber/BQ.php
new file mode 100644
index 00000000..a1a93674
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BQ.php
@@ -0,0 +1,24 @@
+ '599',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[347]\\d{6}$/',
+ 'fixed' => '/^(?:318[023]|416[0239]|7(?:1[578]|50)\\d)\\d{3}$/',
+ 'mobile' => '/^(?:318[1456]|416[15-8]|7(?:0[01]|[89]\\d)\\d)\\d{3}|$/',
+ 'emergency' => '/^112|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BR.php b/module/Zend/I18n/src/Validator/PhoneNumber/BR.php
new file mode 100644
index 00000000..7f2aa0e3
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BR.php
@@ -0,0 +1,29 @@
+ '55',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-46-9]\\d{7,10}|5\\d{8,9}$/',
+ 'fixed' => '/^1[1-9][2-5]\\d{7}|(?:[4689][1-9]|2[12478]|3[1-578]|5[13-5]|7[13-579])[2-5]\\d{7}$/',
+ 'mobile' => '/^1(?:1(?:5[347]|[6-8]\\d|9\\d{1,2})|[2-9][6-9]\\d)\\d{6}|(?:[4689][1-9]|2[12478]|3[1-578]|5[13-5]|7[13-579])[6-9]\\d{7}$/',
+ 'tollfree' => '/^800\\d{6,7}$/',
+ 'premium' => '/^[359]00\\d{6,7}$/',
+ 'shared' => '/^[34]00\\d{5}$/',
+ 'emergency' => '/^1(?:12|28|9[023])|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,11}$/',
+ 'mobile' => '/^\\d{10,11}$/',
+ 'shared' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BS.php b/module/Zend/I18n/src/Validator/PhoneNumber/BS.php
new file mode 100644
index 00000000..7b797bf3
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BS.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2589]\\d{9}$/',
+ 'fixed' => '/^242(?:3(?:02|[236][1-9]|4[0-24-9]|5[0-68]|7[3467]|8[0-4]|9[2-467])|461|502|6(?:12|7[67]|8[78]|9[89])|702)\\d{4}$/',
+ 'mobile' => '/^242(?:3(?:5[79]|[79]5)|4(?:[2-4][1-9]|5[1-8]|6[2-8]|7\\d|81)|5(?:2[45]|3[35]|44|5[1-9]|65|77)|6[34]6|727)\\d{4}$/',
+ 'tollfree' => '/^242300\\d{4}|8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^91[19]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BT.php b/module/Zend/I18n/src/Validator/PhoneNumber/BT.php
new file mode 100644
index 00000000..a9c4ce72
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BT.php
@@ -0,0 +1,26 @@
+ '975',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-8]\\d{6,7}$/',
+ 'fixed' => '/^(?:2[3-6]|[34][5-7]|5[236]|6[2-46]|7[246]|8[2-4])\\d{5}$/',
+ 'mobile' => '/^[17]7\\d{6}$/',
+ 'emergency' => '/^11[023]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,8}$/',
+ 'fixed' => '/^\\d{6,7}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BW.php b/module/Zend/I18n/src/Validator/PhoneNumber/BW.php
new file mode 100644
index 00000000..b8ba0349
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BW.php
@@ -0,0 +1,30 @@
+ '267',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-79]\\d{6,7}$/',
+ 'fixed' => '/^(?:2(?:4[0-48]|6[0-24]|9[0578])|3(?:1[0235-9]|55|6\\d|7[01]|9[0-57])|4(?:6[03]|7[1267]|9[0-5])|5(?:3[0389]|4[0489]|7[1-47]|88|9[0-49])|6(?:2[1-35]|5[149]|8[067]))\\d{4}$/',
+ 'mobile' => '/^7(?:[1-35]\\d{6}|[46][0-7]\\d{5}|7[01]\\d{5})$/',
+ 'premium' => '/^90\\d{5}$/',
+ 'voip' => '/^79[12][01]\\d{4}$/',
+ 'emergency' => '/^99[789]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{7}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BY.php b/module/Zend/I18n/src/Validator/PhoneNumber/BY.php
new file mode 100644
index 00000000..570e314c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BY.php
@@ -0,0 +1,30 @@
+ '375',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-4]\\d{8}|[89]\\d{9,10}$/',
+ 'fixed' => '/^(?:1(?:5(?:1[1-5]|2\\d|6[2-4]|9[1-7])|6(?:[235]\\d|4[1-7])|7\\d{2})|2(?:1(?:[246]\\d|3[0-35-9]|5[1-9])|2(?:[235]\\d|4[0-8])|3(?:2\\d|3[02-79]|4[024-7]|5[0-7])))\\d{5}$/',
+ 'mobile' => '/^(?:2(?:5[5679]|9[1-9])|33\\d|44\\d)\\d{6}$/',
+ 'tollfree' => '/^8(?:0[13]|20\\d)\\d{7}$/',
+ 'premium' => '/^(?:810|902)\\d{7}$/',
+ 'emergency' => '/^1(?:0[123]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,11}$/',
+ 'fixed' => '/^\\d{7,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{10,11}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/BZ.php b/module/Zend/I18n/src/Validator/PhoneNumber/BZ.php
new file mode 100644
index 00000000..f884f185
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/BZ.php
@@ -0,0 +1,28 @@
+ '501',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-8]\\d{6}|0\\d{10}$/',
+ 'fixed' => '/^[234578][02]\\d{5}$/',
+ 'mobile' => '/^6[0-367]\\d{5}$/',
+ 'tollfree' => '/^0800\\d{7}$/',
+ 'emergency' => '/^9(?:0|11)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{4})?$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'mobile' => '/^\\d{7}$/',
+ 'tollfree' => '/^\\d{11}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CA.php b/module/Zend/I18n/src/Validator/PhoneNumber/CA.php
new file mode 100644
index 00000000..23cec320
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CA.php
@@ -0,0 +1,29 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{9}|3\\d{6}$/',
+ 'fixed' => '/^(?:2(?:04|[23]6|[48]9|50)|3(?:06|43|65)|4(?:03|1[68]|3[178]|5[06])|5(?:0[06]|1[49]|79|8[17])|6(?:0[04]|13|39|47)|7(?:0[059]|80|78)|8(?:[06]7|19|73)|90[25])[2-9]\\d{6}|310\\d{4}$/',
+ 'mobile' => '/^(?:2(?:04|[23]6|[48]9|50)|3(?:06|43|65)|4(?:03|1[68]|3[178]|5[06])|5(?:0[06]|1[49]|79|8[17])|6(?:0[04]|13|39|47)|7(?:0[059]|80|78)|8(?:[06]7|19|73)|90[25])[2-9]\\d{6}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}|310\\d{4}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^112|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CC.php b/module/Zend/I18n/src/Validator/PhoneNumber/CC.php
new file mode 100644
index 00000000..98e9b2cf
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CC.php
@@ -0,0 +1,34 @@
+ '61',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1458]\\d{5,9}$/',
+ 'fixed' => '/^89162\\d{4}$/',
+ 'mobile' => '/^4(?:[0-2]\\d|3[0-57-9]|4[47-9]|5[0-37-9]|6[6-9]|7[07-9]|8[7-9])\\d{6}$/',
+ 'tollfree' => '/^1(?:80(?:0\\d{2})?|3(?:00\\d{2})?)\\d{4}$/',
+ 'premium' => '/^190[0126]\\d{6}$/',
+ 'personal' => '/^500\\d{6}$/',
+ 'voip' => '/^550\\d{6}$/',
+ 'emergency' => '/^000|112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'fixed' => '/^\\d{8,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{6,10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{9}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CD.php b/module/Zend/I18n/src/Validator/PhoneNumber/CD.php
new file mode 100644
index 00000000..15fa2609
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CD.php
@@ -0,0 +1,23 @@
+ '243',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-6]\\d{6}|8\\d{6,8}|9\\d{8}$/',
+ 'fixed' => '/^[1-6]\\d{6}$/',
+ 'mobile' => '/^8(?:[0-259]\\d{2}|[48])\\d{5}|9[7-9]\\d{7}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'fixed' => '/^\\d{7}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CF.php b/module/Zend/I18n/src/Validator/PhoneNumber/CF.php
new file mode 100644
index 00000000..04a43e7b
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CF.php
@@ -0,0 +1,23 @@
+ '236',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[278]\\d{7}$/',
+ 'fixed' => '/^2[12]\\d{6}$/',
+ 'mobile' => '/^7[0257]\\d{6}$/',
+ 'premium' => '/^8776\\d{4}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CG.php b/module/Zend/I18n/src/Validator/PhoneNumber/CG.php
new file mode 100644
index 00000000..b8e7922f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CG.php
@@ -0,0 +1,23 @@
+ '242',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[028]\\d{8}$/',
+ 'fixed' => '/^222[1-589]\\d{5}$/',
+ 'mobile' => '/^0[14-6]\\d{7}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CH.php b/module/Zend/I18n/src/Validator/PhoneNumber/CH.php
new file mode 100644
index 00000000..dea29ea4
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CH.php
@@ -0,0 +1,36 @@
+ '41',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{8}|860\\d{9}$/',
+ 'fixed' => '/^(?:2[12467]|3[1-4]|4[134]|5[12568]|6[12]|[7-9]1)\\d{7}$/',
+ 'mobile' => '/^7[46-9]\\d{7}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'premium' => '/^90[016]\\d{6}$/',
+ 'shared' => '/^84[0248]\\d{6}$/',
+ 'personal' => '/^878\\d{6}$/',
+ 'voicemail' => '/^860\\d{9}$/',
+ 'emergency' => '/^1(?:1[278]|44)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}(?:\\d{3})?$/',
+ 'fixed' => '/^\\d{9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'shared' => '/^\\d{9}$/',
+ 'personal' => '/^\\d{9}$/',
+ 'voicemail' => '/^\\d{12}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CI.php b/module/Zend/I18n/src/Validator/PhoneNumber/CI.php
new file mode 100644
index 00000000..e61d5f35
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CI.php
@@ -0,0 +1,24 @@
+ '225',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[02-6]\\d{7}$/',
+ 'fixed' => '/^(?:2(?:0[023]|1[02357]|[23][045]|4[03-5])|3(?:0[06]|1[069]|[2-4][07]|5[09]|6[08]))\\d{5}$/',
+ 'mobile' => '/^(?:0[1-9]|4[0-24-9]|5[057-9]|6[05679])\\d{6}$/',
+ 'emergency' => '/^1(?:1[01]|[78]0)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CK.php b/module/Zend/I18n/src/Validator/PhoneNumber/CK.php
new file mode 100644
index 00000000..6cf6c2b8
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CK.php
@@ -0,0 +1,24 @@
+ '682',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-57]\\d{4}$/',
+ 'fixed' => '/^(?:2\\d|3[13-7]|4[1-5])\\d{3}$/',
+ 'mobile' => '/^(?:5[0-68]|7\\d)\\d{3}$/',
+ 'emergency' => '/^99[689]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CL.php b/module/Zend/I18n/src/Validator/PhoneNumber/CL.php
new file mode 100644
index 00000000..676ad716
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CL.php
@@ -0,0 +1,32 @@
+ '56',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^(?:[2-9]|600|123)\\d{7,8}$/',
+ 'fixed' => '/^(?:[23]2|41|58)\\d{7}|(?:3[3-5]|4[235]|5[1-357]|6[13-57]|7[1-35])\\d{6,7}$/',
+ 'mobile' => '/^9[5-9]\\d{7}$/',
+ 'tollfree' => '/^800\\d{6}|1230\\d{7}$/',
+ 'shared' => '/^600\\d{7,8}$/',
+ 'voip' => '/^44\\d{7}$/',
+ 'emergency' => '/^13[123]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,11}$/',
+ 'fixed' => '/^\\d{6,9}$/',
+ 'mobile' => '/^\\d{8,9}$/',
+ 'tollfree' => '/^\\d{9,11}$/',
+ 'shared' => '/^\\d{10,11}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CM.php b/module/Zend/I18n/src/Validator/PhoneNumber/CM.php
new file mode 100644
index 00000000..34b5319f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CM.php
@@ -0,0 +1,26 @@
+ '237',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[237-9]\\d{7}$/',
+ 'fixed' => '/^(?:22|33)\\d{6}$/',
+ 'mobile' => '/^[79]\\d{7}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'premium' => '/^88\\d{6}$/',
+ 'emergency' => '/^1?1[37]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CN.php b/module/Zend/I18n/src/Validator/PhoneNumber/CN.php
new file mode 100644
index 00000000..7173279e
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CN.php
@@ -0,0 +1,31 @@
+ '86',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-7]\\d{7,11}|8[0-357-9]\\d{6,9}|9(?:5\\d{3}|\\d{9})$/',
+ 'fixed' => '/^21\\d{8,10}|(?:10|2[02-57-9]|3(?:11|7[179])|4(?:[15]1|3[12])|5(?:1\\d|2[37]|3[12]|51|7[13-79]|9[15])|7(?:31|5[457]|6[09]|91)|898)\\d{8}|(?:3(?:1[02-9]|35|49|5\\d|7[02-68]|9[1-68])|4(?:1[02-9]|2[179]|3[3-9]|5[2-9]|6[4789]|7\\d|8[23])|5(?:3[03-9]|4[36]|5[02-9]|6[1-46]|7[028]|80|9[2-46-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[17]\\d|2[248]|3[04-9]|4[3-6]|5[0-3689]|6[2368]|9[02-9])|8(?:1[236-8]|2[5-7]|[37]\\d|5[1-9]|8[3678]|9[1-7])|9(?:0[1-3689]|1[1-79]|[379]\\d|4[13]|5[1-5]))\\d{7}|80(?:29|6[03578]|7[018]|81)\\d{4}$/',
+ 'mobile' => '/^1(?:3\\d|4[5-9]|[58][0-35-9]|66|7[3567]|18[015-9]|9[89])\\d{8}$/',
+ 'tollfree' => '/^(?:10)?800\\d{7}$/',
+ 'premium' => '/^16[08]\\d{5}$/',
+ 'shared' => '/^400\\d{7}|95\\d{3}$/',
+ 'emergency' => '/^1(?:1[09]|20)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4,12}$/',
+ 'mobile' => '/^1\\d{10}$/',
+ 'tollfree' => '/^\\d{10,12}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'shared' => '/^\\d{5}(?:\\d{5})?$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CO.php b/module/Zend/I18n/src/Validator/PhoneNumber/CO.php
new file mode 100644
index 00000000..54ae5071
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CO.php
@@ -0,0 +1,30 @@
+ '57',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^(?:[13]\\d{0,3}|[24-8])\\d{7}$/',
+ 'fixed' => '/^[124-8][2-9]\\d{6}$/',
+ 'mobile' => '/^3(?:0[0-24]|1\\d|2[01])\\d{7}$/',
+ 'tollfree' => '/^1800\\d{7}$/',
+ 'premium' => '/^19(?:0[01]|4[78])\\d{7}$/',
+ 'emergency' => '/^1(?:1[29]|23|32|56)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,11}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{11}$/',
+ 'premium' => '/^\\d{11}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CR.php b/module/Zend/I18n/src/Validator/PhoneNumber/CR.php
new file mode 100644
index 00000000..fe99fa3a
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CR.php
@@ -0,0 +1,34 @@
+ '506',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[24-9]\\d{7,9}$/',
+ 'fixed' => '/^2[24-7]\\d{6}$/',
+ 'mobile' => '/^5(?:0[0-4]|7[01])\\d{5}|[67][0-2]\\d{6}|8[3-9]\\d{6}$/',
+ 'tollfree' => '/^800\\d{7}$/',
+ 'premium' => '/^90[059]\\d{7}$/',
+ 'voip' => '/^210[0-6]\\d{4}|4(?:0(?:[04]0\\d{4}|10[0-3]\\d{3}|2(?:00\\d|900)\\d{2}|3[01]\\d{4}|5\\d{5}|70[01]\\d{3})|1[01]\\d{5}|400\\d{4})|5100\\d{4}$/',
+ 'shortcode' => '/^1(?:0(?:00|15|2[2-4679])|1(?:1[0-35-9]|37|[46]6|75|8[79]|9[0-379])|2(?:00|[12]2|34|55)|333|400|5(?:15|5[15])|693|7(?:00|1[789]|2[02]|[67]7))$/',
+ 'emergency' => '/^112|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,10}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'shortcode' => '/^\\d{4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CU.php b/module/Zend/I18n/src/Validator/PhoneNumber/CU.php
new file mode 100644
index 00000000..e8080dd1
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CU.php
@@ -0,0 +1,27 @@
+ '53',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-57]\\d{5,7}$/',
+ 'fixed' => '/^2[1-4]\\d{5,6}|3(?:1\\d{6}|[23]\\d{4,6})|4(?:[125]\\d{5,6}|[36]\\d{6}|[78]\\d{4,6})|7\\d{6,7}$/',
+ 'mobile' => '/^5\\d{7}$/',
+ 'shortcode' => '/^1(?:1(?:6111|8)|40)$/',
+ 'emergency' => '/^10[456]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4,8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CV.php b/module/Zend/I18n/src/Validator/PhoneNumber/CV.php
new file mode 100644
index 00000000..69a61662
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CV.php
@@ -0,0 +1,24 @@
+ '238',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[259]\\d{6}$/',
+ 'fixed' => '/^2(?:2[1-7]|3[0-8]|4[12]|5[1256]|6\\d|7[1-3]|8[1-5])\\d{4}$/',
+ 'mobile' => '/^(?:9\\d|59)\\d{5}$/',
+ 'emergency' => '/^13[012]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CW.php b/module/Zend/I18n/src/Validator/PhoneNumber/CW.php
new file mode 100644
index 00000000..2d9c1c5e
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CW.php
@@ -0,0 +1,27 @@
+ '599',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[169]\\d{6,7}$/',
+ 'fixed' => '/^9(?:[48]\\d{2}|50\\d|7(?:2[0-2]|[34]\\d|6[35-7]|77))\\d{4}$/',
+ 'mobile' => '/^9(?:5(?:[1246]\\d|3[01])|6(?:[1679]\\d|3[01]))\\d{4}$/',
+ 'pager' => '/^955\\d{5}$/',
+ 'shared' => '/^(?:10|69)\\d{5}$/',
+ 'emergency' => '/^112|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'shared' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CX.php b/module/Zend/I18n/src/Validator/PhoneNumber/CX.php
new file mode 100644
index 00000000..65e711a0
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CX.php
@@ -0,0 +1,34 @@
+ '61',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1458]\\d{5,9}$/',
+ 'fixed' => '/^89164\\d{4}$/',
+ 'mobile' => '/^4(?:[0-2]\\d|3[0-57-9]|4[47-9]|5[0-37-9]|6[6-9]|7[07-9]|8[7-9])\\d{6}$/',
+ 'tollfree' => '/^1(?:80(?:0\\d{2})?|3(?:00\\d{2})?)\\d{4}$/',
+ 'premium' => '/^190[0126]\\d{6}$/',
+ 'personal' => '/^500\\d{6}$/',
+ 'voip' => '/^550\\d{6}$/',
+ 'emergency' => '/^000|112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'fixed' => '/^\\d{8,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{6,10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{9}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CY.php b/module/Zend/I18n/src/Validator/PhoneNumber/CY.php
new file mode 100644
index 00000000..5208f3a7
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CY.php
@@ -0,0 +1,29 @@
+ '357',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[257-9]\\d{7}$/',
+ 'fixed' => '/^2[2-6]\\d{6}$/',
+ 'mobile' => '/^9[5-79]\\d{6}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'premium' => '/^90[09]\\d{5}$/',
+ 'shared' => '/^80[1-9]\\d{5}$/',
+ 'personal' => '/^700\\d{5}$/',
+ 'uan' => '/^(?:50|77)\\d{6}$/',
+ 'emergency' => '/^1(?:12|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/CZ.php b/module/Zend/I18n/src/Validator/PhoneNumber/CZ.php
new file mode 100644
index 00000000..56b77a34
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/CZ.php
@@ -0,0 +1,34 @@
+ '420',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-8]\\d{8}|9\\d{8,11}$/',
+ 'fixed' => '/^2\\d{8}|(?:3[1257-9]|4[16-9]|5[13-9])\\d{7}$/',
+ 'mobile' => '/^(?:60[1-8]|7(?:0[2-5]|[2379]\\d))\\d{6}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'premium' => '/^9(?:0[05689]|76)\\d{6}$/',
+ 'shared' => '/^8[134]\\d{7}$/',
+ 'personal' => '/^70[01]\\d{6}$/',
+ 'voip' => '/^9[17]0\\d{6}$/',
+ 'uan' => '/^9(?:5[056]|7[234])\\d{6}$/',
+ 'voicemail' => '/^9(?:3\\d{9}|6\\d{7,10})$/',
+ 'shortcode' => '/^1(?:1(?:6\\d{3}|8\\d)|2\\d{2,3}|3\\d{3,4}|4\\d{3}|99)$/',
+ 'emergency' => '/^1(?:12|5[058])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9,12}$/',
+ 'voicemail' => '/^\\d{9,12}$/',
+ 'shortcode' => '/^\\d{4,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/DE.php b/module/Zend/I18n/src/Validator/PhoneNumber/DE.php
new file mode 100644
index 00000000..3056528f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/DE.php
@@ -0,0 +1,41 @@
+ '49',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-35-9]\\d{3,14}|4(?:[0-8]\\d{4,12}|9(?:[0-37]\\d|4(?:[1-35-8]|4\\d?)|5\\d{1,2}|6[1-8]\\d?)\\d{2,7})$/',
+ 'fixed' => '/^[246]\\d{5,13}|3(?:[03-9]\\d{4,13}|2\\d{9})|5(?:0[2-8]|[1256]\\d|[38][0-8]|4\\d{0,2}|[79][0-7])\\d{3,11}|7(?:0[2-8]|[1-9]\\d)\\d{3,10}|8(?:0[2-9]|[1-9]\\d)\\d{3,10}|9(?:0[6-9]|[1-9]\\d)\\d{3,10}$/',
+ 'mobile' => '/^1(?:5[0-2579]\\d{8}|6[023]\\d{7,8}|7(?:[0-57-9]\\d?|6\\d)\\d{7})$/',
+ 'pager' => '/^16(?:4\\d{1,10}|[89]\\d{1,11})$/',
+ 'tollfree' => '/^800\\d{7,10}$/',
+ 'premium' => '/^900(?:[135]\\d{6}|9\\d{7})$/',
+ 'shared' => '/^180\\d{5,11}$/',
+ 'personal' => '/^700\\d{8}$/',
+ 'uan' => '/^18(?:1\\d{5,11}|[2-9]\\d{8})$/',
+ 'voicemail' => '/^17799\\d{7,8}$/',
+ 'shortcode' => '/^115$/',
+ 'emergency' => '/^11[02]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{2,15}$/',
+ 'mobile' => '/^\\d{10,11}$/',
+ 'pager' => '/^\\d{4,14}$/',
+ 'tollfree' => '/^\\d{10,13}$/',
+ 'premium' => '/^\\d{10,11}$/',
+ 'shared' => '/^\\d{8,14}$/',
+ 'personal' => '/^\\d{11}$/',
+ 'uan' => '/^\\d{8,14}$/',
+ 'voicemail' => '/^\\d{12,13}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/DJ.php b/module/Zend/I18n/src/Validator/PhoneNumber/DJ.php
new file mode 100644
index 00000000..04bb7e30
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/DJ.php
@@ -0,0 +1,24 @@
+ '253',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[27]\\d{7}$/',
+ 'fixed' => '/^2(?:1[2-5]|7[45])\\d{5}$/',
+ 'mobile' => '/^77[6-8]\\d{5}$/',
+ 'emergency' => '/^1[78]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/DK.php b/module/Zend/I18n/src/Validator/PhoneNumber/DK.php
new file mode 100644
index 00000000..1d6f8ebd
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/DK.php
@@ -0,0 +1,26 @@
+ '45',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{7}$/',
+ 'fixed' => '/^(?:[2-7]\\d|8[126-9]|9[126-9])\\d{6}$/',
+ 'mobile' => '/^(?:[2-7]\\d|8[126-9]|9[126-9])\\d{6}$/',
+ 'tollfree' => '/^80\\d{6}$/',
+ 'premium' => '/^90\\d{6}$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/DM.php b/module/Zend/I18n/src/Validator/PhoneNumber/DM.php
new file mode 100644
index 00000000..2a830494
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/DM.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[57-9]\\d{9}$/',
+ 'fixed' => '/^767(?:2(?:55|66)|4(?:2[01]|4[0-25-9])|50[0-4])\\d{4}$/',
+ 'mobile' => '/^767(?:2(?:[234689]5|7[5-7])|31[5-7]|61[2-7])\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^333|9(?:11|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/DO.php b/module/Zend/I18n/src/Validator/PhoneNumber/DO.php
new file mode 100644
index 00000000..abd5f021
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/DO.php
@@ -0,0 +1,30 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[589]\\d{9}$/',
+ 'fixed' => '/^8(?:[04]9[2-9]\\d{6}|29(?:2(?:[0-59]\\d|6[04-9]|7[0-27]|8[0237-9])|3(?:[0-35-9]\\d|4[7-9])|[45]\\d{2}|6(?:[0-27-9]\\d|[3-5][1-9]|6[0135-8])|7(?:0[013-9]|[1-37]\\d|4[1-35689]|5[1-4689]|6[1-57-9]|8[1-79]|9[1-8])|8(?:0[146-9]|1[0-48]|[248]\\d|3[1-79]|5[01589]|6[013-68]|7[124-8]|9[0-8])|9(?:[0-24]\\d|3[02-46-9]|5[0-79]|60|7[0169]|8[57-9]|9[02-9]))\\d{4})$/',
+ 'mobile' => '/^8[024]9[2-9]\\d{6}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^112|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/DZ.php b/module/Zend/I18n/src/Validator/PhoneNumber/DZ.php
new file mode 100644
index 00000000..049bddf8
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/DZ.php
@@ -0,0 +1,33 @@
+ '213',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^(?:[1-4]|[5-9]\\d)\\d{7}$/',
+ 'fixed' => '/^(?:1\\d|2[014-79]|3[0-8]|4[0135689])\\d{6}|9619\\d{5}$/',
+ 'mobile' => '/^(?:5[56]|7[7-9])\\d{7}|6(?:[569]\\d|70)\\d{6}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'premium' => '/^80[3-689]1\\d{5}$/',
+ 'shared' => '/^80[12]1\\d{5}$/',
+ 'voip' => '/^98[23]\\d{6}$/',
+ 'emergency' => '/^1[47]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'shared' => '/^\\d{9}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/EC.php b/module/Zend/I18n/src/Validator/PhoneNumber/EC.php
new file mode 100644
index 00000000..aa3e4aac
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/EC.php
@@ -0,0 +1,30 @@
+ '593',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^1\\d{9,10}|[2-8]\\d{7}|9\\d{8}$/',
+ 'fixed' => '/^[2-7][2-7]\\d{6}$/',
+ 'mobile' => '/^9(?:[2-7]9|[89]\\d)\\d{6}$/',
+ 'tollfree' => '/^1800\\d{6,7}$/',
+ 'voip' => '/^[2-7]890\\d{4}$/',
+ 'emergency' => '/^1(?:0[12]|12)|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,11}$/',
+ 'fixed' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{10,11}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/EE.php b/module/Zend/I18n/src/Validator/PhoneNumber/EE.php
new file mode 100644
index 00000000..66a97559
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/EE.php
@@ -0,0 +1,36 @@
+ '372',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^1\\d{3,4}|[3-9]\\d{6,7}|800\\d{6,7}$/',
+ 'fixed' => '/^(?:3[23589]|4(?:0\\d|[3-8])|6\\d|7[1-9]|88)\\d{5}$/',
+ 'mobile' => '/^(?:5\\d|8[1-5])\\d{6}|5(?:[02]\\d{2}|1(?:[0-8]\\d|95)|5[0-478]\\d|64[0-4]|65[1-589])\\d{3}$/',
+ 'tollfree' => '/^800(?:0\\d{3}|1\\d|[2-9])\\d{3}$/',
+ 'premium' => '/^900\\d{4}$/',
+ 'personal' => '/^70[0-2]\\d{5}$/',
+ 'uan' => '/^1(?:2[01245]|3[0-6]|4[1-489]|5[0-59]|6[1-46-9]|7[0-27-9]|8[189]|9[012])\\d{1,2}$/',
+ 'shortcode' => '/^1(?:1[13-9]|[2-9]\\d)$/',
+ 'emergency' => '/^11[02]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4,10}$/',
+ 'fixed' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{7,8}$/',
+ 'tollfree' => '/^\\d{7,10}$/',
+ 'premium' => '/^\\d{7}$/',
+ 'personal' => '/^\\d{8}$/',
+ 'uan' => '/^\\d{4,5}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/EG.php b/module/Zend/I18n/src/Validator/PhoneNumber/EG.php
new file mode 100644
index 00000000..418e893e
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/EG.php
@@ -0,0 +1,30 @@
+ '20',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^1\\d{4,9}|[2456]\\d{8}|3\\d{7}|[89]\\d{8,9}$/',
+ 'fixed' => '/^(?:1(3[23]\\d|5[23])|2[2-4]\\d{2}|3\\d{2}|4(?:0[2-5]|[578][23]|64)\\d|5(?:0[2-7]|[57][23])\\d|6[24-689]3\\d|8(?:2[2-57]|4[26]|6[237]|8[2-4])\\d|9(?:2[27]|3[24]|52|6[2356]|7[2-4])\\d)\\d{5}|1[69]\\d{3}$/',
+ 'mobile' => '/^1(?:0[01269]|1[1245]|2[0-278])\\d{7}$/',
+ 'tollfree' => '/^800\\d{7}$/',
+ 'premium' => '/^900\\d{7}$/',
+ 'emergency' => '/^1(?:2[23]|80)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,10}$/',
+ 'fixed' => '/^\\d{5,9}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/EH.php b/module/Zend/I18n/src/Validator/PhoneNumber/EH.php
new file mode 100644
index 00000000..5e99a76d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/EH.php
@@ -0,0 +1,26 @@
+ '212',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5689]\\d{8}$/',
+ 'fixed' => '/^528[89]\\d{5}$/',
+ 'mobile' => '/^6(?:0[0-6]|[14-7]\\d|2[2-46-9]|3[03-8]|8[01]|99)\\d{6}$/',
+ 'tollfree' => '/^80\\d{7}$/',
+ 'premium' => '/^89\\d{7}$/',
+ 'emergency' => '/^1(?:[59]|77)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/ER.php b/module/Zend/I18n/src/Validator/PhoneNumber/ER.php
new file mode 100644
index 00000000..b0f60e12
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/ER.php
@@ -0,0 +1,23 @@
+ '291',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[178]\\d{6}$/',
+ 'fixed' => '/^1(?:1[12568]|20|40|55|6[146])\\d{4}|8\\d{6}$/',
+ 'mobile' => '/^17[1-3]\\d{4}|7\\d{6}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,7}$/',
+ 'mobile' => '/^\\d{7}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/ES.php b/module/Zend/I18n/src/Validator/PhoneNumber/ES.php
new file mode 100644
index 00000000..f4d056e2
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/ES.php
@@ -0,0 +1,29 @@
+ '34',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5-9]\\d{8}$/',
+ 'fixed' => '/^8(?:[13]0|[28][0-8]|[47][1-9]|5[01346-9]|6[0457-9])\\d{6}|9(?:[1238][0-8]\\d{6}|4[1-9]\\d{6}|5\\d{7}|6(?:[0-8]\\d{6}|9(?:0(?:[0-57-9]\\d{4}|6(?:0[0-8]|1[1-9]|[2-9]\\d)\\d{2})|[1-9]\\d{5}))|7(?:[124-9]\\d{2}|3(?:[0-8]\\d|9[1-9]))\\d{4})$/',
+ 'mobile' => '/^(?:6\\d{6}|7[1-4]\\d{5}|9(?:6906(?:09|10)|7390\\d{2}))\\d{2}$/',
+ 'tollfree' => '/^[89]00\\d{6}$/',
+ 'premium' => '/^80[367]\\d{6}$/',
+ 'shared' => '/^90[12]\\d{6}$/',
+ 'personal' => '/^70\\d{7}$/',
+ 'uan' => '/^51\\d{7}$/',
+ 'emergency' => '/^0(?:[69][12]|8[05])|112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/ET.php b/module/Zend/I18n/src/Validator/PhoneNumber/ET.php
new file mode 100644
index 00000000..76a52737
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/ET.php
@@ -0,0 +1,25 @@
+ '251',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-59]\\d{8}$/',
+ 'fixed' => '/^(?:11(?:1(?:1[124]|2[2-57]|3[1-5]|5[5-8]|8[6-8])|2(?:13|3[6-8]|5[89]|7[05-9]|8[2-6])|3(?:2[01]|3[0-289]|4[1289]|7[1-4]|87)|4(?:1[69]|3[2-49]|4[0-3]|6[5-8])|5(?:1[57]|44|5[0-4])|6(?:18|2[69]|4[5-7]|5[1-5]|6[0-59]|8[015-8]))|2(?:2(?:11[1-9]|22[0-7]|33\\d|44[1467]|66[1-68])|5(?:11[124-6]|33[2-8]|44[1467]|55[14]|66[1-3679]|77[124-79]|880))|3(?:3(?:11[0-46-8]|22[0-6]|33[0134689]|44[04]|55[0-6]|66[01467])|4(?:44[0-8]|55[0-69]|66[0-3]|77[1-5]))|4(?:6(?:22[0-24-7]|33[1-5]|44[13-69]|55[14-689]|660|88[1-4])|7(?:11[1-9]|22[1-9]|33[13-7]|44[13-6]|55[1-689]))|5(?:7(?:227|55[05]|(?:66|77)[14-8])|8(?:11[149]|22[013-79]|33[0-68]|44[013-8]|550|66[1-5]|77\\d)))\\d{4}$/',
+ 'mobile' => '/^9(?:[1-3]\\d|5[89])\\d{6}$/',
+ 'emergency' => '/^9(?:11?|[23]|9[17])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/FI.php b/module/Zend/I18n/src/Validator/PhoneNumber/FI.php
new file mode 100644
index 00000000..6c994f85
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/FI.php
@@ -0,0 +1,31 @@
+ '358',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^1\\d{4,11}|[2-9]\\d{4,10}$/',
+ 'fixed' => '/^1(?:[3569][1-8]\\d{3,9}|[47]\\d{5,10})|2[1-8]\\d{3,9}|3(?:[1-8]\\d{3,9}|9\\d{4,8})|[5689][1-8]\\d{3,9}$/',
+ 'mobile' => '/^4\\d{5,10}|50\\d{4,8}$/',
+ 'tollfree' => '/^800\\d{4,7}$/',
+ 'premium' => '/^[67]00\\d{5,6}$/',
+ 'uan' => '/^[13]0\\d{4,8}|2(?:0(?:[016-8]\\d{3,7}|[2-59]\\d{2,7})|9\\d{4,8})|60(?:[12]\\d{5,6}|6\\d{7})|7(?:1\\d{7}|3\\d{8}|5[03-9]\\d{2,7})$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,12}$/',
+ 'mobile' => '/^\\d{6,11}$/',
+ 'tollfree' => '/^\\d{7,10}$/',
+ 'premium' => '/^\\d{8,9}$/',
+ 'uan' => '/^\\d{5,10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/FJ.php b/module/Zend/I18n/src/Validator/PhoneNumber/FJ.php
new file mode 100644
index 00000000..eee9d499
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/FJ.php
@@ -0,0 +1,30 @@
+ '679',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[36-9]\\d{6}|0\\d{10}$/',
+ 'fixed' => '/^(?:3[0-5]|6[25-7]|8[58])\\d{5}$/',
+ 'mobile' => '/^(?:7[0-467]|8[367]|9[02346-9])\\d{5}$/',
+ 'tollfree' => '/^0800\\d{7}$/',
+ 'shortcode' => '/^0(?:04|1[34]|8[1-4])|1(?:0[1-3]|[25]9)|2[289]|30|[45]4|75|913$/',
+ 'emergency' => '/^91[17]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{4})?$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'mobile' => '/^\\d{7}$/',
+ 'tollfree' => '/^\\d{11}$/',
+ 'shortcode' => '/^\\d{2,3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/FK.php b/module/Zend/I18n/src/Validator/PhoneNumber/FK.php
new file mode 100644
index 00000000..06bf9ec7
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/FK.php
@@ -0,0 +1,26 @@
+ '500',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-7]\\d{4}$/',
+ 'fixed' => '/^[2-47]\\d{4}$/',
+ 'mobile' => '/^[56]\\d{4}$/',
+ 'shortcode' => '/^1\\d{2}$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/FM.php b/module/Zend/I18n/src/Validator/PhoneNumber/FM.php
new file mode 100644
index 00000000..9aed0243
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/FM.php
@@ -0,0 +1,24 @@
+ '691',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[39]\\d{6}$/',
+ 'fixed' => '/^3[2357]0[1-9]\\d{3}|9[2-6]\\d{5}$/',
+ 'mobile' => '/^3[2357]0[1-9]\\d{3}|9[2-7]\\d{5}$/',
+ 'emergency' => '/^911|320221$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}(?:\\d{3})?$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/FO.php b/module/Zend/I18n/src/Validator/PhoneNumber/FO.php
new file mode 100644
index 00000000..5c8d3eb2
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/FO.php
@@ -0,0 +1,29 @@
+ '298',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{5}$/',
+ 'fixed' => '/^(?:20|[3-4]\\d|8[19])\\d{4}$/',
+ 'mobile' => '/^(?:2[1-9]|5\\d|7[1-79])\\d{4}$/',
+ 'tollfree' => '/^80[257-9]\\d{3}$/',
+ 'premium' => '/^90(?:[1345][15-7]|2[125-7]|99)\\d{2}$/',
+ 'voip' => '/^(?:6[0-36]|88)\\d{4}$/',
+ 'shortcode' => '/^1(?:1[48]|4[124]\\d|71\\d|8[7-9]\\d)$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6}$/',
+ 'shortcode' => '/^\\d{3,4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/FR.php b/module/Zend/I18n/src/Validator/PhoneNumber/FR.php
new file mode 100644
index 00000000..4c171d01
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/FR.php
@@ -0,0 +1,34 @@
+ '33',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[124-9]\\d{8}|3\\d{3}(?:\\d{5})?$/',
+ 'fixed' => '/^[1-5]\\d{8}$/',
+ 'mobile' => '/^(?:[6-7]\\d{8}|7[5-9]\\d{7})$/',
+ 'tollfree' => '/^80\\d{7}$/',
+ 'premium' => '/^(?:3\\d{3}|89[1-37-9])\\d{6}$/',
+ 'shared' => '/^8(?:1[019]|2[0156]|84|90)\\d{6}$/',
+ 'voip' => '/^9\\d{8}$/',
+ 'emergency' => '/^1(?:[578]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4}(?:\\d{5})?$/',
+ 'fixed' => '/^\\d{9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{4}(?:\\d{5})?$/',
+ 'shared' => '/^\\d{9}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GA.php b/module/Zend/I18n/src/Validator/PhoneNumber/GA.php
new file mode 100644
index 00000000..5d5a3474
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GA.php
@@ -0,0 +1,24 @@
+ '241',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[01]\\d{6,7}$/',
+ 'fixed' => '/^1\\d{6}$/',
+ 'mobile' => '/^0[2-7]\\d{6}$/',
+ 'emergency' => '/^1730|18|13\\d{2}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'emergency' => '/^\\d{2,4}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GB.php b/module/Zend/I18n/src/Validator/PhoneNumber/GB.php
new file mode 100644
index 00000000..e0381959
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GB.php
@@ -0,0 +1,41 @@
+ '44',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^\\d{7,10}$/',
+ 'fixed' => '/^2(?:0[01378]|3[0189]|4[017]|8[0-46-9]|9[012])\\d{7}|1(?:(?:1(?:3[0-48]|[46][0-4]|5[012789]|7[0-49]|8[01349])|21[0-7]|31[0-8]|[459]1\\d|61[0-46-9]))\\d{6}|1(?:2(?:0[024-9]|2[3-9]|3[3-79]|4[1-689]|[58][02-9]|6[0-4789]|7[013-9]|9\\d)|3(?:0\\d|[25][02-9]|3[02-579]|[468][0-46-9]|7[1235679]|9[24578])|4(?:0[03-9]|[28][02-5789]|[37]\\d|4[02-69]|5[0-8]|[69][0-79])|5(?:0[1235-9]|2[024-9]|3[015689]|4[02-9]|5[03-9]|6\\d|7[0-35-9]|8[0-468]|9[0-5789])|6(?:0[034689]|2[0-35689]|[38][013-9]|4[1-467]|5[0-69]|6[13-9]|7[0-8]|9[0124578])|7(?:0[0246-9]|2\\d|3[023678]|4[03-9]|5[0-46-9]|6[013-9]|7[0-35-9]|8[024-9]|9[02-9])|8(?:0[35-9]|2[1-5789]|3[02-578]|4[0-578]|5[124-9]|6[2-69]|7\\d|8[02-9]|9[02569])|9(?:0[02-589]|2[02-689]|3[1-5789]|4[2-9]|5[0-579]|6[234789]|7[0124578]|8\\d|9[2-57]))\\d{6}|1(?:2(?:0(?:46[1-4]|87[2-9])|545[1-79]|76(?:2\\d|3[1-8]|6[1-6])|9(?:7(?:2[0-4]|3[2-5])|8(?:2[2-8]|7[0-4789]|8[345])))|3(?:638[2-5]|647[23]|8(?:47[04-9]|64[015789]))|4(?:044[1-7]|20(?:2[23]|8\\d)|6(?:0(?:30|5[2-57]|6[1-8]|7[2-8])|140)|8(?:052|87[123]))|5(?:24(?:3[2-79]|6\\d)|276\\d|6(?:26[06-9]|686))|6(?:06(?:4\\d|7[4-79])|295[567]|35[34]\\d|47(?:24|61)|59(?:5[08]|6[67]|74)|955[0-4])|7(?:26(?:6[13-9]|7[0-7])|442\\d|50(?:2[0-3]|[3-68]2|76))|8(?:27[56]\\d|37(?:5[2-5]|8[239])|84(?:3[2-58]))|9(?:0(?:0(?:6[1-8]|85)|52\\d)|3583|4(?:66[1-8]|9(?:2[01]|81))|63(?:23|3[1-4])|9561))\\d{3}|176888[234678]\\d{2}|16977[23]\\d{3}$/',
+ 'mobile' => '/^7(?:[1-4]\\d\\d|5(?:0[0-8]|[13-9]\\d|2[0-35-9])|7(?:0[1-9]|[1-7]\\d|8[02-9]|9[0-689])|8(?:[014-9]\\d|[23][0-8])|9(?:[04-9]\\d|1[02-9]|2[0-35-9]|3[0-689]))\\d{6}$/',
+ 'pager' => '/^76(?:0[012]|2[356]|4[0134]|5[49]|6[0-369]|77|81|9[39])\\d{6}$/',
+ 'tollfree' => '/^80(?:0(?:1111|\\d{6,7})|8\\d{7})|500\\d{6}$/',
+ 'premium' => '/^(?:87[123]|9(?:[01]\\d|8[2349]))\\d{7}$/',
+ 'shared' => '/^8(?:4(?:5464\\d|[2-5]\\d{7})|70\\d{7})$/',
+ 'personal' => '/^70\\d{8}$/',
+ 'voip' => '/^56\\d{8}$/',
+ 'uan' => '/^(?:3[0347]|55)\\d{8}$/',
+ 'shortcode' => '/^1(?:0[01]|1(?:1|[68]\\d{3})|2[123]|33|4(?:1|7\\d)|5\\d|70\\d|800\\d|9[15])|2(?:02|2(?:02|11|2)|3(?:02|45)|425)|3[13]3|4(?:0[02]|35[01]|44[45]|5\\d)|650|789|901$/',
+ 'emergency' => '/^112|999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4,10}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'pager' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{7}(?:\\d{2,3})?$/',
+ 'premium' => '/^\\d{10}$/',
+ 'shared' => '/^\\d{7}(?:\\d{3})?$/',
+ 'personal' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'uan' => '/^\\d{10}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GD.php b/module/Zend/I18n/src/Validator/PhoneNumber/GD.php
new file mode 100644
index 00000000..ad6dde80
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GD.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[4589]\\d{9}$/',
+ 'fixed' => '/^473(?:2(?:3[0-2]|69)|3(?:2[89]|86)|4(?:[06]8|3[5-9]|4[0-49]|5[5-79]|68|73|90)|63[68]|7(?:58|84)|938)\\d{4}$/',
+ 'mobile' => '/^473(?:4(?:0[3-79]|1[04-9]|20|58)|53[3-8])\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GE.php b/module/Zend/I18n/src/Validator/PhoneNumber/GE.php
new file mode 100644
index 00000000..6f9008c1
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GE.php
@@ -0,0 +1,28 @@
+ '995',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[3458]\\d{8}$/',
+ 'fixed' => '/^(?:3(?:[256]\\d|4[124-9]|7[0-4])|4(?:1\\d|2[2-7]|3[1-79]|4[2-8]|7[239]|9[1-7]))\\d{6}$/',
+ 'mobile' => '/^5(?:14|5[01578]|68|7[0147-9]|9[0-35-9])\\d{6}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'emergency' => '/^0(?:11|22|33)|1(?:1[123]|22)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,9}$/',
+ 'fixed' => '/^\\d{6,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GF.php b/module/Zend/I18n/src/Validator/PhoneNumber/GF.php
new file mode 100644
index 00000000..194dfc86
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GF.php
@@ -0,0 +1,24 @@
+ '594',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[56]\\d{8}$/',
+ 'fixed' => '/^594(?:10|2[012457-9]|3[0-57-9]|4[3-9]|5[7-9]|6[0-3]|9[014])\\d{4}$/',
+ 'mobile' => '/^694(?:[04][0-7]|1[0-5]|2[0-46-9]|38|9\\d)\\d{4}$/',
+ 'emergency' => '/^1[578]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GG.php b/module/Zend/I18n/src/Validator/PhoneNumber/GG.php
new file mode 100644
index 00000000..cf5b6a61
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GG.php
@@ -0,0 +1,41 @@
+ '44',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[135789]\\d{6,9}$/',
+ 'fixed' => '/^1481\\d{6}$/',
+ 'mobile' => '/^7(?:781|839|911)\\d{6}$/',
+ 'pager' => '/^76(?:0[012]|2[356]|4[0134]|5[49]|6[0-369]|77|81|9[39])\\d{6}$/',
+ 'tollfree' => '/^80(?:0(?:1111|\\d{6,7})|8\\d{7})|500\\d{6}$/',
+ 'premium' => '/^(?:87[123]|9(?:[01]\\d|8[0-3]))\\d{7}$/',
+ 'shared' => '/^8(?:4(?:5464\\d|[2-5]\\d{7})|70\\d{7})$/',
+ 'personal' => '/^70\\d{8}$/',
+ 'voip' => '/^56\\d{8}$/',
+ 'uan' => '/^(?:3[0347]|55)\\d{8}$/',
+ 'shortcode' => '/^1(?:0[01]|1(?:1|[68]\\d{3})|23|4(?:1|7\\d)|55|800\\d|95)$/',
+ 'emergency' => '/^112|999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'pager' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{7}(?:\\d{2,3})?$/',
+ 'premium' => '/^\\d{10}$/',
+ 'shared' => '/^\\d{7}(?:\\d{3})?$/',
+ 'personal' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'uan' => '/^\\d{10}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GH.php b/module/Zend/I18n/src/Validator/PhoneNumber/GH.php
new file mode 100644
index 00000000..011b7a56
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GH.php
@@ -0,0 +1,28 @@
+ '233',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[235]\\d{8}|8\\d{7}$/',
+ 'fixed' => '/^3(?:0[237]\\d|[167](?:2[0-6]|7\\d)|2(?:2[0-5]|7\\d)|3(?:2[0-3]|7\\d)|4(?:2[013-9]|3[01]|7\\d)|5(?:2[0-7]|7\\d)|8(?:2[0-2]|7\\d)|9(?:20|7\\d))\\d{5}$/',
+ 'mobile' => '/^(?:2[034678]|5[047])\\d{7}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'emergency' => '/^19[123]|999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'fixed' => '/^\\d{7,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GI.php b/module/Zend/I18n/src/Validator/PhoneNumber/GI.php
new file mode 100644
index 00000000..5be5b166
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GI.php
@@ -0,0 +1,29 @@
+ '350',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2568]\\d{7}$/',
+ 'fixed' => '/^2(?:00\\d|16[0-7]|22[2457])\\d{4}$/',
+ 'mobile' => '/^(?:5[4-8]|60)\\d{6}$/',
+ 'tollfree' => '/^80\\d{6}$/',
+ 'premium' => '/^8[1-689]\\d{6}$/',
+ 'shared' => '/^87\\d{6}$/',
+ 'shortcode' => '/^1(?:00|1(?:6(?:00[06]|11[17])|8\\d{2})|23|4(?:1|7[014])|5[015]|9[34])|8(?:00|4[0-2]|8\\d)$/',
+ 'emergency' => '/^1(?:12|9[09])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GL.php b/module/Zend/I18n/src/Validator/PhoneNumber/GL.php
new file mode 100644
index 00000000..26f8bca0
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GL.php
@@ -0,0 +1,26 @@
+ '299',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-689]\\d{5}$/',
+ 'fixed' => '/^(?:19|3[1-6]|6[14689]|8[14-79]|9\\d)\\d{4}$/',
+ 'mobile' => '/^[245][2-9]\\d{4}$/',
+ 'tollfree' => '/^80\\d{4}$/',
+ 'voip' => '/^3[89]\\d{4}$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GM.php b/module/Zend/I18n/src/Validator/PhoneNumber/GM.php
new file mode 100644
index 00000000..87b00da7
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GM.php
@@ -0,0 +1,24 @@
+ '220',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{6}$/',
+ 'fixed' => '/^(?:4(?:[23]\\d{2}|4(?:1[024679]|[6-9]\\d))|5(?:54[0-7]|6(?:[67]\\d)|7(?:1[04]|2[035]|3[58]|48))|8\\d{3})\\d{3}$/',
+ 'mobile' => '/^(?:2[0-2]|[3679]\\d)\\d{5}$/',
+ 'emergency' => '/^1?1[678]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GN.php b/module/Zend/I18n/src/Validator/PhoneNumber/GN.php
new file mode 100644
index 00000000..0274a0bf
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GN.php
@@ -0,0 +1,25 @@
+ '224',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[23567]\\d{7,8}$/',
+ 'fixed' => '/^30(?:24|3[12]|4[1-35-7]|5[13]|6[189]|[78]1|9[1478])\\d{4}$/',
+ 'mobile' => '/^(?:24|55)\\d{6}|6(?:0(?:2[0-35-9]|3[3467]|5[2457-9])|1[0-5]\\d|2\\d{2,3}|[4-9]\\d{2}|3(?:[14]0|35))\\d{4}$/',
+ 'voip' => '/^78\\d{6}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,9}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'voip' => '/^\\d{8}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GP.php b/module/Zend/I18n/src/Validator/PhoneNumber/GP.php
new file mode 100644
index 00000000..df11476b
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GP.php
@@ -0,0 +1,24 @@
+ '590',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[56]\\d{8}$/',
+ 'fixed' => '/^590(?:1[12]|2[0-68]|3[28]|4[126-8]|5[067]|6[018]|[89]\\d)\\d{4}$/',
+ 'mobile' => '/^690(?:00|1[1-9]|2[013-5]|[3-5]\\d|6[0-57-9]|7[1-6]|8[0-6]|9[09])\\d{4}$/',
+ 'emergency' => '/^1[578]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GQ.php b/module/Zend/I18n/src/Validator/PhoneNumber/GQ.php
new file mode 100644
index 00000000..2d275ee1
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GQ.php
@@ -0,0 +1,24 @@
+ '240',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[23589]\\d{8}$/',
+ 'fixed' => '/^3(?:3(?:3\\d[7-9]|[0-24-9]\\d[46])|5\\d{2}[7-9])\\d{4}$/',
+ 'mobile' => '/^(?:222|551)\\d{6}$/',
+ 'tollfree' => '/^80\\d[1-9]\\d{5}$/',
+ 'premium' => '/^90\\d[1-9]\\d{5}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GR.php b/module/Zend/I18n/src/Validator/PhoneNumber/GR.php
new file mode 100644
index 00000000..4f0d2542
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GR.php
@@ -0,0 +1,28 @@
+ '30',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[26-9]\\d{9}$/',
+ 'fixed' => '/^2(?:1\\d{2}|2(?:3[1-8]|4[1-7]|5[1-4]|6[1-8]|7[1-5]|[289][1-9])|3(?:1\\d|2[1-57]|3[1-4]|[45][1-3]|7[1-7]|8[1-6]|9[1-79])|4(?:1\\d|2[1-8]|3[1-4]|4[13-5]|6[1-578]|9[1-5])|5(?:1\\d|[239][1-4]|4[124]|5[1-6])|6(?:1\\d|3[124]|4[1-7]|5[13-9]|[269][1-6]|7[14]|8[1-5])|7(?:1\\d|2[1-5]|3[1-6]|4[1-7]|5[1-57]|6[134]|9[15-7])|8(?:1\\d|2[1-5]|[34][1-4]|9[1-7]))\\d{6}$/',
+ 'mobile' => '/^69\\d{8}$/',
+ 'tollfree' => '/^800\\d{7}$/',
+ 'premium' => '/^90[19]\\d{7}$/',
+ 'shared' => '/^8(?:0[16]|12|25)\\d{7}$/',
+ 'personal' => '/^70\\d{8}$/',
+ 'emergency' => '/^1(?:00|12|66|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GT.php b/module/Zend/I18n/src/Validator/PhoneNumber/GT.php
new file mode 100644
index 00000000..2c7cb15c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GT.php
@@ -0,0 +1,32 @@
+ '502',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-7]\\d{7}|1[89]\\d{9}$/',
+ 'fixed' => '/^[267][2-9]\\d{6}$/',
+ 'mobile' => '/^[345]\\d{7}$/',
+ 'tollfree' => '/^18[01]\\d{8}$/',
+ 'premium' => '/^19\\d{9}$/',
+ 'shortcode' => '/^1(?:2[124-9]|[57]\\d{2})$/',
+ 'emergency' => '/^1(?:10|2[03])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}(?:\\d{3})?$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{11}$/',
+ 'premium' => '/^\\d{11}$/',
+ 'shortcode' => '/^\\d{3,4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GU.php b/module/Zend/I18n/src/Validator/PhoneNumber/GU.php
new file mode 100644
index 00000000..0216f2f0
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GU.php
@@ -0,0 +1,30 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5689]\\d{9}$/',
+ 'fixed' => '/^671(?:3(?:00|3[39]|4[349]|55|6[26])|4(?:56|7[1-9]|8[23678])|5(?:55|6[2-5]|88)|6(?:3[2-578]|4[24-9]|5[34]|78|8[5-9])|7(?:[079]7|2[0167]|3[45]|8[789])|8(?:[2-5789]8|6[48])|9(?:2[29]|6[79]|7[179]|8[789]|9[78]))\\d{4}$/',
+ 'mobile' => '/^671(?:3(?:00|3[39]|4[349]|55|6[26])|4(?:56|7[1-9]|8[23678])|5(?:55|6[2-5]|88)|6(?:3[2-578]|4[24-9]|5[34]|78|8[5-9])|7(?:[079]7|2[0167]|3[45]|8[789])|8(?:[2-5789]8|6[48])|9(?:2[29]|6[79]|7[179]|8[789]|9[78]))\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GW.php b/module/Zend/I18n/src/Validator/PhoneNumber/GW.php
new file mode 100644
index 00000000..864d9059
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GW.php
@@ -0,0 +1,24 @@
+ '245',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[3567]\\d{6}$/',
+ 'fixed' => '/^3(?:2[0125]|3[1245]|4[12]|5[1-4]|70|9[1-467])\\d{4}$/',
+ 'mobile' => '/^[5-7]\\d{6}$/',
+ 'emergency' => '/^11[378]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/GY.php b/module/Zend/I18n/src/Validator/PhoneNumber/GY.php
new file mode 100644
index 00000000..48f11933
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/GY.php
@@ -0,0 +1,28 @@
+ '592',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-4679]\\d{6}$/',
+ 'fixed' => '/^(?:2(?:1[6-9]|2[0-35-9]|3[1-4]|5[3-9]|6\\d|7[0-24-79])|3(?:2[25-9]|3\\d)|4(?:4[0-24]|5[56])|77[1-57])\\d{4}$/',
+ 'mobile' => '/^6\\d{6}$/',
+ 'tollfree' => '/^(?:289|862)\\d{4}$/',
+ 'premium' => '/^9008\\d{3}$/',
+ 'shortcode' => '/^0(?:02|171|444|7[67]7|801|9(?:0[78]|[2-47]))$/',
+ 'emergency' => '/^91[123]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'shortcode' => '/^\\d{3,4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/HK.php b/module/Zend/I18n/src/Validator/PhoneNumber/HK.php
new file mode 100644
index 00000000..c6a77c70
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/HK.php
@@ -0,0 +1,34 @@
+ '852',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[235-7]\\d{7}|8\\d{7,8}|9\\d{4,10}$/',
+ 'fixed' => '/^(?:[23]\\d|5[78])\\d{6}$/',
+ 'mobile' => '/^(?:5[1-69]\\d|6\\d{2}|9(?:0[1-9]|[1-8]\\d))\\d{5}$/',
+ 'pager' => '/^7\\d{7}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'premium' => '/^900(?:[0-24-9]\\d{7}|3\\d{1,4})$/',
+ 'personal' => '/^8[1-3]\\d{6}$/',
+ 'emergency' => '/^112|99[29]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,11}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'pager' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{5,11}$/',
+ 'personal' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/HN.php b/module/Zend/I18n/src/Validator/PhoneNumber/HN.php
new file mode 100644
index 00000000..9f0fa79f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/HN.php
@@ -0,0 +1,24 @@
+ '504',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[237-9]\\d{7}$/',
+ 'fixed' => '/^2(?:2(?:0[019]|1[1-36]|[23]\\d|4[056]|5[57]|8[0146-9]|9[012])|4(?:2|3-59]|3[13-689]|4[0-68]|5[1-35])|5(?:4[3-5]|5\\d|6[56]|74)|6(?:4[0-378]|[56]\\d|[78][0-8]|9[01])|7(?:6[46-9]|7[02-9]|8[34])|8(?:79|8[0-35789]|9[1-57-9]))\\d{4}$/',
+ 'mobile' => '/^[37-9]\\d{7}$/',
+ 'emergency' => '/^199$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/HR.php b/module/Zend/I18n/src/Validator/PhoneNumber/HR.php
new file mode 100644
index 00000000..6836eca1
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/HR.php
@@ -0,0 +1,34 @@
+ '385',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-7]\\d{5,8}|[89]\\d{6,11}$/',
+ 'fixed' => '/^1\\d{7}|(?:2[0-3]|3[1-5]|4[02-47-9]|5[1-3])\\d{6}$/',
+ 'uan' => '/^62\\d{6,7}$/',
+ 'mobile' => '/^9[1257-9]\\d{6,10}$/',
+ 'tollfree' => '/^80[01]\\d{4,7}$/',
+ 'premium' => '/^6(?:[09]\\d{7}|[145]\\d{4,7})$/',
+ 'personal' => '/^7[45]\\d{4,7}$/',
+ 'emergency' => '/^1(?:12|92)|9[34]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,12}$/',
+ 'fixed' => '/^\\d{6,8}$/',
+ 'uan' => '/^\\d{8,9}$/',
+ 'mobile' => '/^\\d{8,12}$/',
+ 'tollfree' => '/^\\d{7,10}$/',
+ 'premium' => '/^\\d{6,9}$/',
+ 'personal' => '/^\\d{6,9}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/HT.php b/module/Zend/I18n/src/Validator/PhoneNumber/HT.php
new file mode 100644
index 00000000..ce64ac07
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/HT.php
@@ -0,0 +1,30 @@
+ '509',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-489]\\d{7}$/',
+ 'fixed' => '/^2(?:[24]\\d|5[1-5]|94)\\d{5}$/',
+ 'mobile' => '/^(?:3[1-9]|4\\d)\\d{6}$/',
+ 'tollfree' => '/^8\\d{7}$/',
+ 'voip' => '/^98[89]\\d{5}$/',
+ 'shortcode' => '/^1\\d{2}$/',
+ 'emergency' => '/^11[48]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/HU.php b/module/Zend/I18n/src/Validator/PhoneNumber/HU.php
new file mode 100644
index 00000000..4881a3d2
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/HU.php
@@ -0,0 +1,31 @@
+ '36',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{7,8}$/',
+ 'fixed' => '/^(?:1\\d|2(?:1\\d|[2-9])|3[2-7]|4[24-9]|5[2-79]|6[23689]|7(?:1\\d|[2-9])|8[2-57-9]|9[2-69])\\d{6}$/',
+ 'mobile' => '/^(?:[27]0|3[01])\\d{7}$/',
+ 'tollfree' => '/^80\\d{6}$/',
+ 'premium' => '/^9[01]\\d{6}$/',
+ 'shared' => '/^40\\d{6}$/',
+ 'emergency' => '/^1(?:0[457]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'shared' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/ID.php b/module/Zend/I18n/src/Validator/PhoneNumber/ID.php
new file mode 100644
index 00000000..48d61698
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/ID.php
@@ -0,0 +1,30 @@
+ '62',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{6,10}$/',
+ 'fixed' => '/^2(?:1(?:[0-8]\\d{6,7}|9\\d{6})|[24]\\d{7,8})|(?:2(?:[35][1-4]|6[0-8]|7[1-6]|8\\d|9[1-8])|3(?:1|2[1-578]|3[1-68]|4[1-3]|5[1-8]|6[1-3568]|7[0-46]|8\\d)|4(?:0[1-589]|1[01347-9]|2[0-36-8]|3[0-24-68]|5[1-378]|6[1-5]|7[134]|8[1245])|5(?:1[1-35-9]|2[25-8]|3[1246-9]|4[1-3589]|5[1-46]|6[1-8])|6(?:19?|[25]\\d|3[1-469]|4[1-6])|7(?:1[1-46-9]|2[14-9]|[36]\\d|4[1-8]|5[1-9]|7[0-36-9])|9(?:0[12]|1[013-8]|2[0-479]|5[125-8]|6[23679]|7[159]|8[01346]))\\d{5,8}$/',
+ 'mobile' => '/^(?:2(?:1(?:3[145]|4[01]|5[1-469]|60|8[0359]|9\\d)|2(?:88|9[1256])|3[1-4]9|4(?:36|91)|5(?:1[349]|[2-4]9)|6[0-7]9|7(?:[1-36]9|4[39])|8[1-5]9|9[1-48]9)|3(?:19[1-3]|2[12]9|3[13]9|4(?:1[69]|39)|5[14]9|6(?:1[69]|2[89])|709)|4[13]19|5(?:1(?:19|8[39])|4[129]9|6[12]9)|6(?:19[12]|2(?:[23]9|77))|7(?:1[13]9|2[15]9|419|5(?:1[89]|29)|6[15]9|7[178]9))\\d{5,6}|8[1-35-9]\\d{7,9}$/',
+ 'tollfree' => '/^177\\d{6,8}|800\\d{5,7}$/',
+ 'premium' => '/^809\\d{7}$/',
+ 'emergency' => '/^11[02389]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,11}$/',
+ 'fixed' => '/^\\d{5,10}$/',
+ 'mobile' => '/^\\d{9,11}$/',
+ 'tollfree' => '/^\\d{8,11}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/IE.php b/module/Zend/I18n/src/Validator/PhoneNumber/IE.php
new file mode 100644
index 00000000..13dcd1a7
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/IE.php
@@ -0,0 +1,40 @@
+ '353',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[124-9]\\d{6,9}$/',
+ 'fixed' => '/^1\\d{7,8}|2(?:1\\d{6,7}|3\\d{7}|[24-9]\\d{5})|4(?:0[24]\\d{5}|[1-469]\\d{7}|5\\d{6}|7\\d{5}|8[0-46-9]\\d{7})|5(?:0[45]\\d{5}|1\\d{6}|[23679]\\d{7}|8\\d{5})|6(?:1\\d{6}|[237-9]\\d{5}|[4-6]\\d{7})|7[14]\\d{7}|9(?:1\\d{6}|[04]\\d{7}|[35-9]\\d{5})$/',
+ 'mobile' => '/^8(?:22\\d{6}|[35-9]\\d{7})$/',
+ 'tollfree' => '/^1800\\d{6}$/',
+ 'premium' => '/^15(?:1[2-8]|[2-8]0|9[089])\\d{6}$/',
+ 'shared' => '/^18[59]0\\d{6}$/',
+ 'personal' => '/^700\\d{6}$/',
+ 'voip' => '/^76\\d{7}$/',
+ 'uan' => '/^818\\d{6}$/',
+ 'voicemail' => '/^8[35-9]\\d{8}$/',
+ 'emergency' => '/^112|999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,10}$/',
+ 'fixed' => '/^\\d{5,10}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'shared' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{9}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'uan' => '/^\\d{9}$/',
+ 'voicemail' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/IL.php b/module/Zend/I18n/src/Validator/PhoneNumber/IL.php
new file mode 100644
index 00000000..9eaa3ce6
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/IL.php
@@ -0,0 +1,40 @@
+ '972',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[17]\\d{6,9}|[2-589]\\d{3}(?:\\d{3,6})?|6\\d{3}$/',
+ 'fixed' => '/^[2-489]\\d{7}$/',
+ 'mobile' => '/^5(?:[02347-9]\\d{2}|5(?:2[23]|3[34]|4[45]|5[5689]|6[67]|7[78]|8[89])|6[2-9]\\d)\\d{5}$/',
+ 'tollfree' => '/^1(?:80[019]\\d{3}|255)\\d{3}$/',
+ 'premium' => '/^1(?:212|(?:9(?:0[01]|19)|200)\\d{2})\\d{4}$/',
+ 'shared' => '/^1700\\d{6}$/',
+ 'voip' => '/^7(?:2[23]\\d|3[237]\\d|47\\d|6(?:5\\d|8[08])|7\\d{2}|8(?:33|55|77|81))\\d{5}$/',
+ 'uan' => '/^[2-689]\\d{3}$/',
+ 'voicemail' => '/^1599\\d{6}$/',
+ 'shortcode' => '/^1\\d{3}$/',
+ 'emergency' => '/^1(?:0[012]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4,10}$/',
+ 'fixed' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{7,10}$/',
+ 'premium' => '/^\\d{8,10}$/',
+ 'shared' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'uan' => '/^\\d{4}$/',
+ 'voicemail' => '/^\\d{10}$/',
+ 'shortcode' => '/^\\d{4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/IM.php b/module/Zend/I18n/src/Validator/PhoneNumber/IM.php
new file mode 100644
index 00000000..55169e4c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/IM.php
@@ -0,0 +1,39 @@
+ '44',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[135789]\\d{6,9}$/',
+ 'fixed' => '/^1624\\d{6}$/',
+ 'mobile' => '/^7[569]24\\d{6}$/',
+ 'tollfree' => '/^808162\\d{4}$/',
+ 'premium' => '/^(?:872299|90[0167]624)\\d{4}$/',
+ 'shared' => '/^8(?:4(?:40[49]06|5624\\d)|70624\\d)\\d{3}$/',
+ 'personal' => '/^70\\d{8}$/',
+ 'voip' => '/^56\\d{8}$/',
+ 'uan' => '/^3(?:08162\\d|3\\d{5}|4(?:40[49]06|5624\\d)|7(?:0624\\d|2299\\d))\\d{3}|55\\d{8}$/',
+ 'shortcode' => '/^1\\d{2}(?:\\d{3})?$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'shared' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'uan' => '/^\\d{10}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/IN.php b/module/Zend/I18n/src/Validator/PhoneNumber/IN.php
new file mode 100644
index 00000000..cdb70a2a
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/IN.php
@@ -0,0 +1,32 @@
+ '91',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^1\\d{7,12}|[2-9]\\d{9,10}$/',
+ 'fixed' => '/^(?:11|2[02]|33|4[04]|79)[2-7]\\d{7}|80[2-467]\\d{7}|(?:1(?:2[0-249]|3[0-25]|4[145]|[59][14]|6[014]|7[1257]|8[01346])|2(?:1[257]|3[013]|4[01]|5[0137]|6[0158]|78|8[1568]|9[14])|3(?:26|4[1-3]|5[34]|6[01489]|7[02-46]|8[159])|4(?:1[36]|2[1-47]|3[15]|5[12]|6[126-9]|7[0-24-9]|8[013-57]|9[014-7])|5(?:[136][25]|22|4[28]|5[12]|[78]1|9[15])|6(?:12|[2345]1|57|6[13]|7[14]|80)|7(?:12|2[14]|3[134]|4[47]|5[15]|[67]1|88)|8(?:16|2[014]|3[126]|6[136]|7[078]|8[34]|91))[2-7]\\d{6}|(?:(?:1(?:2[35-8]|3[346-9]|4[236-9]|[59][0235-9]|6[235-9]|7[34689]|8[257-9])|2(?:1[134689]|3[24-8]|4[2-8]|5[25689]|6[2-4679]|7[13-79]|8[2-479]|9[235-9])|3(?:01|1[79]|2[1-5]|4[25-8]|5[125689]|6[235-7]|7[157-9]|8[2-467])|4(?:1[14578]|2[5689]|3[2-467]|5[4-7]|6[35]|73|8[2689]|9[2389])|5(?:[16][146-9]|2[14-8]|3[1346]|4[14-69]|5[46]|7[2-4]|8[2-8]|9[246])|6(?:1[1358]|2[2457]|3[2-4]|4[235-7]|[57][2-689]|6[24-58]|8[1-6])|8(?:1[1357-9]|2[235-8]|3[03-57-9]|4[0-24-9]|5\\d|6[2457-9]|7[1-6]|8[1256]|9[2-4]))\\d|7(?:(?:1[013-9]|2[0235-9]|3[2679]|4[1-35689]|5[2-46-9]|[67][02-9]|9\\d)\\d|8(?:2[0-6]|[013-8]\\d)))[2-7]\\d{5}$/',
+ 'mobile' => '/^(?:7(?:2(?:0[04-9]|5[09]|7[5-8]|9[389])|3(?:0[1-9]|[58]\\d|7[3679]|9[689])|4(?:0[1-9]|1[15-9]|[29][89]|39|8[389])|5(?:0\\d|[47]9|[25]0|6[6-9]|[89][7-9])|6(?:0[027]|12|20|3[19]|5[45]|6[5-9]|7[679]|9[6-9])|7(?:0[27-9]|3[5-9]|42|60|7[7-9]|8[1-9]|9[05-9])|8(?:[03][07-9]|14|2[7-9]|[4-7]\\d|9[013-9]))|8(?:0(?:[01589]\\d|6[67])|1(?:[02-589]\\d|1[0135-9]|7[0-79])|2(?:[236-9]\\d|5[1-9])|3(?:[037-9]\\d|4[1-9]|5[0-37-9])|[45]\\d{2}|6[02457-9]\\d|7[1-69]\\d|8(?:[0-26-9]\\d|44|5[2-9])|9(?:[035-9]\\d|2[2-9]|4[0-8]))|9\\d{3})\\d{6}$/',
+ 'tollfree' => '/^1(?:600\\d{6}|80(?:0\\d{4,8}|3\\d{9}))$/',
+ 'premium' => '/^186[12]\\d{9}$/',
+ 'uan' => '/^1860\\d{7}$/',
+ 'emergency' => '/^1(?:0[0128]|12|298)|2611$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,13}$/',
+ 'fixed' => '/^\\d{6,10}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{8,13}$/',
+ 'premium' => '/^\\d{13}$/',
+ 'uan' => '/^\\d{11}$/',
+ 'emergency' => '/^\\d{3,4}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/IO.php b/module/Zend/I18n/src/Validator/PhoneNumber/IO.php
new file mode 100644
index 00000000..c990838c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/IO.php
@@ -0,0 +1,22 @@
+ '246',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^3\\d{6}$/',
+ 'fixed' => '/^37\\d{5}$/',
+ 'mobile' => '/^38\\d{5}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/IQ.php b/module/Zend/I18n/src/Validator/PhoneNumber/IQ.php
new file mode 100644
index 00000000..f4bb11f2
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/IQ.php
@@ -0,0 +1,24 @@
+ '964',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-7]\\d{7,9}$/',
+ 'fixed' => '/^1\\d{7}|(?:2[13-5]|3[02367]|4[023]|5[03]|6[026])\\d{6,7}$/',
+ 'mobile' => '/^7[3-9]\\d{8}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'fixed' => '/^\\d{6,9}$/',
+ 'mobile' => '/^\\d{10}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/IR.php b/module/Zend/I18n/src/Validator/PhoneNumber/IR.php
new file mode 100644
index 00000000..c014e81b
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/IR.php
@@ -0,0 +1,31 @@
+ '98',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-6]\\d{4,9}|9(?:[1-4]\\d{8}|9\\d{2,8})|[178]\\d{9}$/',
+ 'fixed' => '/^2(?:1[2-9]\\d{2,7}|51\\d{3,7})|(?:241|3(?:11|51)|441|5[14]1)\\d{4,7}|(?:3(?:34|41)|6(?:11|52))\\d{6,7}|(?:1(?:[134589][12]|[27][1-4])|2(?:2[189]|[389][12]|42|5[256]|6[1-59]|7[34])|3(?:12|2[1-4]|3[125]|4[24-9]|5[23]|[6-9][12])|4(?:[135-9][12]|2[1-467]|4[2-4])|5(?:12|2[89]|3[1-5]|4[2-8]|[5-7][12]|8[1245])|6(?:12|[347-9][12]|51|6[1-6])|7(?:[13589][12]|2[1289]|4[1-4]|6[1-6]|7[1-3])|8(?:[145][12]|3[124578]|6[1256]|7[1245]))\\d{7}$/',
+ 'mobile' => '/^9(?:1(?:[039]\\d|[16][1-35-9]|2[1-8]|4[013-9]|[57][1-9]|8[13-9])|2[01]\\d|3(?:[035-9]\\d|13|2[1-579]|47))\\d{6}$/',
+ 'pager' => '/^943[24678]\\d{6}$/',
+ 'voip' => '/^993[12]\\d{6}$/',
+ 'uan' => '/^9990\\d{0,6}$/',
+ 'emergency' => '/^1(?:1[025]|25)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4,10}$/',
+ 'fixed' => '/^\\d{5,10}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'pager' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/IS.php b/module/Zend/I18n/src/Validator/PhoneNumber/IS.php
new file mode 100644
index 00000000..699b62cc
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/IS.php
@@ -0,0 +1,32 @@
+ '354',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[4-9]\\d{6}|38\\d{7}$/',
+ 'fixed' => '/^(?:4(?:[14][0-245]|2[0-7]|[37][0-8]|5[0-3568]|6\\d|8[0-36-8])|5(?:05|[156]\\d|2[02578]|3[013-7]|4[03-7]|7[0-2578]|8[0-35-9]|9[013-689])|87[23])\\d{4}$/',
+ 'mobile' => '/^38[59]\\d{6}|(?:6(?:1[0-8]|3[0-27-9]|4[0-27]|5[0-29]|[67][0-69]|9\\d)|7(?:5[057]|7\\d|8[0-3])|8(?:2[0-5]|[469]\\d|5[1-9]))\\d{4}$/',
+ 'tollfree' => '/^800\\d{4}$/',
+ 'premium' => '/^90\\d{5}$/',
+ 'voip' => '/^49[0-24-79]\\d{4}$/',
+ 'voicemail' => '/^388\\d{6}|(?:6(?:2[0-8]|49|8\\d)|8(?:2[6-9]|[38]\\d|50|7[014-9])|95[48])\\d{4}$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'tollfree' => '/^\\d{7}$/',
+ 'premium' => '/^\\d{7}$/',
+ 'voip' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/IT.php b/module/Zend/I18n/src/Validator/PhoneNumber/IT.php
new file mode 100644
index 00000000..1d29919f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/IT.php
@@ -0,0 +1,38 @@
+ '39',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[01589]\\d{5,10}|3(?:[12457-9]\\d{8}|[36]\\d{7,9})$/',
+ 'fixed' => '/^0(?:[26]\\d{4,9}|(?:1(?:[0159]\\d|[27][1-5]|31|4[1-4]|6[1356]|8[2-57])|3(?:[0159]\\d|2[1-4]|3[12]|[48][1-6]|6[2-59]|7[1-7])|4(?:[0159]\\d|[23][1-9]|4[245]|6[1-5]|7[1-4]|81)|5(?:[0159]\\d|2[1-5]|3[2-6]|4[1-79]|6[4-6]|7[1-578]|8[3-8])|7(?:[0159]\\d|2[12]|3[1-7]|4[2346]|6[13569]|7[13-6]|8[1-59])|8(?:[0159]\\d|2[34578]|3[1-356]|[6-8][1-5])|9(?:[0159]\\d|[238][1-5]|4[12]|6[1-8]|7[1-6]))\\d{2,7})$/',
+ 'mobile' => '/^3(?:[12457-9]\\d{8}|6\\d{7,8}|3\\d{7,9})$/',
+ 'tollfree' => '/^80(?:0\\d{6}|3\\d{3})$/',
+ 'premium' => '/^0878\\d{5}|1(?:44|6[346])\\d{6}|89(?:2\\d{3}|4(?:[0-4]\\d{2}|[5-9]\\d{4})|5(?:[0-4]\\d{2}|[5-9]\\d{6})|9\\d{6})$/',
+ 'shared' => '/^84(?:[08]\\d{6}|[17]\\d{3})$/',
+ 'personal' => '/^1(?:78\\d|99)\\d{6}$/',
+ 'voip' => '/^55\\d{8}$/',
+ 'shortcode' => '/^1(?:1(?:[47]|6\\d{3})|2\\d{2}|4(?:82|9\\d{1,3})|5(?:00|1[58]|2[25]|3[03]|44)|86|9(?:2(?:[01]\\d{2}|[2-9]\\d)|4\\d|696))$/',
+ 'emergency' => '/^11[2358]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,11}$/',
+ 'fixed' => '/^\\d{6,11}$/',
+ 'mobile' => '/^\\d{9,11}$/',
+ 'tollfree' => '/^\\d{6,9}$/',
+ 'premium' => '/^\\d{6,10}$/',
+ 'shared' => '/^\\d{6,9}$/',
+ 'personal' => '/^\\d{9,10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/JE.php b/module/Zend/I18n/src/Validator/PhoneNumber/JE.php
new file mode 100644
index 00000000..fb507fed
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/JE.php
@@ -0,0 +1,41 @@
+ '44',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[135789]\\d{6,9}$/',
+ 'fixed' => '/^1534\\d{6}$/',
+ 'mobile' => '/^7(?:509|7(?:00|97)|829|937)\\d{6}$/',
+ 'pager' => '/^76(?:0[012]|2[356]|4[0134]|5[49]|6[0-369]|77|81|9[39])\\d{6}$/',
+ 'tollfree' => '/^80(?:07(?:35|81)|8901)\\d{4}$/',
+ 'premium' => '/^(?:871206|90(?:066[59]|1810|71(?:07|55)))\\d{4}$/',
+ 'shared' => '/^8(?:4(?:4(?:4(?:05|42|69)|703)|5(?:041|800))|70002)\\d{4}$/',
+ 'personal' => '/^701511\\d{4}$/',
+ 'voip' => '/^56\\d{8}$/',
+ 'uan' => '/^3(?:0(?:07(?:35|81)|8901)|3\\d{4}|4(?:4(?:4(?:05|42|69)|703)|5(?:041|800))|7(?:0002|1206))\\d{4}|55\\d{8}$/',
+ 'shortcode' => '/^1(?:00|18\\d{3}|23|4(?:[14]|28|7\\d)|5\\d|7(?:0[12]|[128]|35?)|808|9[135])|23[234]$/',
+ 'emergency' => '/^112|999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'pager' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'shared' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'uan' => '/^\\d{10}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/JM.php b/module/Zend/I18n/src/Validator/PhoneNumber/JM.php
new file mode 100644
index 00000000..48b2164a
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/JM.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[589]\\d{9}$/',
+ 'fixed' => '/^876(?:5(?:0[12]|1[0-468]|2[35]|63)|6(?:0[1-3579]|1[027-9]|[23]\\d|40|5[06]|6[2-489]|7[05]|8[04]|9[4-9])|7(?:0[2-689]|[1-6]\\d|8[056]|9[45])|9(?:0[1-8]|1[02378]|[2-8]\\d|9[2-468]))\\d{4}$/',
+ 'mobile' => '/^876(?:2[1789]\\d|[348]\\d{2}|5(?:08|27|6[0-24-9]|[3-578]\\d)|7(?:0[07]|7\\d|8[1-47-9]|9[0-36-9])|9(?:[01]9|9[0579]))\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^11[09]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/JO.php b/module/Zend/I18n/src/Validator/PhoneNumber/JO.php
new file mode 100644
index 00000000..82b7a528
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/JO.php
@@ -0,0 +1,40 @@
+ '962',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[235-9]\\d{7,8}$/',
+ 'fixed' => '/^(?:2(?:6(?:2[0-35-9]|3[0-57-8]|4[24-7]|5[0-24-8]|[6-9][02])|7(?:0[1-79]|10|2[014-7]|3[0-689]|4[019]|5[0-3578]))|32(?:0[1-69]|1[1-35-7]|2[024-7]|3\\d|[457][02]|60)|53(?:[013][02]|2[0-59]|49|5[0-35-9]|6[15]|7[45]|8[1-6]|9[0-36-9])|6(?:2[50]0|300|4(?:0[0125]|1[2-7]|2[0569]|[38][07-9]|4[025689]|6[0-589]|7\\d|9[0-2])|5(?:[01][056]|2[034]|3[0-57-9]|4[17-8]|5[0-69]|6[0-35-9]|7[1-379]|8[0-68]|9[02-39]))|87(?:[02]0|7[08]|9[09]))\\d{4}$/',
+ 'mobile' => '/^7(?:55|7[25-9]|8[5-9]|9[05-9])\\d{6}$/',
+ 'pager' => '/^74(?:66|77)\\d{5}$/',
+ 'tollfree' => '/^80\\d{6}$/',
+ 'premium' => '/^900\\d{5}$/',
+ 'shared' => '/^85\\d{6}$/',
+ 'personal' => '/^70\\d{7}$/',
+ 'uan' => '/^8(?:10|8\\d)\\d{5}$/',
+ 'shortcode' => '/^1(?:09|1[01]|9[024-79])$/',
+ 'emergency' => '/^1(?:12|91)|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'fixed' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'pager' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'shared' => '/^\\d{8}$/',
+ 'personal' => '/^\\d{9}$/',
+ 'uan' => '/^\\d{8}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/JP.php b/module/Zend/I18n/src/Validator/PhoneNumber/JP.php
new file mode 100644
index 00000000..5c5e036f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/JP.php
@@ -0,0 +1,38 @@
+ '81',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{8,9}|0(?:[36]\\d{7,14}|7\\d{5,7}|8\\d{7})$/',
+ 'fixed' => '/^(?:1(?:1[235-8]|2[3-6]|3[3-9]|4[2-6]|[58][2-8]|6[2-7]|7[2-9]|9[1-9])|2[2-9]\\d|[36][1-9]\\d|4(?:6[02-8]|[2-578]\\d|9[2-59])|5(?:6[1-9]|7[2-8]|[2-589]\\d)|7(?:3[4-9]|4[02-9]|[25-9]\\d)|8(?:3[2-9]|4[5-9]|5[1-9]|8[03-9]|[2679]\\d)|9(?:[679][1-9]|[2-58]\\d))\\d{6}$/',
+ 'mobile' => '/^(?:[79]0\\d|80[1-9])\\d{7}$/',
+ 'pager' => '/^20\\d{8}$/',
+ 'tollfree' => '/^120\\d{6}|800\\d{7}|0(?:37\\d{6,13}|66\\d{6,13}|777(?:[01]\\d{2}|5\\d{3}|8\\d{4})|882[1245]\\d{4})$/',
+ 'premium' => '/^990\\d{6}$/',
+ 'personal' => '/^60\\d{7}$/',
+ 'voip' => '/^50\\d{8}$/',
+ 'uan' => '/^570\\d{6}$/',
+ 'emergency' => '/^11[09]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,16}$/',
+ 'fixed' => '/^\\d{9}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'pager' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{7,16}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'personal' => '/^\\d{9}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'uan' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KE.php b/module/Zend/I18n/src/Validator/PhoneNumber/KE.php
new file mode 100644
index 00000000..e8479d0f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KE.php
@@ -0,0 +1,32 @@
+ '254',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^20\\d{6,7}|[4-9]\\d{6,9}$/',
+ 'fixed' => '/^20\\d{6,7}|4(?:[013]\\d{7}|[24-6]\\d{5,7})|5(?:[0-36-8]\\d{5,7}|[459]\\d{5})|6(?:[08]\\d{5}|[14-79]\\d{5,7}|2\\d{7})$/',
+ 'mobile' => '/^7(?:0[0-8]|[123]\\d|5[0-6]|7[0-5]|8[5-9])\\d{6}$/',
+ 'tollfree' => '/^800[24-8]\\d{5,6}$/',
+ 'premium' => '/^900[02-578]\\d{5}$/',
+ 'shortcode' => '/^1(?:0[09]|1(?:[06]|9[0-2579])|2[13]|3[01])$/',
+ 'emergency' => '/^112|999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,10}$/',
+ 'fixed' => '/^\\d{5,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9,10}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'shortcode' => '/^\\d{3,4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KG.php b/module/Zend/I18n/src/Validator/PhoneNumber/KG.php
new file mode 100644
index 00000000..ae50074d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KG.php
@@ -0,0 +1,27 @@
+ '996',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[35-8]\\d{8,9}$/',
+ 'fixed' => '/^(?:3(?:1(?:2\\d|3[1-9]|47|5[02]|6[1-8])|2(?:22|3[0-479]|6[0-7])|4(?:22|5[6-9]|6[0-4])|5(?:22|3[4-7]|59|6[0-5])|6(?:22|5[35-7]|6[0-3])|7(?:22|3[468]|4[1-9]|59|6\\d|7[5-7])|9(?:22|4[1-8]|6[0-8]))|6(?:09|12|2[2-4])\\d)\\d{5}$/',
+ 'mobile' => '/^5[124-7]\\d{7}|7(?:0[0-357-9]|7\\d)\\d{6}$/',
+ 'tollfree' => '/^800\\d{6,7}$/',
+ 'emergency' => '/^10[123]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,10}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9,10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KH.php b/module/Zend/I18n/src/Validator/PhoneNumber/KH.php
new file mode 100644
index 00000000..10cdcb5a
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KH.php
@@ -0,0 +1,30 @@
+ '855',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{7,9}$/',
+ 'fixed' => '/^(?:2[3-6]|3[2-6]|4[2-4]|[567][2-5])(?:[2-47-9]|5\\d|6\\d?)\\d{5}$/',
+ 'mobile' => '/^(?:(?:1\\d|6[06-9]|7(?:[07-9]|6\\d))[1-9]|8(?:0[89]|[134679]\\d|5[2-689]|8\\d{2})|9(?:[0-589][1-9]|[67][1-9]\\d?))\\d{5}$/',
+ 'tollfree' => '/^1800(?:1\\d|2[019])\\d{4}$/',
+ 'premium' => '/^1900(?:1\\d|2[09])\\d{4}$/',
+ 'emergency' => '/^11[789]|666$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'fixed' => '/^\\d{6,9}$/',
+ 'mobile' => '/^\\d{8,9}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KI.php b/module/Zend/I18n/src/Validator/PhoneNumber/KI.php
new file mode 100644
index 00000000..ee45a14b
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KI.php
@@ -0,0 +1,26 @@
+ '686',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-689]\\d{4}$/',
+ 'fixed' => '/^(?:[234]\\d|50|8[1-5])\\d{3}$/',
+ 'mobile' => '/^6\\d{4}|9(?:[0-8]\\d|9[015-8])\\d{2}$/',
+ 'shortcode' => '/^10(?:[0-8]|5[01259])$/',
+ 'emergency' => '/^99[2349]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5}$/',
+ 'shortcode' => '/^\\d{3,4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KM.php b/module/Zend/I18n/src/Validator/PhoneNumber/KM.php
new file mode 100644
index 00000000..c0213ede
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KM.php
@@ -0,0 +1,25 @@
+ '269',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[379]\\d{6}$/',
+ 'fixed' => '/^7(?:6[0-37-9]|7[0-57-9])\\d{4}$/',
+ 'mobile' => '/^3[234]\\d{5}$/',
+ 'premium' => '/^(?:39[01]|9[01]0)\\d{4}$/',
+ 'emergency' => '/^1[78]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KN.php b/module/Zend/I18n/src/Validator/PhoneNumber/KN.php
new file mode 100644
index 00000000..218579b1
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KN.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[589]\\d{9}$/',
+ 'fixed' => '/^869(?:2(?:29|36)|302|4(?:6[5-9]|70))\\d{4}$/',
+ 'mobile' => '/^869(?:5(?:5[6-8]|6[5-7])|66\\d|76[02-6])\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^333|9(?:11|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KP.php b/module/Zend/I18n/src/Validator/PhoneNumber/KP.php
new file mode 100644
index 00000000..b81bf6a5
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KP.php
@@ -0,0 +1,24 @@
+ '850',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^1\\d{9}|[28]\\d{7}$/',
+ 'fixed' => '/^2\\d{7}|85\\d{6}$/',
+ 'mobile' => '/^19[123]\\d{7}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,8}|\\d{10}$/',
+ 'fixed' => '/^\\d{6,8}$/',
+ 'mobile' => '/^\\d{10}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KR.php b/module/Zend/I18n/src/Validator/PhoneNumber/KR.php
new file mode 100644
index 00000000..6f4a0631
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KR.php
@@ -0,0 +1,36 @@
+ '82',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-7]\\d{3,9}|8\\d{8}$/',
+ 'fixed' => '/^(?:2|[34][1-3]|5[1-5]|6[1-4])(?:1\\d{2,3}|[2-9]\\d{6,7})$/',
+ 'mobile' => '/^1[0-25-9]\\d{7,8}$/',
+ 'tollfree' => '/^80\\d{7}$/',
+ 'premium' => '/^60[2-9]\\d{6}$/',
+ 'personal' => '/^50\\d{8}$/',
+ 'voip' => '/^70\\d{8}$/',
+ 'uan' => '/^1(?:5(?:44|66|77|88|99)|6(?:00|44|6[16]|70|88))\\d{4}$/',
+ 'emergency' => '/^11[29]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4,10}$/',
+ 'fixed' => '/^\\d{4,10}$/',
+ 'mobile' => '/^\\d{9,10}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'uan' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KW.php b/module/Zend/I18n/src/Validator/PhoneNumber/KW.php
new file mode 100644
index 00000000..d08bea98
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KW.php
@@ -0,0 +1,28 @@
+ '965',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[12569]\\d{6,7}$/',
+ 'fixed' => '/^(?:18\\d|2(?:[23]\\d{2}|4(?:[1-35-9]\\d|44)|5(?:0[034]|[2-46]\\d|5[1-3]|7[1-7])))\\d{4}$/',
+ 'mobile' => '/^(?:5(?:11|[05]\\d)|6(?:0[034679]|5[015-9]|6\\d|7[067]|9[069])|9(?:0[09]|4[049]|6[69]|[79]\\d))\\d{5}$/',
+ 'shortcode' => '/^1(?:[02-9]\\d|1[013-9])$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'fixed' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KY.php b/module/Zend/I18n/src/Validator/PhoneNumber/KY.php
new file mode 100644
index 00000000..ba1acba2
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KY.php
@@ -0,0 +1,33 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[3589]\\d{9}$/',
+ 'fixed' => '/^345(?:2(?:22|44)|444|6(?:23|38|40)|7(?:4[35-79]|6[6-9]|77)|8(?:00|1[45]|25|[48]8)|9(?:14|4[035-9]))\\d{4}$/',
+ 'mobile' => '/^345(?:32[1-9]|5(?:1[67]|2[5-7]|4[6-8]|76)|9(?:1[67]|2[3-9]|3[689]))\\d{4}$/',
+ 'pager' => '/^345849\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}|345976\\d{4}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'pager' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/KZ.php b/module/Zend/I18n/src/Validator/PhoneNumber/KZ.php
new file mode 100644
index 00000000..79494f39
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/KZ.php
@@ -0,0 +1,27 @@
+ '7',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^(?:33\\d|7\\d{2}|80[09])\\d{7}$/',
+ 'fixed' => '/^33622\\d{5}|7(?:1(?:0(?:[23]\\d|4[023]|59|63)|1(?:[23]\\d|4[0-79]|59)|2(?:[23]\\d|59)|3(?:2\\d|3[1-79]|4[0-35-9]|59)|4(?:2\\d|3[013-79]|4[0-8]|5[1-79])|5(?:2\\d|3[1-8]|4[1-7]|59)|6(?:[234]\\d|5[19]|61)|72\\d|8(?:[27]\\d|3[1-46-9]|4[0-5]))|2(?:1(?:[23]\\d|4[46-9]|5[3469])|2(?:2\\d|3[0679]|46|5[12679])|3(?:[234]\\d|5[139])|4(?:2\\d|3[1235-9]|59)|5(?:[23]\\d|4[01246-8]|59|61)|6(?:2\\d|3[1-9]|4[0-4]|59)|7(?:[237]\\d|40|5[279])|8(?:[23]\\d|4[0-3]|59)|9(?:2\\d|3[124578]|59)))\\d{5}$/',
+ 'mobile' => '/^7(?:0[01257]|6[02-4]|7[1578]|85)\\d{7}$/',
+ 'tollfree' => '/^800\\d{7}$/',
+ 'premium' => '/^809\\d{7}$/',
+ 'voip' => '/^751\\d{7}$/',
+ 'emergency' => '/^1(?:0[123]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LA.php b/module/Zend/I18n/src/Validator/PhoneNumber/LA.php
new file mode 100644
index 00000000..0065e6ae
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LA.php
@@ -0,0 +1,26 @@
+ '856',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-8]\\d{7,9}$/',
+ 'fixed' => '/^(?:2[13]|[35-7][14]|41|8[1468])\\d{6}$/',
+ 'mobile' => '/^20(?:2[2389]|5[4-689]|7[6-8]|9[57-9])\\d{6}$/',
+ 'emergency' => '/^19[015]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'fixed' => '/^\\d{6,8}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LB.php b/module/Zend/I18n/src/Validator/PhoneNumber/LB.php
new file mode 100644
index 00000000..ac93ff2d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LB.php
@@ -0,0 +1,30 @@
+ '961',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[13-9]\\d{6,7}$/',
+ 'fixed' => '/^(?:[14-6]\\d{2}|7(?:[2-579]\\d|62|8[0-7])|[89][2-9]\\d)\\d{4}$/',
+ 'mobile' => '/^(?:3\\d|7(?:[01]\\d|6[013-9]|8[89]|91))\\d{5}$/',
+ 'premium' => '/^9[01]\\d{6}$/',
+ 'shared' => '/^8[01]\\d{6}$/',
+ 'emergency' => '/^1(?:12|40|75)|999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'mobile' => '/^\\d{7,8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'shared' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LC.php b/module/Zend/I18n/src/Validator/PhoneNumber/LC.php
new file mode 100644
index 00000000..637e36af
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LC.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5789]\\d{9}$/',
+ 'fixed' => '/^758(?:234|4(?:30|5[0-9]|6[2-9]|8[0-2])|572|638|758)\\d{4}$/',
+ 'mobile' => '/^758(?:28[4-7]|384|4(?:6[01]|8[4-9])|5(?:1[89]|20|84)|7(?:1[2-9]|2[0-6]))\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^9(?:11|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LI.php b/module/Zend/I18n/src/Validator/PhoneNumber/LI.php
new file mode 100644
index 00000000..3c31c8a7
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LI.php
@@ -0,0 +1,37 @@
+ '423',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^6\\d{8}|[23789]\\d{6}$/',
+ 'fixed' => '/^(?:2(?:01|1[27]|3\\d|6[02-578]|96)|3(?:7[0135-7]|8[048]|9[0269]))\\d{4}$/',
+ 'mobile' => '/^6(?:51[01]|6(?:[01][0-4]|2[016-9]|88)|710)\\d{5}|7(?:36|4[25]|56|[7-9]\\d)\\d{4}$/',
+ 'tollfree' => '/^80(?:0(?:2[238]|79)|9\\d{2})\\d{2}$/',
+ 'premium' => '/^90(?:0(?:2[278]|79)|1(?:23|3[012])|6(?:4\\d|6[0126]))\\d{2}$/',
+ 'uan' => '/^87(?:0[128]|7[0-4])\\d{3}$/',
+ 'voicemail' => '/^697(?:[35]6|4[25]|[7-9]\\d)\\d{4}$/',
+ 'personal' => '/^701\\d{4}$/',
+ 'shortcode' => '/^1(?:145|4(?:[0357]|14)|50\\d{4}|6(?:00|[1-4])|75|8(?:1[128]|7))$/',
+ 'emergency' => '/^1(?:1[278]|44)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'tollfree' => '/^\\d{7}$/',
+ 'premium' => '/^\\d{7}$/',
+ 'uan' => '/^\\d{7}$/',
+ 'voicemail' => '/^\\d{9}$/',
+ 'personal' => '/^\\d{7}$/',
+ 'shortcode' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LK.php b/module/Zend/I18n/src/Validator/PhoneNumber/LK.php
new file mode 100644
index 00000000..8a7ce086
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LK.php
@@ -0,0 +1,25 @@
+ '94',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{8}$/',
+ 'fixed' => '/^(?:[189]1|2[13-7]|3[1-8]|4[157]|5[12457]|6[35-7])[2-57]\\d{6}$/',
+ 'mobile' => '/^7[125-8]\\d{7}$/',
+ 'emergency' => '/^11[0189]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LR.php b/module/Zend/I18n/src/Validator/PhoneNumber/LR.php
new file mode 100644
index 00000000..407f83cf
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LR.php
@@ -0,0 +1,29 @@
+ '231',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^(?:[29]\\d|[4-6]|7\\d{1,2}|[38]\\d{2})\\d{6}$/',
+ 'fixed' => '/^2\\d{7}$/',
+ 'mobile' => '/^(?:4[67]|5\\d|6[4-8]|7(?:7[67]\\d|\\d{2})|88\\d{2})\\d{5}$/',
+ 'premium' => '/^90\\d{6}$/',
+ 'voip' => '/^33200\\d{4}$/',
+ 'emergency' => '/^355|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LS.php b/module/Zend/I18n/src/Validator/PhoneNumber/LS.php
new file mode 100644
index 00000000..cc9f8335
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LS.php
@@ -0,0 +1,25 @@
+ '266',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2568]\\d{7}$/',
+ 'fixed' => '/^2\\d{7}$/',
+ 'mobile' => '/^[56]\\d{7}$/',
+ 'tollfree' => '/^800[256]\\d{4}$/',
+ 'emergency' => '/^11[257]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LT.php b/module/Zend/I18n/src/Validator/PhoneNumber/LT.php
new file mode 100644
index 00000000..528eb245
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LT.php
@@ -0,0 +1,29 @@
+ '370',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[3-9]\\d{7}$/',
+ 'fixed' => '/^(?:3[1478]|4[124-6]|52)\\d{6}$/',
+ 'mobile' => '/^6\\d{7}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'premium' => '/^9(?:0[0239]|10)\\d{5}$/',
+ 'personal' => '/^700\\d{5}$/',
+ 'shared' => '/^808\\d{5}$/',
+ 'uan' => '/^70[67]\\d{5}$/',
+ 'emergency' => '/^0(?:11?|22?|33?)|1(?:0[123]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LU.php b/module/Zend/I18n/src/Validator/PhoneNumber/LU.php
new file mode 100644
index 00000000..ef9699fb
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LU.php
@@ -0,0 +1,37 @@
+ '352',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[24-9]\\d{3,10}|3(?:[0-46-9]\\d{2,9}|5[013-9]\\d{1,8})$/',
+ 'fixed' => '/^(?:2(?:2\\d{1,2}|3[2-9]|[67]\\d|4[1-8]\\d?|5[1-5]\\d?|9[0-24-9]\\d?)|3(?:[059][05-9]|[13]\\d|[26][015-9]|4[0-26-9]|7[0-389]|8[08])\\d?|4\\d{2,3}|5(?:[01458]\\d|[27][0-69]|3[0-3]|[69][0-7])\\d?|7(?:1[019]|2[05-9]|3[05]|[45][07-9]|[679][089]|8[06-9])\\d?|8(?:0[2-9]|1[0-36-9]|3[3-9]|[469]9|[58][7-9]|7[89])\\d?|9(?:0[89]|2[0-49]|37|49|5[0-27-9]|7[7-9]|9[0-478])\\d?)\\d{1,7}$/',
+ 'mobile' => '/^6[269][18]\\d{6}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'premium' => '/^90[01]\\d{5}$/',
+ 'shared' => '/^801\\d{5}$/',
+ 'personal' => '/^70\\d{6}$/',
+ 'voip' => '/^20\\d{2,8}$/',
+ 'shortcode' => '/^12\\d{3}$/',
+ 'emergency' => '/^11[23]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4,11}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'shared' => '/^\\d{8}$/',
+ 'personal' => '/^\\d{8}$/',
+ 'voip' => '/^\\d{4,10}$/',
+ 'shortcode' => '/^\\d{3,5}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LV.php b/module/Zend/I18n/src/Validator/PhoneNumber/LV.php
new file mode 100644
index 00000000..ea311978
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LV.php
@@ -0,0 +1,27 @@
+ '371',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2689]\\d{7}$/',
+ 'fixed' => '/^6[3-8]\\d{6}$/',
+ 'mobile' => '/^2\\d{7}$/',
+ 'tollfree' => '/^80\\d{6}$/',
+ 'premium' => '/^90\\d{6}$/',
+ 'shared' => '/^81\\d{6}$/',
+ 'emergency' => '/^0[123]|112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/LY.php b/module/Zend/I18n/src/Validator/PhoneNumber/LY.php
new file mode 100644
index 00000000..ef7d00f1
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/LY.php
@@ -0,0 +1,25 @@
+ '218',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[25679]\\d{8}$/',
+ 'fixed' => '/^(?:2[1345]|5[1347]|6[123479]|71)\\d{7}$/',
+ 'mobile' => '/^9[1-6]\\d{7}$/',
+ 'emergency' => '/^19[013]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MA.php b/module/Zend/I18n/src/Validator/PhoneNumber/MA.php
new file mode 100644
index 00000000..25ab3522
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MA.php
@@ -0,0 +1,26 @@
+ '212',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5689]\\d{8}$/',
+ 'fixed' => '/^5(?:2(?:(?:[015-7]\\d|2[2-9]|3[2-57]|4[2-8]|8[235-7])\\d|9(?:0\\d|[89]0))|3(?:(?:[0-4]\\d|[57][2-9]|6[235-8]|9[3-9])\\d|8(?:0\\d|[89]0)))\\d{4}$/',
+ 'mobile' => '/^6(?:0[0-6]|[14-7]\\d|2[2-46-9]|3[03-8]|8[01]|99)\\d{6}$/',
+ 'tollfree' => '/^80\\d{7}$/',
+ 'premium' => '/^89\\d{7}$/',
+ 'emergency' => '/^1(?:[59]|77)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MC.php b/module/Zend/I18n/src/Validator/PhoneNumber/MC.php
new file mode 100644
index 00000000..5ed6fe66
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MC.php
@@ -0,0 +1,27 @@
+ '377',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[4689]\\d{7,8}$/',
+ 'fixed' => '/^9[2-47-9]\\d{6}$/',
+ 'mobile' => '/^6\\d{8}|4\\d{7}$/',
+ 'tollfree' => '/^(?:8\\d|90)\\d{6}$/',
+ 'emergency' => '/^1(?:12|[578])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,9}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MD.php b/module/Zend/I18n/src/Validator/PhoneNumber/MD.php
new file mode 100644
index 00000000..4094d6fb
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MD.php
@@ -0,0 +1,31 @@
+ '373',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[235-9]\\d{7}$/',
+ 'fixed' => '/^(?:2(?:1[0569]|2\\d|3[015-7]|4[1-46-9]|5[0-24689]|6[2-589]|7[1-37]|9[1347-9])|5(?:33|5[257]))\\d{5}$/',
+ 'mobile' => '/^(?:562|6(?:50|7[1-5]|[089]\\d)|7(?:7[47-9]|[89]\\d))\\d{5}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'premium' => '/^90[056]\\d{5}$/',
+ 'shared' => '/^808\\d{5}$/',
+ 'uan' => '/^8(?:03|14)\\d{5}$/',
+ 'voip' => '/^3[08]\\d{6}$/',
+ 'shortcode' => '/^1(?:1(?:[79]|6(?:000|1(?:11|23))|8\\d)|4\\d{3}|5[0-3]\\d|6[0-389]\\d|8\\d{2}|9(?:0[04-9]|[1-4]\\d))$/',
+ 'emergency' => '/^112|90[123]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/ME.php b/module/Zend/I18n/src/Validator/PhoneNumber/ME.php
new file mode 100644
index 00000000..5e9f053d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/ME.php
@@ -0,0 +1,36 @@
+ '382',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{7,8}$/',
+ 'fixed' => '/^(?:20[2-8]|3(?:0[2-7]|1[35-7]|2[3567]|3[4-7])|4(?:0[237]|1[27])|5(?:0[47]|1[27]|2[378]))\\d{5}$/',
+ 'mobile' => '/^6(?:32\\d|[89]\\d{2}|7(?:[0-8]\\d|9(?:[3-9]|[0-2]\\d)))\\d{4}$/',
+ 'tollfree' => '/^800[28]\\d{4}$/',
+ 'premium' => '/^(?:88\\d|9(?:4[13-8]|5[16-8]))\\d{5}$/',
+ 'voip' => '/^78[1-9]\\d{5}$/',
+ 'uan' => '/^77\\d{6}$/',
+ 'shortcode' => '/^1(?:16\\d{3}|2(?:[015-9]|\\d{2})|[0135]\\d{2}|4\\d{2,3}|9\\d{3})$/',
+ 'emergency' => '/^1(?:12|2[234])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,9}$/',
+ 'fixed' => '/^\\d{6,8}$/',
+ 'mobile' => '/^\\d{8,9}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'uan' => '/^\\d{8}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MF.php b/module/Zend/I18n/src/Validator/PhoneNumber/MF.php
new file mode 100644
index 00000000..fcb0569a
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MF.php
@@ -0,0 +1,24 @@
+ '590',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[56]\\d{8}$/',
+ 'fixed' => '/^590(?:10|2[79]|5[128]|[78]7)\\d{4}$/',
+ 'mobile' => '/^690(?:10|2[27]|66|77|8[78])\\d{4}$/',
+ 'emergency' => '/^1[578]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MG.php b/module/Zend/I18n/src/Validator/PhoneNumber/MG.php
new file mode 100644
index 00000000..762dbaea
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MG.php
@@ -0,0 +1,25 @@
+ '261',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[23]\\d{8}$/',
+ 'fixed' => '/^2(?:0(?:(?:2\\d|4[47]|5[3467]|6[279]|8[268]|9[245])\\d|7(?:2[29]|[35]\\d))|210\\d)\\d{4}$/',
+ 'mobile' => '/^3[02-4]\\d{7}$/',
+ 'emergency' => '/^11?[78]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MH.php b/module/Zend/I18n/src/Validator/PhoneNumber/MH.php
new file mode 100644
index 00000000..cbeef135
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MH.php
@@ -0,0 +1,23 @@
+ '692',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-6]\\d{6}$/',
+ 'fixed' => '/^(?:247|528|625)\\d{4}$/',
+ 'mobile' => '/^(?:235|329|45[56]|545)\\d{4}$/',
+ 'voip' => '/^635\\d{4}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MK.php b/module/Zend/I18n/src/Validator/PhoneNumber/MK.php
new file mode 100644
index 00000000..8b0e01d2
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MK.php
@@ -0,0 +1,28 @@
+ '389',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-578]\\d{7}$/',
+ 'fixed' => '/^(?:2(?:[23]\\d|5[124578]|6[01])|3(?:1[3-6]|[23][2-6]|4[2356])|4(?:[23][2-6]|4[3-6]|5[256]|6[25-8]|7[24-6]|8[4-6]))\\d{5}$/',
+ 'mobile' => '/^7(?:[0-25-8]\\d|33)\\d{5}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'premium' => '/^5[02-9]\\d{6}$/',
+ 'shared' => '/^8(?:0[1-9]|[1-9]\\d)\\d{5}$/',
+ 'emergency' => '/^1(?:12|9[234])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'fixed' => '/^\\d{6,8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/ML.php b/module/Zend/I18n/src/Validator/PhoneNumber/ML.php
new file mode 100644
index 00000000..4228ea24
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/ML.php
@@ -0,0 +1,25 @@
+ '223',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[246-8]\\d{7}$/',
+ 'fixed' => '/^(?:2(?:0(?:2[0-589]|7[027-9])|1(?:2[5-7]|[3-689]\\d))|44[239]\\d)\\d{4}$/',
+ 'mobile' => '/^(?:6[3569]|7\\d)\\d{6}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'emergency' => '/^1[578]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MM.php b/module/Zend/I18n/src/Validator/PhoneNumber/MM.php
new file mode 100644
index 00000000..16540fa9
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MM.php
@@ -0,0 +1,28 @@
+ '95',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[14578]\\d{5,7}|[26]\\d{5,8}|9(?:[258]|4\\d{1,2}|[679]\\d?)\\d{6}$/',
+ 'fixed' => '/^1(?:2\\d{1,2}|[3-5]\\d|6\\d?|[89][0-6]\\d)\\d{4}|2(?:[236-9]\\d{4}|4(?:0\\d{5}|\\d{4})|5(?:1\\d{3,6}|[02-9]\\d{3,5}))|4(?:2[245-8]|[346][2-6]|5[3-5])\\d{4}|5(?:2(?:20?|[3-8])|3[2-68]|4(?:21?|[4-8])|5[23]|6[2-4]|7[2-8]|8[24-7]|9[2-7])\\d{4}|6(?:0[23]|1[2356]|[24][2-6]|3[24-6]|5[2-4]|6[2-8]|7(?:[2367]|4\\d|5\\d?|8[145]\\d)|8[245]|9[24])\\d{4}|7(?:[04][24-8]|[15][2-7]|22|3[2-4])\\d{4}|8(?:1(?:2\\d?|[3-689])|2[2-8]|3[24]|4[24-7]|5[245]|6[23])\\d{4}$/',
+ 'mobile' => '/^17[01]\\d{4}|9(?:2[0-4]|4(?:0[0-4]\\d|[1379]\\d|[24][0-589]\\d|5\\d{2}|88)|5[0-6]|61?\\d|73\\d|8\\d|9(?:1\\d|[089]))\\d{5}$/',
+ 'voip' => '/^1333\\d{4}$/',
+ 'emergency' => '/^199$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,10}$/',
+ 'fixed' => '/^\\d{5,9}$/',
+ 'mobile' => '/^\\d{7,10}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MN.php b/module/Zend/I18n/src/Validator/PhoneNumber/MN.php
new file mode 100644
index 00000000..822ec8dc
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MN.php
@@ -0,0 +1,27 @@
+ '976',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[12]\\d{7,9}|[57-9]\\d{7}$/',
+ 'fixed' => '/^[12](?:1\\d|2(?:[1-3]\\d?|7\\d)|3[2-8]\\d{1,2}|4[2-68]\\d{1,2}|5[1-4689]\\d{1,2})\\d{5}|5[0568]\\d{6}$/',
+ 'mobile' => '/^(?:8[89]|9[013-9])\\d{6}$/',
+ 'voip' => '/^7[05-8]\\d{6}$/',
+ 'emergency' => '/^10[0-3]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MO.php b/module/Zend/I18n/src/Validator/PhoneNumber/MO.php
new file mode 100644
index 00000000..300cd2b2
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MO.php
@@ -0,0 +1,24 @@
+ '853',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[268]\\d{7}$/',
+ 'fixed' => '/^(?:28[2-57-9]|8[2-57-9]\\d)\\d{5}$/',
+ 'mobile' => '/^6[2356]\\d{6}$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MP.php b/module/Zend/I18n/src/Validator/PhoneNumber/MP.php
new file mode 100644
index 00000000..01f46fed
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MP.php
@@ -0,0 +1,30 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5689]\\d{9}$/',
+ 'fixed' => '/^670(?:2(?:3[3-7]|56|8[5-8])|32[1238]|4(?:33|8[348])|5(?:32|55|88)|6(?:64|70|82)|78[589]|8[3-9]8|989)\\d{4}$/',
+ 'mobile' => '/^670(?:2(?:3[3-7]|56|8[5-8])|32[1238]|4(?:33|8[348])|5(?:32|55|88)|6(?:64|70|82)|78[589]|8[3-9]8|989)\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MQ.php b/module/Zend/I18n/src/Validator/PhoneNumber/MQ.php
new file mode 100644
index 00000000..501fa09a
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MQ.php
@@ -0,0 +1,24 @@
+ '596',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[56]\\d{8}$/',
+ 'fixed' => '/^596(?:0[2-5]|[12]0|3[05-9]|4[024-8]|[5-7]\\d|89|9[4-8])\\d{4}$/',
+ 'mobile' => '/^696(?:[0-479]\\d|5[01]|8[0-689])\\d{4}$/',
+ 'emergency' => '/^1(?:12|[578])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MR.php b/module/Zend/I18n/src/Validator/PhoneNumber/MR.php
new file mode 100644
index 00000000..acdd7a9d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MR.php
@@ -0,0 +1,25 @@
+ '222',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-48]\\d{7}$/',
+ 'fixed' => '/^25[08]\\d{5}|35\\d{6}|45[1-7]\\d{5}$/',
+ 'mobile' => '/^(?:2(?:2\\d|70)|3(?:3\\d|6[1-36]|7[1-3])|4(?:4\\d|6[0457-9]|7[4-9]))\\d{5}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'emergency' => '/^1[78]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MS.php b/module/Zend/I18n/src/Validator/PhoneNumber/MS.php
new file mode 100644
index 00000000..87e63034
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MS.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5689]\\d{9}$/',
+ 'fixed' => '/^664491\\d{4}$/',
+ 'mobile' => '/^66449[2-6]\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^9(?:11|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MT.php b/module/Zend/I18n/src/Validator/PhoneNumber/MT.php
new file mode 100644
index 00000000..c186cc7f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MT.php
@@ -0,0 +1,26 @@
+ '356',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2579]\\d{7}$/',
+ 'fixed' => '/^2(?:0(?:1[0-6]|[69]\\d)|[1-357]\\d{2})\\d{4}$/',
+ 'mobile' => '/^(?:7(?:210|[79]\\d{2})|9(?:2[13]\\d|696|8(?:1[1-3]|89|97)|9\\d{2}))\\d{4}$/',
+ 'pager' => '/^7117\\d{4}$/',
+ 'premium' => '/^50(?:0(?:3[1679]|4\\d)|[169]\\d{2}|7[06]\\d)\\d{3}$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MU.php b/module/Zend/I18n/src/Validator/PhoneNumber/MU.php
new file mode 100644
index 00000000..3af42e58
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MU.php
@@ -0,0 +1,30 @@
+ '230',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{6}$/',
+ 'fixed' => '/^(?:2(?:[034789]\\d|1[0-7]|6[1-69])|4(?:[013-8]\\d|2[4-7])|[56]\\d{2}|8(?:14|3[129]))\\d{4}$/',
+ 'mobile' => '/^(?:25\\d|4(?:2[12389]|9\\d)|7\\d{2}|8(?:20|7[15-8])|9[1-8]\\d)\\d{4}$/',
+ 'pager' => '/^2(?:1[89]|2\\d)\\d{4}$/',
+ 'tollfree' => '/^80[012]\\d{4}$/',
+ 'premium' => '/^30\\d{5}$/',
+ 'voip' => '/^3(?:20|9\\d)\\d{4}$/',
+ 'shortcode' => '/^1(?:1[0-36-9]|[02-9]\\d|\\d{3,4})|8\\d{3}$/',
+ 'emergency' => '/^11[45]|99\\d$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'shortcode' => '/^\\d{3,5}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MV.php b/module/Zend/I18n/src/Validator/PhoneNumber/MV.php
new file mode 100644
index 00000000..77068531
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MV.php
@@ -0,0 +1,32 @@
+ '960',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[3467]\\d{6}|9(?:00\\d{7}|\\d{6})$/',
+ 'fixed' => '/^(?:3(?:0[01]|3[0-59])|6(?:[567][02468]|8[024689]|90))\\d{4}$/',
+ 'mobile' => '/^(?:46[46]|7[3-9]\\d|9[6-9]\\d)\\d{4}$/',
+ 'pager' => '/^781\\d{4}$/',
+ 'premium' => '/^900\\d{7}$/',
+ 'shortcode' => '/^1(?:[19]0|23)$/',
+ 'emergency' => '/^1(?:02|19)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,10}$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'mobile' => '/^\\d{7}$/',
+ 'pager' => '/^\\d{7}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MW.php b/module/Zend/I18n/src/Validator/PhoneNumber/MW.php
new file mode 100644
index 00000000..0f4438ed
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MW.php
@@ -0,0 +1,25 @@
+ '265',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^(?:1(?:\\d{2})?|[2789]\\d{2})\\d{6}$/',
+ 'fixed' => '/^(?:1[2-9]|21\\d{2})\\d{5}$/',
+ 'mobile' => '/^(?:111|77\\d|88\\d|99\\d)\\d{6}$/',
+ 'emergency' => '/^199|99[789]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MX.php b/module/Zend/I18n/src/Validator/PhoneNumber/MX.php
new file mode 100644
index 00000000..132ad7de
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MX.php
@@ -0,0 +1,30 @@
+ '52',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{9,10}$/',
+ 'fixed' => '/^(?:33|55|81)\\d{8}|(?:2(?:2[2-9]|3[1-35-8]|4[13-9]|7[1-689]|8[1-578]|9[467])|3(?:1[1-79]|[2458][1-9]|7[1-8]|9[1-5])|4(?:1[1-57-9]|[24-6][1-9]|[37][1-8]|8[1-35-9]|9[2-689])|5(?:88|9[1-79])|6(?:1[2-68]|[234][1-9]|5[1-3689]|6[12457-9]|7[1-7]|8[67]|9[4-8])|7(?:[13467][1-9]|2[1-8]|5[13-9]|8[1-69]|9[17])|8(?:2[13-689]|3[1-6]|4[124-6]|6[1246-9]|7[1-378]|9[12479])|9(?:1[346-9]|2[1-4]|3[2-46-8]|5[1348]|[69][1-9]|7[12]|8[1-8]))\\d{7}$/',
+ 'mobile' => '/^1(?:(?:33|55|81)\\d{8}|(?:2(?:2[2-9]|3[1-35-8]|4[13-9]|7[1-689]|8[1-578]|9[467])|3(?:1[1-79]|[2458][1-9]|7[1-8]|9[1-5])|4(?:1[1-57-9]|[24-6][1-9]|[37][1-8]|8[1-35-9]|9[2-689])|5(?:88|9[1-79])|6(?:1[2-68]|[2-4][1-9]|5[1-3689]|6[12457-9]|7[1-7]|8[67]|9[4-8])|7(?:[13467][1-9]|2[1-8]|5[13-9]|8[1-69]|9[17])|8(?:2[13-689]|3[1-6]|4[124-6]|6[1246-9]|7[1-378]|9[12479])|9(?:1[346-9]|2[1-4]|3[2-46-8]|5[1348]|[69][1-9]|7[12]|8[1-8]))\\d{7})$/',
+ 'tollfree' => '/^800\\d{7}$/',
+ 'premium' => '/^900\\d{7}$/',
+ 'emergency' => '/^06[568]|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,11}$/',
+ 'fixed' => '/^\\d{7,10}$/',
+ 'mobile' => '/^\\d{11}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MY.php b/module/Zend/I18n/src/Validator/PhoneNumber/MY.php
new file mode 100644
index 00000000..ab7f041c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MY.php
@@ -0,0 +1,34 @@
+ '60',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[13-9]\\d{7,9}$/',
+ 'fixed' => '/^(?:3[2-9]\\d|[4-9][2-9])\\d{6}$/',
+ 'mobile' => '/^1(?:1[1-3]\\d{2}|[02-4679][2-9]\\d|8(?:1[23]|[2-9]\\d))\\d{5}$/',
+ 'tollfree' => '/^1[38]00\\d{6}$/',
+ 'premium' => '/^1600\\d{6}$/',
+ 'personal' => '/^1700\\d{6}$/',
+ 'voip' => '/^154\\d{7}$/',
+ 'emergency' => '/^112|999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'fixed' => '/^\\d{6,9}$/',
+ 'mobile' => '/^\\d{9,10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/MZ.php b/module/Zend/I18n/src/Validator/PhoneNumber/MZ.php
new file mode 100644
index 00000000..3633e028
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/MZ.php
@@ -0,0 +1,30 @@
+ '258',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[28]\\d{7,8}$/',
+ 'fixed' => '/^2(?:[1346]\\d|5[0-2]|[78][12]|93)\\d{5}$/',
+ 'mobile' => '/^8[246]\\d{7}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'shortcode' => '/^1[0234]\\d$/',
+ 'emergency' => '/^1(?:1[79]|9[78])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,9}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NA.php b/module/Zend/I18n/src/Validator/PhoneNumber/NA.php
new file mode 100644
index 00000000..b207ff9f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NA.php
@@ -0,0 +1,30 @@
+ '264',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[68]\\d{7,8}$/',
+ 'fixed' => '/^6(?:1(?:17|2(?:[0189]\\d|[2-6]|7\\d?)|3(?:2\\d|3[378])|4[01]|69|7[014])|2(?:17|25|5(?:[0-36-8]|4\\d?)|69|70)|3(?:17|2(?:[0237]\\d?|[14-689])|34|6[29]|7[01]|81)|4(?:17|2(?:[012]|7?)|4(?:[06]|1\\d)|5(?:[01357]|[25]\\d?)|69|7[01])|5(?:17|2(?:[0459]|[23678]\\d?)|69|7[01])|6(?:17|2(?:5|6\\d?)|38|42|69|7[01])|7(?:17|2(?:[569]|[234]\\d?)|3(?:0\\d?|[13])|69|7[01]))\\d{4}$/',
+ 'mobile' => '/^(?:60|8[125])\\d{7}$/',
+ 'premium' => '/^8701\\d{5}$/',
+ 'voip' => '/^8(3\\d{2}|86)\\d{5}$/',
+ 'shortcode' => '/^1\\d{3}|9(?:3111|\\d{2})$/',
+ 'emergency' => '/^10111$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'shortcode' => '/^\\d{3,5}$/',
+ 'emergency' => '/^\\d{5}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NC.php b/module/Zend/I18n/src/Validator/PhoneNumber/NC.php
new file mode 100644
index 00000000..973d1b0c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NC.php
@@ -0,0 +1,27 @@
+ '687',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-47-9]\\d{5}$/',
+ 'fixed' => '/^(?:2[03-9]|3[0-5]|4[1-7]|88)\\d{4}$/',
+ 'mobile' => '/^(?:[79]\\d|8[0-79])\\d{4}$/',
+ 'premium' => '/^36\\d{4}$/',
+ 'shortcode' => '/^10(?:0[06]|1[02-46]|20|3[0125]|42|5[058]|77)$/',
+ 'emergency' => '/^1[5-8]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6}$/',
+ 'shortcode' => '/^\\d{4}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NE.php b/module/Zend/I18n/src/Validator/PhoneNumber/NE.php
new file mode 100644
index 00000000..78063788
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NE.php
@@ -0,0 +1,24 @@
+ '227',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[029]\\d{7}$/',
+ 'fixed' => '/^2(?:0(?:20|3[1-7]|4[134]|5[14]|6[14578]|7[1-578])|1(?:4[145]|5[14]|6[14-68]|7[169]|88))\\d{4}$/',
+ 'mobile' => '/^9[0-46-9]\\d{6}$/',
+ 'tollfree' => '/^08\\d{6}$/',
+ 'premium' => '/^09\\d{6}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NF.php b/module/Zend/I18n/src/Validator/PhoneNumber/NF.php
new file mode 100644
index 00000000..b6ac5159
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NF.php
@@ -0,0 +1,24 @@
+ '672',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[13]\\d{5}$/',
+ 'fixed' => '/^(?:1(?:06|17|28|39)|3[012]\\d)\\d{3}$/',
+ 'mobile' => '/^38\\d{4}$/',
+ 'emergency' => '/^9(?:11|55|77)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NG.php b/module/Zend/I18n/src/Validator/PhoneNumber/NG.php
new file mode 100644
index 00000000..a82ba7c5
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NG.php
@@ -0,0 +1,30 @@
+ '234',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-69]\\d{5,8}|[78]\\d{5,13}$/',
+ 'fixed' => '/^[12]\\d{6,7}|9\\d{7}|(?:3\\d|4[023568]|5[02368]|6[02-469]|7[4-69]|8[2-9])\\d{6}|(?:4[47]|5[14579]|6[1578]|7[0-357])\\d{5,6}|(?:78|41)\\d{5}$/',
+ 'mobile' => '/^(?:1(?:7[34]\\d|8(?:04|[124579]\\d|8[0-3])|95\\d)|287[0-7]|3(?:18[1-8]|88[0-7]|9(?:8[5-9]|6[1-5]))|4(?:28[0-2]|6(?:7[1-9]|8[02-47])|88[0-2])|5(?:2(?:7[7-9]|8\\d)|38[1-79]|48[0-7]|68[4-7])|6(?:2(?:7[7-9]|8\\d)|4(?:3[7-9]|[68][129]|7[04-69]|9[1-8])|58[0-2]|98[7-9])|7(?:38[0-7]|69[1-8]|78[2-4])|8(?:28[3-9]|38[0-2]|4(?:2[12]|3[147-9]|5[346]|7[4-9]|8[014-689]|90)|58[1-8]|78[2-9]|88[5-7])|98[07]\\d)\\d{4}|(?:70(?:[3-9]\\d|2[1-9])|8(?:0[2-9]|1\\d)\\d)\\d{6}$/',
+ 'tollfree' => '/^800\\d{7,11}$/',
+ 'uan' => '/^700\\d{7,11}$/',
+ 'emergency' => '/^199$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,14}$/',
+ 'fixed' => '/^\\d{5,9}$/',
+ 'mobile' => '/^\\d{8,10}$/',
+ 'tollfree' => '/^\\d{10,14}$/',
+ 'uan' => '/^\\d{10,14}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NI.php b/module/Zend/I18n/src/Validator/PhoneNumber/NI.php
new file mode 100644
index 00000000..79100bc9
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NI.php
@@ -0,0 +1,25 @@
+ '505',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[128]\\d{7}$/',
+ 'fixed' => '/^2\\d{7}$/',
+ 'mobile' => '/^[578]\\d{7}$/',
+ 'tollfree' => '/^1800\\d{4}$/',
+ 'emergency' => '/^118$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NL.php b/module/Zend/I18n/src/Validator/PhoneNumber/NL.php
new file mode 100644
index 00000000..7c312047
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NL.php
@@ -0,0 +1,38 @@
+ '31',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^1\\d{4,8}|[2-7]\\d{8}|[89]\\d{6,9}$/',
+ 'fixed' => '/^(?:1[0135-8]|2[02-69]|3[0-68]|4[0135-9]|[57]\\d|8[478])\\d{7}$/',
+ 'mobile' => '/^6[1-58]\\d{7}$/',
+ 'pager' => '/^66\\d{7}$/',
+ 'tollfree' => '/^800\\d{4,7}$/',
+ 'premium' => '/^90[069]\\d{4,7}$/',
+ 'voip' => '/^85\\d{7}$/',
+ 'uan' => '/^140(?:1(?:[035]|[16-8]\\d)|2(?:[0346]|[259]\\d)|3(?:[03568]|[124]\\d)|4(?:[0356]|[17-9]\\d)|5(?:[0358]|[124679]\\d)|7\\d|8[458])$/',
+ 'shortcode' => '/^18\\d{2}$/',
+ 'emergency' => '/^112|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,10}$/',
+ 'fixed' => '/^\\d{9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'pager' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{7,10}$/',
+ 'premium' => '/^\\d{7,10}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'uan' => '/^\\d{5,6}$/',
+ 'shortcode' => '/^\\d{4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NO.php b/module/Zend/I18n/src/Validator/PhoneNumber/NO.php
new file mode 100644
index 00000000..561f9e87
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NO.php
@@ -0,0 +1,39 @@
+ '47',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^0\\d{4}|[2-9]\\d{7}$/',
+ 'fixed' => '/^(?:2[1-4]|3[1-3578]|5[1-35-7]|6[1-4679]|7[0-8])\\d{6}$/',
+ 'mobile' => '/^(?:4[015-8]|5[89]|9\\d)\\d{6}$/',
+ 'tollfree' => '/^80[01]\\d{5}$/',
+ 'premium' => '/^82[09]\\d{5}$/',
+ 'shared' => '/^810(?:0[0-6]|[2-8]\\d)\\d{3}$/',
+ 'personal' => '/^880\\d{5}$/',
+ 'voip' => '/^85[0-5]\\d{5}$/',
+ 'uan' => '/^0\\d{4}|81(?:0(?:0[7-9]|1\\d)|5\\d{2})\\d{3}$/',
+ 'voicemail' => '/^81[23]\\d{5}$/',
+ 'emergency' => '/^11[023]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5}(?:\\d{3})?$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'shared' => '/^\\d{8}$/',
+ 'personal' => '/^\\d{8}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'voicemail' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NP.php b/module/Zend/I18n/src/Validator/PhoneNumber/NP.php
new file mode 100644
index 00000000..6ae4a51b
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NP.php
@@ -0,0 +1,26 @@
+ '977',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-8]\\d{7}|9(?:[1-69]\\d{6}|7[2-6]\\d{5,7}|8\\d{8})$/',
+ 'fixed' => '/^(?:1[0124-6]|2[13-79]|3[135-8]|4[146-9]|5[135-7]|6[13-9]|7[15-9]|8[1-46-9]|9[1-79])\\d{6}$/',
+ 'mobile' => '/^9(?:7[45]|8[0145])\\d{7}$/',
+ 'emergency' => '/^1(?:0[0-3]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'fixed' => '/^\\d{6,8}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NR.php b/module/Zend/I18n/src/Validator/PhoneNumber/NR.php
new file mode 100644
index 00000000..3f664c95
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NR.php
@@ -0,0 +1,26 @@
+ '674',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[458]\\d{6}$/',
+ 'fixed' => '/^(?:444|888)\\d{4}$/',
+ 'mobile' => '/^55[5-9]\\d{4}$/',
+ 'shortcode' => '/^1(?:23|92)$/',
+ 'emergency' => '/^11[0-2]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NU.php b/module/Zend/I18n/src/Validator/PhoneNumber/NU.php
new file mode 100644
index 00000000..1815b64c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NU.php
@@ -0,0 +1,24 @@
+ '683',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-5]\\d{3}$/',
+ 'fixed' => '/^[34]\\d{3}$/',
+ 'mobile' => '/^[125]\\d{3}$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/NZ.php b/module/Zend/I18n/src/Validator/PhoneNumber/NZ.php
new file mode 100644
index 00000000..aa90f2a5
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/NZ.php
@@ -0,0 +1,32 @@
+ '64',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^6[235-9]\\d{6}|[2-57-9]\\d{7,10}$/',
+ 'fixed' => '/^(?:3[2-79]|[49][2-689]|6[235-9]|7[2-589])\\d{6}|24099\\d{3}$/',
+ 'mobile' => '/^2(?:[028]\\d{7,8}|1(?:0\\d{5,7}|[12]\\d{5,6}|[3-9]\\d{5})|[79]\\d{7})$/',
+ 'pager' => '/^[28]6\\d{6,7}$/',
+ 'tollfree' => '/^508\\d{6,7}|80\\d{6,8}$/',
+ 'premium' => '/^90\\d{7,9}$/',
+ 'emergency' => '/^111$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,11}$/',
+ 'fixed' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{8,10}$/',
+ 'pager' => '/^\\d{8,9}$/',
+ 'tollfree' => '/^\\d{8,10}$/',
+ 'premium' => '/^\\d{9,11}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/OM.php b/module/Zend/I18n/src/Validator/PhoneNumber/OM.php
new file mode 100644
index 00000000..b2936916
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/OM.php
@@ -0,0 +1,28 @@
+ '968',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^(?:2[2-6]|5|9[1-9])\\d{6}|800\\d{5,6}$/',
+ 'fixed' => '/^2[2-6]\\d{6}$/',
+ 'mobile' => '/^9[1-9]\\d{6}$/',
+ 'tollfree' => '/^8007\\d{4,5}|500\\d{4}$/',
+ 'emergency' => '/^9999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{7,9}$/',
+ 'emergency' => '/^\\d{4}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PA.php b/module/Zend/I18n/src/Validator/PhoneNumber/PA.php
new file mode 100644
index 00000000..6314fb53
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PA.php
@@ -0,0 +1,31 @@
+ '507',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{6,7}$/',
+ 'fixed' => '/^(?:1(?:0[02-579]|19|2[37]|3[03]|4[479]|57|65|7[016-8]|8[58]|9[134])|2(?:[0235679]\\d|1[0-7]|4[04-9]|8[028])|3(?:0[0-7]|1[14-7]|2[0-3]|3[03]|4[0457]|5[56]|6[068]|7[078]|80|9\\d)|4(?:3[013-59]|4\\d|7[0-689])|5(?:[01]\\d|2[0-7]|[56]0|79)|7(?:0[09]|2[0-267]|[349]0|5[6-9]|7[0-24-7]|8[89])|8(?:[34]\\d|5[0-4]|8[02])|9(?:0[78]|1[0178]|2[0378]|3[379]|40|5[0489]|6[06-9]|7[046-9]|8[36-8]|9[1-9]))\\d{4}$/',
+ 'mobile' => '/^(?:1[16]1|21[89]|8(?:1[01]|7[23]))\\d{4}|6(?:[04-9]\\d|1[0-5]|2[0-7]|3[5-9])\\d{5}$/',
+ 'tollfree' => '/^80[09]\\d{4}$/',
+ 'premium' => '/^(?:779|8(?:2[235]|55|60|7[578]|86|95)|9(?:0[0-2]|81))\\d{4}$/',
+ 'shortcode' => '/^10[2-4]$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'tollfree' => '/^\\d{7}$/',
+ 'premium' => '/^\\d{7}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PE.php b/module/Zend/I18n/src/Validator/PhoneNumber/PE.php
new file mode 100644
index 00000000..2503083e
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PE.php
@@ -0,0 +1,34 @@
+ '51',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[14-9]\\d{7,8}$/',
+ 'fixed' => '/^(?:1\\d|4[1-4]|5[1-46]|6[1-7]|7[2-46]|8[2-4])\\d{6}$/',
+ 'mobile' => '/^9\\d{8}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'premium' => '/^805\\d{5}$/',
+ 'shared' => '/^801\\d{5}$/',
+ 'personal' => '/^80[24]\\d{5}$/',
+ 'emergency' => '/^1(?:05|1[67])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,9}$/',
+ 'fixed' => '/^\\d{6,8}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'shared' => '/^\\d{8}$/',
+ 'personal' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PF.php b/module/Zend/I18n/src/Validator/PhoneNumber/PF.php
new file mode 100644
index 00000000..b96c6537
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PF.php
@@ -0,0 +1,24 @@
+ '689',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{5}$/',
+ 'fixed' => '/^(?:4(?:[02-9]\\d|1[02-9])|[5689]\\d{2})\\d{3}$/',
+ 'mobile' => '/^(?:[27]\\d{2}|3[0-79]\\d|411)\\d{3}$/',
+ 'emergency' => '/^1[578]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PG.php b/module/Zend/I18n/src/Validator/PhoneNumber/PG.php
new file mode 100644
index 00000000..54dc0dcf
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PG.php
@@ -0,0 +1,30 @@
+ '675',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{6,7}$/',
+ 'fixed' => '/^(?:3\\d{2}|4[257]\\d|5[34]\\d|6(?:29|4[1-9])|85[02-46-9]|9[78]\\d)\\d{4}$/',
+ 'mobile' => '/^(?:68|7[0-36]\\d)\\d{5}$/',
+ 'tollfree' => '/^180\\d{4}$/',
+ 'voip' => '/^275\\d{4}$/',
+ 'emergency' => '/^000$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'mobile' => '/^\\d{7,8}$/',
+ 'tollfree' => '/^\\d{7}$/',
+ 'voip' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PH.php b/module/Zend/I18n/src/Validator/PhoneNumber/PH.php
new file mode 100644
index 00000000..657880bc
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PH.php
@@ -0,0 +1,28 @@
+ '63',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{7,9}|1800\\d{7,9}$/',
+ 'fixed' => '/^(?:2|3[2-68]|4[2-9]|5[2-6]|6[2-58]|7[24578]|8[2-8])\\d{7}$/',
+ 'mobile' => '/^9(?:0[5-9]|1[025-9]|2[0-36-9]|3[02-9]|4[236-9]|7[349]|89|9[49])\\d{7}$/',
+ 'tollfree' => '/^1800\\d{7,9}$/',
+ 'emergency' => '/^11[27]|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,13}$/',
+ 'fixed' => '/^\\d{7,9}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{11,13}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PK.php b/module/Zend/I18n/src/Validator/PhoneNumber/PK.php
new file mode 100644
index 00000000..e28919e1
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PK.php
@@ -0,0 +1,34 @@
+ '92',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^1\\d{8}|[2-8]\\d{5,11}|9(?:[013-9]\\d{4,9}|2\\d(?:111\\d{6}|\\d{3,7}))$/',
+ 'fixed' => '/^(?:21|42)[2-9]\\d{7}|(?:2[25]|4[0146-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)[2-9]\\d{6}|(?:2(?:3[2358]|4[2-4]|9[2-8])|45[3479]|54[2-467]|60[468]|72[236]|8(?:2[2-689]|3[23578]|4[3478]|5[2356])|9(?:1|2[2-8]|3[27-9]|4[2-6]|6[3569]|9[25-8]))[2-9]\\d{5,6}|58[126]\\d{7}$/',
+ 'mobile' => '/^3(?:0\\d|1[1-5]|2[0-5]|3[1-6]|4[1-7]|55|64)\\d{7}$/',
+ 'tollfree' => '/^800\\d{5}$/',
+ 'premium' => '/^900\\d{5}$/',
+ 'personal' => '/^122\\d{6}$/',
+ 'uan' => '/^(?:2(?:[125]|3[2358]|4[2-4]|9[2-8])|4(?:[0-246-9]|5[3479])|5(?:[1-35-7]|4[2-467])|6(?:[1-8]|0[468])|7(?:[14]|2[236])|8(?:[16]|2[2-689]|3[23578]|4[3478]|5[2356])|9(?:1|22|3[27-9]|4[2-6]|6[3569]|9[2-7]))111\\d{6}$/',
+ 'emergency' => '/^1(?:1(?:22?|5)|[56])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,12}$/',
+ 'fixed' => '/^\\d{6,10}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'personal' => '/^\\d{9}$/',
+ 'uan' => '/^\\d{11,12}$/',
+ 'emergency' => '/^\\d{2,4}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PL.php b/module/Zend/I18n/src/Validator/PhoneNumber/PL.php
new file mode 100644
index 00000000..d3346e1d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PL.php
@@ -0,0 +1,34 @@
+ '48',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-58]\\d{6,8}|9\\d{8}|[67]\\d{5,8}$/',
+ 'fixed' => '/^(?:1[2-8]|2[2-59]|3[2-4]|4[1-468]|5[24-689]|6[1-3578]|7[14-6]|8[1-7])\\d{5,7}|77\\d{4,7}|(?:89|9[145])\\d{7}$/',
+ 'mobile' => '/^(?:5[013]|6[069]|7[2389]|88)\\d{7}$/',
+ 'pager' => '/^642\\d{3,6}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'premium' => '/^70\\d{7}$/',
+ 'shared' => '/^801\\d{6}$/',
+ 'voip' => '/^39\\d{7}$/',
+ 'emergency' => '/^112|99[789]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'shared' => '/^\\d{9}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PM.php b/module/Zend/I18n/src/Validator/PhoneNumber/PM.php
new file mode 100644
index 00000000..8fbbee46
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PM.php
@@ -0,0 +1,24 @@
+ '508',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[45]\\d{5}$/',
+ 'fixed' => '/^41\\d{4}$/',
+ 'mobile' => '/^55\\d{4}$/',
+ 'emergency' => '/^1[578]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PR.php b/module/Zend/I18n/src/Validator/PhoneNumber/PR.php
new file mode 100644
index 00000000..af2fa2c4
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PR.php
@@ -0,0 +1,30 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5789]\\d{9}$/',
+ 'fixed' => '/^(?:787|939)[2-9]\\d{6}$/',
+ 'mobile' => '/^(?:787|939)[2-9]\\d{6}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PS.php b/module/Zend/I18n/src/Validator/PhoneNumber/PS.php
new file mode 100644
index 00000000..1bb8e85a
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PS.php
@@ -0,0 +1,30 @@
+ '970',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[24589]\\d{7,8}|1(?:[78]\\d{8}|[49]\\d{2,3})$/',
+ 'fixed' => '/^(?:22[234789]|42[45]|82[01458]|92[369])\\d{5}$/',
+ 'mobile' => '/^5[69]\\d{7}$/',
+ 'tollfree' => '/^1800\\d{6}$/',
+ 'premium' => '/^1(?:4|9\\d)\\d{2}$/',
+ 'shared' => '/^1700\\d{6}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4,10}$/',
+ 'fixed' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{4,5}$/',
+ 'shared' => '/^\\d{10}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PT.php b/module/Zend/I18n/src/Validator/PhoneNumber/PT.php
new file mode 100644
index 00000000..18a2ef1e
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PT.php
@@ -0,0 +1,30 @@
+ '351',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-46-9]\\d{8}$/',
+ 'fixed' => '/^2(?:[12]\\d|[35][1-689]|4[1-59]|6[1-35689]|7[1-9]|8[1-69]|9[1256])\\d{6}$/',
+ 'mobile' => '/^9(?:[136]\\d{2}|2[0-79]\\d|480)\\d{5}$/',
+ 'tollfree' => '/^80[02]\\d{6}$/',
+ 'premium' => '/^76(?:0[1-57]|1[2-47]|2[237])\\d{5}$/',
+ 'shared' => '/^80(?:8\\d|9[1579])\\d{5}$/',
+ 'personal' => '/^884[128]\\d{5}$/',
+ 'voip' => '/^30\\d{7}$/',
+ 'uan' => '/^70(?:7\\d|8[17])\\d{5}$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PW.php b/module/Zend/I18n/src/Validator/PhoneNumber/PW.php
new file mode 100644
index 00000000..0dbd2f7c
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PW.php
@@ -0,0 +1,24 @@
+ '680',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-8]\\d{6}$/',
+ 'fixed' => '/^2552255|(?:277|345|488|5(?:35|44|87)|6(?:22|54|79)|7(?:33|47)|8(?:24|55|76))\\d{4}$/',
+ 'mobile' => '/^(?:6[234689]0|77[45789])\\d{4}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/PY.php b/module/Zend/I18n/src/Validator/PhoneNumber/PY.php
new file mode 100644
index 00000000..2e7091f4
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/PY.php
@@ -0,0 +1,31 @@
+ '595',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^5[0-5]\\d{4,7}|[2-46-9]\\d{5,8}$/',
+ 'fixed' => '/^(?:[26]1|3[289]|4[124678]|7[123]|8[1236])\\d{5,7}|(?:2(?:2[4568]|7[15]|9[1-5])|3(?:18|3[167]|4[2357]|51)|4(?:18|2[45]|3[12]|5[13]|64|71|9[1-47])|5(?:[1-4]\\d|5[0234])|6(?:3[1-3]|44|7[1-4678])|7(?:17|4[0-4]|6[1-578]|75|8[0-8])|858)\\d{5,6}$/',
+ 'mobile' => '/^9(?:61|[78][1-6]|9[1-5])\\d{6}$/',
+ 'voip' => '/^8700[0-4]\\d{4}$/',
+ 'uan' => '/^[2-9]0\\d{4,7}$/',
+ 'shortcode' => '/^1[1-4]\\d$/',
+ 'emergency' => '/^128|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'uan' => '/^\\d{6,9}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/QA.php b/module/Zend/I18n/src/Validator/PhoneNumber/QA.php
new file mode 100644
index 00000000..f5168ccb
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/QA.php
@@ -0,0 +1,29 @@
+ '974',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-8]\\d{6,7}$/',
+ 'fixed' => '/^4[04]\\d{6}$/',
+ 'mobile' => '/^[3567]\\d{7}$/',
+ 'pager' => '/^2(?:[12]\\d|61)\\d{4}$/',
+ 'tollfree' => '/^800\\d{4}$/',
+ 'shortcode' => '/^(?:1|20|9[27]\\d)\\d{2}$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'pager' => '/^\\d{7}$/',
+ 'shortcode' => '/^\\d{3,4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/RE.php b/module/Zend/I18n/src/Validator/PhoneNumber/RE.php
new file mode 100644
index 00000000..0601677e
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/RE.php
@@ -0,0 +1,28 @@
+ '262',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[268]\\d{8}$/',
+ 'fixed' => '/^262\\d{6}$/',
+ 'mobile' => '/^6(?:9[23]|47)\\d{6}$/',
+ 'tollfree' => '/^80\\d{7}$/',
+ 'premium' => '/^89[1-37-9]\\d{6}$/',
+ 'shared' => '/^8(?:1[019]|2[0156]|84|90)\\d{6}$/',
+ 'emergency' => '/^1(?:12|[578])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/RO.php b/module/Zend/I18n/src/Validator/PhoneNumber/RO.php
new file mode 100644
index 00000000..4dfcf4d8
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/RO.php
@@ -0,0 +1,35 @@
+ '40',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^2\\d{5,8}|[37-9]\\d{8}$/',
+ 'fixed' => '/^2(?:1(?:\\d{7}|9\\d{3})|[3-6](?:\\d{7}|\\d9\\d{2}))|3[13-6]\\d{7}$/',
+ 'mobile' => '/^7[1-8]\\d{7}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'premium' => '/^90[036]\\d{6}$/',
+ 'shared' => '/^801\\d{6}$/',
+ 'personal' => '/^802\\d{6}$/',
+ 'uan' => '/^37\\d{7}$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'shared' => '/^\\d{9}$/',
+ 'personal' => '/^\\d{9}$/',
+ 'uan' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/RS.php b/module/Zend/I18n/src/Validator/PhoneNumber/RS.php
new file mode 100644
index 00000000..e0e72996
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/RS.php
@@ -0,0 +1,34 @@
+ '381',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[126-9]\\d{4,11}|3(?:[0-79]\\d{3,10}|8[2-9]\\d{2,9})$/',
+ 'fixed' => '/^(?:1(?:[02-9][2-9]|1[1-9])\\d|2(?:[0-24-7][2-9]\\d|[389](?:0[2-9]|[2-9]\\d))|3(?:[0-8][2-9]\\d|9(?:[2-9]\\d|0[2-9])))\\d{3,8}$/',
+ 'mobile' => '/^6(?:[0-689]|7\\d)\\d{6,7}$/',
+ 'tollfree' => '/^800\\d{3,9}$/',
+ 'premium' => '/^(?:90[0169]|78\\d)\\d{3,7}$/',
+ 'uan' => '/^7[06]\\d{4,10}$/',
+ 'shortcode' => '/^1(?:1(?:[013-9]|\\d(2,4))|[89]\\d{1,4})$/',
+ 'emergency' => '/^112|9[234]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,12}$/',
+ 'fixed' => '/^\\d{5,12}$/',
+ 'mobile' => '/^\\d{8,10}$/',
+ 'tollfree' => '/^\\d{6,12}$/',
+ 'premium' => '/^\\d{6,12}$/',
+ 'uan' => '/^\\d{6,12}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/RU.php b/module/Zend/I18n/src/Validator/PhoneNumber/RU.php
new file mode 100644
index 00000000..5312d40d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/RU.php
@@ -0,0 +1,26 @@
+ '7',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[3489]\\d{9}$/',
+ 'fixed' => '/^(?:3(?:0[12]|4[1-35-79]|5[1-3]|8[1-58]|9[0145])|4(?:01|1[1356]|2[13467]|7[1-5]|8[1-7]|9[1-689])|8(?:1[1-8]|2[01]|3[13-6]|4[0-8]|5[15]|6[1-35-7]|7[1-37-9]))\\d{7}$/',
+ 'mobile' => '/^9\\d{9}$/',
+ 'tollfree' => '/^80[04]\\d{7}$/',
+ 'premium' => '/^80[39]\\d{7}$/',
+ 'emergency' => '/^0[123]|112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/RW.php b/module/Zend/I18n/src/Validator/PhoneNumber/RW.php
new file mode 100644
index 00000000..54e6a7a2
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/RW.php
@@ -0,0 +1,29 @@
+ '250',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[027-9]\\d{7,8}$/',
+ 'fixed' => '/^2[258]\\d{7}|06\\d{6}$/',
+ 'mobile' => '/^7[238]\\d{7}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'premium' => '/^900\\d{6}$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SA.php b/module/Zend/I18n/src/Validator/PhoneNumber/SA.php
new file mode 100644
index 00000000..94ec68df
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SA.php
@@ -0,0 +1,32 @@
+ '966',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^(?:[1-467]|92)\\d{7}|5\\d{8}|8\\d{9}$/',
+ 'fixed' => '/^(?:[12][24-8]|3[35-8]|4[3-68]|6[2-5]|7[235-7])\\d{6}$/',
+ 'mobile' => '/^(?:5[013-689]\\d|8111)\\d{6}$/',
+ 'tollfree' => '/^800\\d{7}$/',
+ 'uan' => '/^9200\\d{5}$/',
+ 'shortcode' => '/^9(0[24-79]|33|40|66|8[59]|9[02-6])$/',
+ 'emergency' => '/^99[7-9]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,10}$/',
+ 'fixed' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{9,10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'uan' => '/^\\d{9}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SB.php b/module/Zend/I18n/src/Validator/PhoneNumber/SB.php
new file mode 100644
index 00000000..08bf98df
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SB.php
@@ -0,0 +1,31 @@
+ '677',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{4,6}$/',
+ 'fixed' => '/^(?:1[4-79]|[23]\\d|4[01]|5[03]|6[0-37])\\d{3}$/',
+ 'mobile' => '/^48\\d{3}|7(?:[46-8]\\d|5[025-9]|90)\\d{4}|8[4-8]\\d{5}|9(?:[46]\\d|5[0-46-9]|7[0-689]|8[0-79]|9[0-8])\\d{4}$/',
+ 'tollfree' => '/^1[38]\\d{3}$/',
+ 'voip' => '/^5[12]\\d{3}$/',
+ 'shortcode' => '/^1(?:0[02-79]|1[12]|2[0-26]|4[189]|68)|9(?:[01]1|22|33|55|77|88)$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,7}$/',
+ 'fixed' => '/^\\d{5}$/',
+ 'tollfree' => '/^\\d{5}$/',
+ 'voip' => '/^\\d{5}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SC.php b/module/Zend/I18n/src/Validator/PhoneNumber/SC.php
new file mode 100644
index 00000000..1d202d78
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SC.php
@@ -0,0 +1,34 @@
+ '248',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[24689]\\d{5,6}$/',
+ 'fixed' => '/^4[2-46]\\d{5}$/',
+ 'mobile' => '/^2[5-8]\\d{5}$/',
+ 'tollfree' => '/^8000\\d{2}$/',
+ 'premium' => '/^98\\d{4}$/',
+ 'voip' => '/^64\\d{5}$/',
+ 'shortcode' => '/^1(?:0\\d|1[027]|2[0-8]|3[13]|4[0-2]|[59][15]|6[1-9]|7[124-6]|8[158])|96\\d{2}$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,7}$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'mobile' => '/^\\d{7}$/',
+ 'tollfree' => '/^\\d{6}$/',
+ 'premium' => '/^\\d{6}$/',
+ 'voip' => '/^\\d{7}$/',
+ 'shortcode' => '/^\\d{3,4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SD.php b/module/Zend/I18n/src/Validator/PhoneNumber/SD.php
new file mode 100644
index 00000000..41ec1265
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SD.php
@@ -0,0 +1,24 @@
+ '249',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[19]\\d{8}$/',
+ 'fixed' => '/^1(?:[125]\\d|8[3567])\\d{6}$/',
+ 'mobile' => '/^9[012569]\\d{7}$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SE.php b/module/Zend/I18n/src/Validator/PhoneNumber/SE.php
new file mode 100644
index 00000000..04b8aa91
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SE.php
@@ -0,0 +1,36 @@
+ '46',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-9]\\d{6,9}$/',
+ 'fixed' => '/^1(?:0[1-8]\\d{6}|[136]\\d{5,7}|(?:2[0-35]|4[0-4]|5[0-25-9]|7[13-6]|[89]\\d)\\d{5,6})|2(?:[136]\\d{5,7}|(?:2[0-7]|4[0136-8]|5[0138]|7[018]|8[01]|9[0-57])\\d{5,6})|3(?:[356]\\d{5,7}|(?:0[0-4]|1\\d|2[0-25]|4[056]|7[0-2]|8[0-3]|9[023])\\d{5,6})|4(?:[0246]\\d{5,7}|(?:1[0-8]|3[0135]|5[14-79]|7[0-246-9]|8[0156]|9[0-689])\\d{5,6})|5(?:0[0-6]|[15][0-5]|2[0-68]|3[0-4]|4\\d|6[03-5]|7[013]|8[0-79]|9[01])\\d{5,6}|6(?:[03]\\d{5,7}|(?:1[1-3]|2[0-4]|4[02-57]|5[0-37]|6[0-3]|7[0-2]|8[0247]|9[0-356])\\d{5,6})|8\\d{6,8}|9(?:0\\d{5,7}|(?:1[0-68]|2\\d|3[02-59]|[45][0-4]|[68][01]|7[0135-8])\\d{5,6})$/',
+ 'mobile' => '/^7[0236]\\d{7}$/',
+ 'pager' => '/^74\\d{7}$/',
+ 'tollfree' => '/^20\\d{4,7}$/',
+ 'premium' => '/^9(?:00|39|44)\\d{7}$/',
+ 'shared' => '/^77\\d{7}$/',
+ 'personal' => '/^75\\d{7}$/',
+ 'emergency' => '/^112|90000$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,10}$/',
+ 'fixed' => '/^\\d{5,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'pager' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{6,9}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'shared' => '/^\\d{9}$/',
+ 'personal' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3,5}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SG.php b/module/Zend/I18n/src/Validator/PhoneNumber/SG.php
new file mode 100644
index 00000000..51c237b7
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SG.php
@@ -0,0 +1,36 @@
+ '65',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[36]\\d{7}|[17-9]\\d{7,10}$/',
+ 'fixed' => '/^6[1-9]\\d{6}$/',
+ 'mobile' => '/^(?:8[1-7]|9[0-8])\\d{6}$/',
+ 'tollfree' => '/^1?800\\d{7}$/',
+ 'premium' => '/^1900\\d{7}$/',
+ 'voip' => '/^3[12]\\d{6}$/',
+ 'uan' => '/^7000\\d{7}$/',
+ 'shortcode' => '/^1(?:[0136]\\d{2}|[89](?:[1-9]\\d|0[1-9])|[57]\\d{2,3})|99[0246-8]$/',
+ 'emergency' => '/^99[359]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,11}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{10,11}$/',
+ 'premium' => '/^\\d{11}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'uan' => '/^\\d{11}$/',
+ 'shortcode' => '/^\\d{3,5}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SH.php b/module/Zend/I18n/src/Validator/PhoneNumber/SH.php
new file mode 100644
index 00000000..238c42f2
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SH.php
@@ -0,0 +1,26 @@
+ '290',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{3}$/',
+ 'fixed' => '/^(?:[2-468]\\d|7[01])\\d{2}$/',
+ 'premium' => '/^(?:[59]\\d|7[2-9])\\d{2}$/',
+ 'shortcode' => '/^1\\d{2,3}$/',
+ 'emergency' => '/^9(?:11|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4}$/',
+ 'shortcode' => '/^\\d{3,4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SI.php b/module/Zend/I18n/src/Validator/PhoneNumber/SI.php
new file mode 100644
index 00000000..82863579
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SI.php
@@ -0,0 +1,32 @@
+ '386',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-7]\\d{6,7}|[89]\\d{4,7}$/',
+ 'fixed' => '/^(?:1\\d|[25][2-8]|3[4-8]|4[24-8]|7[3-8])\\d{6}$/',
+ 'mobile' => '/^(?:[37][01]|4[019]|51|6[48])\\d{6}$/',
+ 'tollfree' => '/^80\\d{4,6}$/',
+ 'premium' => '/^90\\d{4,6}|89[1-3]\\d{2,5}$/',
+ 'voip' => '/^(?:59|8[1-3])\\d{6}$/',
+ 'emergency' => '/^11[23]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,8}$/',
+ 'fixed' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{6,8}$/',
+ 'premium' => '/^\\d{5,8}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SJ.php b/module/Zend/I18n/src/Validator/PhoneNumber/SJ.php
new file mode 100644
index 00000000..0c99aa31
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SJ.php
@@ -0,0 +1,39 @@
+ '47',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^0\\d{4}|[4789]\\d{7}$/',
+ 'fixed' => '/^79\\d{6}$/',
+ 'mobile' => '/^(?:4[015-8]|5[89]|9\\d)\\d{6}$/',
+ 'tollfree' => '/^80[01]\\d{5}$/',
+ 'premium' => '/^82[09]\\d{5}$/',
+ 'shared' => '/^810(?:0[0-6]|[2-8]\\d)\\d{3}$/',
+ 'personal' => '/^880\\d{5}$/',
+ 'voip' => '/^85[0-5]\\d{5}$/',
+ 'uan' => '/^0\\d{4}|81(?:0(?:0[7-9]|1\\d)|5\\d{2})\\d{3}$/',
+ 'voicemail' => '/^81[23]\\d{5}$/',
+ 'emergency' => '/^11[023]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5}(?:\\d{3})?$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'shared' => '/^\\d{8}$/',
+ 'personal' => '/^\\d{8}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'voicemail' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SK.php b/module/Zend/I18n/src/Validator/PhoneNumber/SK.php
new file mode 100644
index 00000000..1f515539
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SK.php
@@ -0,0 +1,30 @@
+ '421',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-689]\\d{8}$/',
+ 'fixed' => '/^[2-5]\\d{8}$/',
+ 'mobile' => '/^9(?:0[1-8]|1[0-24-9]|4[0489])\\d{6}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'premium' => '/^9(?:[78]\\d{7}|00\\d{6})$/',
+ 'shared' => '/^8[5-9]\\d{7}$/',
+ 'voip' => '/^6(?:5[0-4]|9[0-6])\\d{6}$/',
+ 'uan' => '/^96\\d{7}$/',
+ 'emergency' => '/^1(?:12|5[058])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'uan' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SL.php b/module/Zend/I18n/src/Validator/PhoneNumber/SL.php
new file mode 100644
index 00000000..85ab7e7a
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SL.php
@@ -0,0 +1,24 @@
+ '232',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-578]\\d{7}$/',
+ 'fixed' => '/^[235]2[2-4][2-9]\\d{4}$/',
+ 'mobile' => '/^(?:2[15]|3[034]|4[04]|5[05]|7[6-9]|88)\\d{6}$/',
+ 'emergency' => '/^(?:01|99)9$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SM.php b/module/Zend/I18n/src/Validator/PhoneNumber/SM.php
new file mode 100644
index 00000000..0a0ffdbf
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SM.php
@@ -0,0 +1,29 @@
+ '378',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[05-7]\\d{7,9}$/',
+ 'fixed' => '/^0549(?:8[0157-9]|9\\d)\\d{4}$/',
+ 'mobile' => '/^6[16]\\d{6}$/',
+ 'premium' => '/^7[178]\\d{6}$/',
+ 'voip' => '/^5[158]\\d{6}$/',
+ 'emergency' => '/^11[358]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,10}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'premium' => '/^\\d{8}$/',
+ 'voip' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SN.php b/module/Zend/I18n/src/Validator/PhoneNumber/SN.php
new file mode 100644
index 00000000..87d83ce3
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SN.php
@@ -0,0 +1,23 @@
+ '221',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[37]\\d{8}$/',
+ 'fixed' => '/^3(?:0(?:1[01]|80)|3(?:8[1-9]|9[2-9]))\\d{5}$/',
+ 'mobile' => '/^7(?:0(?:[01279]0|3[03]|4[05]|5[06]|6[03-5]|8[029])|6(?:1[23]|2[89]|3[3489]|4[6-9]|5\\d|6[3-9]|7[45]|8[3-8])|7\\d{2}|8(?:01|1[01]))\\d{5}$/',
+ 'voip' => '/^33301\\d{4}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SO.php b/module/Zend/I18n/src/Validator/PhoneNumber/SO.php
new file mode 100644
index 00000000..d2c24e44
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SO.php
@@ -0,0 +1,23 @@
+ '252',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-79]\\d{6,8}$/',
+ 'fixed' => '/^(?:[134]\\d|2[0-79]|5[57-9])\\d{5}$/',
+ 'mobile' => '/^(?:15\\d|2(?:4\\d|8)|6[17-9]?\\d{2}|7\\d{2}|9[01]\\d)\\d{5}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'fixed' => '/^\\d{7}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SR.php b/module/Zend/I18n/src/Validator/PhoneNumber/SR.php
new file mode 100644
index 00000000..14307391
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SR.php
@@ -0,0 +1,29 @@
+ '597',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-8]\\d{5,6}$/',
+ 'fixed' => '/^(?:2[1-3]|3[0-7]|4\\d|5[2-58]|68\\d)\\d{4}$/',
+ 'mobile' => '/^(?:7[1-57]|8[1-9])\\d{5}$/',
+ 'voip' => '/^56\\d{4}$/',
+ 'shortcode' => '/^1(?:[02-9]\\d|1[0-46-9]|\\d{3})$/',
+ 'emergency' => '/^115$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,7}$/',
+ 'mobile' => '/^\\d{7}$/',
+ 'voip' => '/^\\d{6}$/',
+ 'shortcode' => '/^\\d{3,4}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SS.php b/module/Zend/I18n/src/Validator/PhoneNumber/SS.php
new file mode 100644
index 00000000..37c0c04d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SS.php
@@ -0,0 +1,22 @@
+ '211',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[19]\\d{8}$/',
+ 'fixed' => '/^18\\d{7}$/',
+ 'mobile' => '/^(?:12|9[1257])\\d{7}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/ST.php b/module/Zend/I18n/src/Validator/PhoneNumber/ST.php
new file mode 100644
index 00000000..59916bc7
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/ST.php
@@ -0,0 +1,24 @@
+ '239',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[29]\\d{6}$/',
+ 'fixed' => '/^22\\d{5}$/',
+ 'mobile' => '/^9[89]\\d{5}$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SV.php b/module/Zend/I18n/src/Validator/PhoneNumber/SV.php
new file mode 100644
index 00000000..91c47e34
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SV.php
@@ -0,0 +1,30 @@
+ '503',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[267]\\d{7}|[89]\\d{6}(?:\\d{4})?$/',
+ 'fixed' => '/^2[1-6]\\d{6}$/',
+ 'mobile' => '/^[67]\\d{7}$/',
+ 'tollfree' => '/^800\\d{4}(?:\\d{4})?$/',
+ 'premium' => '/^900\\d{4}(?:\\d{4})?$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}|\\d{11}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{7}(?:\\d{4})?$/',
+ 'premium' => '/^\\d{7}(?:\\d{4})?$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SX.php b/module/Zend/I18n/src/Validator/PhoneNumber/SX.php
new file mode 100644
index 00000000..4e3a0d50
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SX.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5789]\\d{9}$/',
+ 'fixed' => '/^7215(?:4[2-8]|8[239]|9[056])\\d{4}$/',
+ 'mobile' => '/^7215(?:1[02]|2\\d|5[034679]|8[014-8])\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^919$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SY.php b/module/Zend/I18n/src/Validator/PhoneNumber/SY.php
new file mode 100644
index 00000000..6e0eb477
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SY.php
@@ -0,0 +1,25 @@
+ '963',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-59]\\d{7,8}$/',
+ 'fixed' => '/^(?:1(?:1\\d?|4\\d|[2356])|2[1-35]|3(?:[13]\\d|4)|4[13]|5[1-3])\\d{6}$/',
+ 'mobile' => '/^9(?:22|[35][0-8]|4\\d|6[024-9]|88|9[0-489])\\d{6}$/',
+ 'emergency' => '/^11[023]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/SZ.php b/module/Zend/I18n/src/Validator/PhoneNumber/SZ.php
new file mode 100644
index 00000000..06d118dc
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/SZ.php
@@ -0,0 +1,25 @@
+ '268',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[027]\\d{7}$/',
+ 'fixed' => '/^2(?:2(?:0[07]|[13]7|2[57])|3(?:0[34]|[1278]3|3[23]|[46][34])|(?:40[4-69]|67)|5(?:0[5-7]|1[6-9]|[23][78]|48|5[01]))\\d{4}$/',
+ 'mobile' => '/^7[6-8]\\d{6}$/',
+ 'tollfree' => '/^0800\\d{4}$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TC.php b/module/Zend/I18n/src/Validator/PhoneNumber/TC.php
new file mode 100644
index 00000000..901d50c3
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TC.php
@@ -0,0 +1,33 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5689]\\d{9}$/',
+ 'fixed' => '/^649(?:712|9(?:4\\d|50))\\d{4}$/',
+ 'mobile' => '/^649(?:2(?:3[129]|4[1-7])|3(?:3[1-39]|4[1-7])|4[34][12])\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'voip' => '/^64971[01]\\d{4}$/',
+ 'emergency' => '/^9(?:11|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TD.php b/module/Zend/I18n/src/Validator/PhoneNumber/TD.php
new file mode 100644
index 00000000..2260ad1f
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TD.php
@@ -0,0 +1,24 @@
+ '235',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2679]\\d{7}$/',
+ 'fixed' => '/^22(?:[3789]0|5[0-5]|6[89])\\d{4}$/',
+ 'mobile' => '/^(?:6[36]\\d|77\\d|9(?:5[0-4]|9\\d))\\d{5}$/',
+ 'emergency' => '/^1[78]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TG.php b/module/Zend/I18n/src/Validator/PhoneNumber/TG.php
new file mode 100644
index 00000000..e8483003
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TG.php
@@ -0,0 +1,24 @@
+ '228',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[29]\\d{7}$/',
+ 'fixed' => '/^2(?:2[2-7]|3[23]|44|55|66|77)\\d{5}$/',
+ 'mobile' => '/^9[0-289]\\d{6}$/',
+ 'emergency' => '/^1(?:01|1[78]|7[17])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TH.php b/module/Zend/I18n/src/Validator/PhoneNumber/TH.php
new file mode 100644
index 00000000..67fb8060
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TH.php
@@ -0,0 +1,34 @@
+ '66',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{7,8}|1\\d{3}(?:\\d{6})?$/',
+ 'fixed' => '/^(?:2[1-9]|3[2-9]|4[2-5]|5[2-6]|7[3-7])\\d{6}$/',
+ 'mobile' => '/^[89]\\d{8}$/',
+ 'tollfree' => '/^1800\\d{6}$/',
+ 'premium' => '/^1900\\d{6}$/',
+ 'voip' => '/^60\\d{7}$/',
+ 'uan' => '/^1\\d{3}$/',
+ 'emergency' => '/^1(?:669|9[19])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4}|\\d{8,10}$/',
+ 'fixed' => '/^\\d{8}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'uan' => '/^\\d{4}$/',
+ 'emergency' => '/^\\d{3,4}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TJ.php b/module/Zend/I18n/src/Validator/PhoneNumber/TJ.php
new file mode 100644
index 00000000..01ec084b
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TJ.php
@@ -0,0 +1,25 @@
+ '992',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[3-59]\\d{8}$/',
+ 'fixed' => '/^(?:3(?:1[3-5]|2[245]|3[12]|4[24-7]|5[25]|72)|4(?:46|74|87))\\d{6}$/',
+ 'mobile' => '/^(?:505|9[0-35-9]\\d)\\d{6}$/',
+ 'emergency' => '/^1(?:0[1-3]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{3,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TK.php b/module/Zend/I18n/src/Validator/PhoneNumber/TK.php
new file mode 100644
index 00000000..812683a9
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TK.php
@@ -0,0 +1,22 @@
+ '690',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-5]\\d{3}$/',
+ 'fixed' => '/^[2-4]\\d{3}$/',
+ 'mobile' => '/^5\\d{3}$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{4}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TL.php b/module/Zend/I18n/src/Validator/PhoneNumber/TL.php
new file mode 100644
index 00000000..a61fe5f9
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TL.php
@@ -0,0 +1,34 @@
+ '670',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-489]\\d{6}|7\\d{6,7}$/',
+ 'fixed' => '/^(?:2[1-5]|3[1-9]|4[1-4])\\d{5}$/',
+ 'mobile' => '/^7[78]\\d{6}$/',
+ 'tollfree' => '/^80\\d{5}$/',
+ 'premium' => '/^90\\d{5}$/',
+ 'personal' => '/^70\\d{5}$/',
+ 'shortcode' => '/^1(?:0[02]|2[0138]|72|9[07])$/',
+ 'emergency' => '/^11[25]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'fixed' => '/^\\d{7}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{7}$/',
+ 'premium' => '/^\\d{7}$/',
+ 'personal' => '/^\\d{7}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TM.php b/module/Zend/I18n/src/Validator/PhoneNumber/TM.php
new file mode 100644
index 00000000..3fe14d73
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TM.php
@@ -0,0 +1,24 @@
+ '993',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-6]\\d{7}$/',
+ 'fixed' => '/^(?:1(?:2\\d|3[1-9])|2(?:22|4[0-35-8])|3(?:22|4[03-9])|4(?:22|3[128]|4\\d|6[15])|5(?:22|5[7-9]|6[014-689]))\\d{5}$/',
+ 'mobile' => '/^6[3-8]\\d{6}$/',
+ 'emergency' => '/^0[1-3]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TN.php b/module/Zend/I18n/src/Validator/PhoneNumber/TN.php
new file mode 100644
index 00000000..6f7a2c36
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TN.php
@@ -0,0 +1,25 @@
+ '216',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-57-9]\\d{7}$/',
+ 'fixed' => '/^(?:3[012]|7\\d)\\d{6}$/',
+ 'mobile' => '/^(?:[259]\\d|4[0-2])\\d{6}$/',
+ 'premium' => '/^8[0128]\\d{6}$/',
+ 'emergency' => '/^19[078]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TO.php b/module/Zend/I18n/src/Validator/PhoneNumber/TO.php
new file mode 100644
index 00000000..bb962b36
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TO.php
@@ -0,0 +1,28 @@
+ '676',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[02-8]\\d{4,6}$/',
+ 'fixed' => '/^(?:2\\d|3[1-8]|4[1-4]|[56]0|7[0149]|8[05])\\d{3}$/',
+ 'mobile' => '/^(?:7[578]|8[7-9])\\d{5}$/',
+ 'tollfree' => '/^0800\\d{3}$/',
+ 'emergency' => '/^9(?:11|22|33|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,7}$/',
+ 'fixed' => '/^\\d{5}$/',
+ 'mobile' => '/^\\d{7}$/',
+ 'tollfree' => '/^\\d{7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TR.php b/module/Zend/I18n/src/Validator/PhoneNumber/TR.php
new file mode 100644
index 00000000..59a558f1
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TR.php
@@ -0,0 +1,34 @@
+ '90',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-589]\\d{9}|444\\d{4}$/',
+ 'fixed' => '/^(?:2(?:[13][26]|[28][2468]|[45][268]|[67][246])|3(?:[13][28]|[24-6][2468]|[78][02468]|92)|4(?:[16][246]|[23578][2468]|4[26]))\\d{7}$/',
+ 'mobile' => '/^5(?:0[1-7]|22|[34]\\d|5[1-59]|9[246])\\d{7}$/',
+ 'pager' => '/^512\\d{7}$/',
+ 'tollfree' => '/^800\\d{7}$/',
+ 'premium' => '/^900\\d{7}$/',
+ 'uan' => '/^444\\d{4}|850\\d{7}$/',
+ 'emergency' => '/^1(?:1[02]|55)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,10}$/',
+ 'fixed' => '/^\\d{10}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'pager' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'uan' => '/^\\d{7,10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TT.php b/module/Zend/I18n/src/Validator/PhoneNumber/TT.php
new file mode 100644
index 00000000..65858cf7
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TT.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[589]\\d{9}$/',
+ 'fixed' => '/^868(?:2(?:01|2[1-5])|6(?:07|1[4-6]|2[1-9]|[3-6]\\d|7[0-79]|9[0-8])|82[12])\\d{4}$/',
+ 'mobile' => '/^868(?:29\\d|3(?:0[1-9]|1[02-9]|[2-9]\\d)|4(?:[679]\\d|8[0-4])|6(?:20|78|8\\d)|7(?:03|1[02-9]|[2-9]\\d))\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^99[09]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TV.php b/module/Zend/I18n/src/Validator/PhoneNumber/TV.php
new file mode 100644
index 00000000..bdc0aefa
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TV.php
@@ -0,0 +1,26 @@
+ '688',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[29]\\d{4,5}$/',
+ 'fixed' => '/^2[02-9]\\d{3}$/',
+ 'mobile' => '/^90\\d{4}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,6}$/',
+ 'fixed' => '/^\\d{5}$/',
+ 'mobile' => '/^\\d{6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TW.php b/module/Zend/I18n/src/Validator/PhoneNumber/TW.php
new file mode 100644
index 00000000..88ad6693
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TW.php
@@ -0,0 +1,30 @@
+ '886',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{7,8}$/',
+ 'fixed' => '/^[2-8]\\d{7,8}$/',
+ 'mobile' => '/^9\\d{8}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'premium' => '/^900\\d{6}$/',
+ 'emergency' => '/^11[029]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{8,9}$/',
+ 'fixed' => '/^\\d{8,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/TZ.php b/module/Zend/I18n/src/Validator/PhoneNumber/TZ.php
new file mode 100644
index 00000000..58d60179
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/TZ.php
@@ -0,0 +1,34 @@
+ '255',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^\\d{9}$/',
+ 'fixed' => '/^2[2-8]\\d{7}$/',
+ 'mobile' => '/^(?:6[158]|7[1-9])\\d{7}$/',
+ 'tollfree' => '/^80[08]\\d{6}$/',
+ 'premium' => '/^90\\d{7}$/',
+ 'shared' => '/^8(?:40|6[01])\\d{6}$/',
+ 'voip' => '/^41\\d{7}$/',
+ 'emergency' => '/^11[12]|999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'fixed' => '/^\\d{7,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'shared' => '/^\\d{9}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/UA.php b/module/Zend/I18n/src/Validator/PhoneNumber/UA.php
new file mode 100644
index 00000000..56d9d890
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/UA.php
@@ -0,0 +1,29 @@
+ '380',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[3-689]\\d{8}$/',
+ 'fixed' => '/^(?:3[1-8]|4[13-8]|5[1-7]|6[12459])\\d{7}$/',
+ 'mobile' => '/^(?:39|50|6[36-8]|9[1-9])\\d{7}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'premium' => '/^900\\d{6}$/',
+ 'emergency' => '/^1(?:0[123]|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/UG.php b/module/Zend/I18n/src/Validator/PhoneNumber/UG.php
new file mode 100644
index 00000000..b845c20d
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/UG.php
@@ -0,0 +1,30 @@
+ '256',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^\\d{9}$/',
+ 'fixed' => '/^20(?:[014]\\d{2}|2(?:40|[5-9]\\d)|3[23]\\d|5[0-4]\\d)\\d{4}|[34]\\d{8}$/',
+ 'mobile' => '/^7(?:0[0-7]|[15789]\\d|20|[46][0-4])\\d{6}$/',
+ 'tollfree' => '/^800[123]\\d{5}$/',
+ 'premium' => '/^90[123]\\d{6}$/',
+ 'emergency' => '/^999$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,9}$/',
+ 'fixed' => '/^\\d{5,9}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/US.php b/module/Zend/I18n/src/Validator/PhoneNumber/US.php
new file mode 100644
index 00000000..d87cc894
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/US.php
@@ -0,0 +1,30 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-9]\\d{9}$/',
+ 'fixed' => '/^(?:2(?:0[1-35-9]|1[02-9]|2[4589]|3[149]|4[08]|5[1-46]|6[0279]|7[06]|8[13])|3(?:0[1-57-9]|1[02-9]|2[0135]|3[014679]|47|5[12]|6[01]|8[056])|4(?:0[124-9]|1[02-579]|2[3-5]|3[0245]|4[0235]|58|69|7[0589]|8[04])|5(?:0[1-57-9]|1[0235-8]|20|3[0149]|4[01]|5[19]|6[1-37]|7[013-5]|8[056])|6(?:0[1-35-9]|1[024-9]|2[036]|3[016]|4[16]|5[017]|6[0-279]|78|8[12])|7(?:0[1-46-8]|1[02-9]|2[047]|3[124]|4[07]|5[47]|6[02359]|7[02-59]|8[156])|8(?:0[1-68]|1[02-8]|28|3[0-25]|4[3578]|5[06-9]|6[02-5]|7[028])|9(?:0[1346-9]|1[02-9]|2[0589]|3[1678]|4[0179]|5[1246]|7[0-3589]|8[0459]))[2-9]\\d{6}$/',
+ 'mobile' => '/^(?:2(?:0[1-35-9]|1[02-9]|2[4589]|3[149]|4[08]|5[1-46]|6[0279]|7[06]|8[13])|3(?:0[1-57-9]|1[02-9]|2[0135]|3[014679]|47|5[12]|6[01]|8[056])|4(?:0[124-9]|1[02-579]|2[3-5]|3[0245]|4[0235]|58|69|7[0589]|8[04])|5(?:0[1-57-9]|1[0235-8]|20|3[0149]|4[01]|5[19]|6[1-37]|7[013-5]|8[056])|6(?:0[1-35-9]|1[024-9]|2[036]|3[016]|4[16]|5[017]|6[0-279]|78|8[12])|7(?:0[1-46-8]|1[02-9]|2[047]|3[124]|4[07]|5[47]|6[02359]|7[02-59]|8[156])|8(?:0[1-68]|1[02-8]|28|3[0-25]|4[3578]|5[06-9]|6[02-5]|7[028])|9(?:0[1346-9]|1[02-9]|2[0589]|3[1678]|4[0179]|5[1246]|7[0-3589]|8[0459]))[2-9]\\d{6}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^112|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/UY.php b/module/Zend/I18n/src/Validator/PhoneNumber/UY.php
new file mode 100644
index 00000000..f056e875
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/UY.php
@@ -0,0 +1,31 @@
+ '598',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2489]\\d{6,7}$/',
+ 'fixed' => '/^2\\d{7}|4[2-7]\\d{6}$/',
+ 'mobile' => '/^9[13-9]\\d{6}$/',
+ 'tollfree' => '/^80[05]\\d{4}$/',
+ 'premium' => '/^90[0-8]\\d{4}$/',
+ 'shortcode' => '/^1(?:0[4-9]|1[2368]|2[0-3568])$/',
+ 'emergency' => '/^128|911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,8}$/',
+ 'mobile' => '/^\\d{8}$/',
+ 'tollfree' => '/^\\d{7}$/',
+ 'premium' => '/^\\d{7}$/',
+ 'shortcode' => '/^\\d{3}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/UZ.php b/module/Zend/I18n/src/Validator/PhoneNumber/UZ.php
new file mode 100644
index 00000000..b4dee294
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/UZ.php
@@ -0,0 +1,24 @@
+ '998',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[679]\\d{8}$/',
+ 'fixed' => '/^(?:6(?:1(?:22|3[124]|4[1-4]|5[123578]|64)|2(?:22|3[0-57-9]|41)|5(?:22|3[3-7]|5[024-8])|6\\d{2}|7(?:[23]\\d|7[69])|9(?:22|4[1-8]|6[135]))|7(?:0(?:5[4-9]|6[0146]|7[12456]|9[135-8])|1[12]\\d|2(?:22|3[1345789]|4[123579]|5[14])|3(?:2\\d|3[1578]|4[1-35-7]|5[1-57]|61)|4(?:2\\d|3[1-579]|7[1-79])|5(?:22|5[1-9]|6[1457])|6(?:22|3[12457]|4[13-8])|9(?:22|5[1-9])))\\d{5}$/',
+ 'mobile' => '/^6(?:1(?:2(?:98|2[01])|35[0-4]|50\\d|61[23]|7(?:[01][017]|4\\d|55|9[5-9]))|2(?:11\\d|2(?:[12]1|9[01379])|5(?:[126]\\d|3[0-4])|7\\d{2})|5(?:19[01]|2(?:27|9[26])|30\\d|59\\d|7\\d{2})|6(?:2(?:1[5-9]|2[0367]|38|41|52|60)|3[79]\\d|4(?:56|83)|7(?:[07]\\d|1[017]|3[07]|4[047]|5[057]|67|8[0178]|9[79])|9[0-3]\\d)|7(?:2(?:24|3[237]|4[5-9]|7[15-8])|5(?:7[12]|8[0589])|7(?:0\\d|[39][07])|9(?:0\\d|7[079]))|9(2(?:1[1267]|5\\d|3[01]|7[0-4])|5[67]\\d|6(?:2[0-26]|8\\d)|7\\d{2}))\\d{4}|7(?:0\\d{3}|1(?:13[01]|6(?:0[47]|1[67]|66)|71[3-69]|98\\d)|2(?:2(?:2[79]|95)|3(?:2[5-9]|6[0-6])|57\\d|7(?:0\\d|1[17]|2[27]|3[37]|44|5[057]|66|88))|3(?:2(?:1[0-6]|21|3[469]|7[159])|33\\d|5(?:0[0-4]|5[579]|9\\d)|7(?:[0-3579]\\d|4[0467]|6[67]|8[078])|9[4-6]\\d)|4(?:2(?:29|5[0257]|6[0-7]|7[1-57])|5(?:1[0-4]|8\\d|9[5-9])|7(?:0\\d|1[024589]|2[0127]|3[0137]|[46][07]|5[01]|7[5-9]|9[079])|9(?:7[015-9]|[89]\\d))|5(?:112|2(?:0\\d|2[29]|[49]4)|3[1568]\\d|52[6-9]|7(?:0[01578]|1[017]|[23]7|4[047]|[5-7]\\d|8[78]|9[079]))|6(?:2(?:2[1245]|4[2-4])|39\\d|41[179]|5(?:[349]\\d|5[0-2])|7(?:0[017]|[13]\\d|22|44|55|67|88))|9(?:22[128]|3(?:2[0-4]|7\\d)|57[05629]|7(?:2[05-9]|3[37]|4\\d|60|7[2579]|87|9[07])))\\d{4}|9[0-57-9]\\d{7}$/',
+ 'emergency' => '/^0(?:0[123]|[123]|50)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,9}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/VA.php b/module/Zend/I18n/src/Validator/PhoneNumber/VA.php
new file mode 100644
index 00000000..dbfb8dc7
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/VA.php
@@ -0,0 +1,25 @@
+ '379',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^06\\d{8}$/',
+ 'fixed' => '/^06698\\d{5}$/',
+ 'mobile' => '/^N/A$/',
+ 'emergency' => '/^11[2358]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{10}$/',
+ 'mobile' => '/^N/A$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/VC.php b/module/Zend/I18n/src/Validator/PhoneNumber/VC.php
new file mode 100644
index 00000000..a78fbcf7
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/VC.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5789]\\d{9}$/',
+ 'fixed' => '/^784(?:266|3(?:6[6-9]|7\\d|8[0-24-6])|4(?:38|5[0-36-8]|8\\d|9[01])|555|638|784)\\d{4}$/',
+ 'mobile' => '/^784(?:4(?:3[0-4]|5[45]|9[2-5])|5(?:2[6-9]|3[0-4]|93))\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^9(?:11|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/VE.php b/module/Zend/I18n/src/Validator/PhoneNumber/VE.php
new file mode 100644
index 00000000..01dc3f36
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/VE.php
@@ -0,0 +1,29 @@
+ '58',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[24589]\\d{9}$/',
+ 'fixed' => '/^(?:2(?:12|3[457-9]|[58][1-9]|[467]\\d|9[1-6])|50[01])\\d{7}$/',
+ 'mobile' => '/^4(?:1[24-8]|2[46])\\d{7}$/',
+ 'tollfree' => '/^800\\d{7}$/',
+ 'premium' => '/^900\\d{7}$/',
+ 'emergency' => '/^171$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,10}$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/VG.php b/module/Zend/I18n/src/Validator/PhoneNumber/VG.php
new file mode 100644
index 00000000..ae844d05
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/VG.php
@@ -0,0 +1,31 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2589]\\d{9}$/',
+ 'fixed' => '/^284(?:(?:229|4(?:22|9[45])|774|8(?:52|6[459]))\\d{4}|496[0-5]\\d{3})$/',
+ 'mobile' => '/^284(?:(?:3(?:0[0-3]|4[0-367])|4(?:4[0-6]|68|99)|54[0-57])\\d{4}|496[6-9]\\d{3})$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^9(?:11|99)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'mobile' => '/^\\d{10}$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/VI.php b/module/Zend/I18n/src/Validator/PhoneNumber/VI.php
new file mode 100644
index 00000000..aa8df88b
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/VI.php
@@ -0,0 +1,30 @@
+ '1',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[3589]\\d{9}$/',
+ 'fixed' => '/^340(?:2(?:01|2[067]|36|44|77)|3(?:32|44)|4(?:4[38]|7[34])|5(?:1[34]|55)|6(?:26|4[23]|9[023])|7(?:[17]\\d|27)|884|998)\\d{4}$/',
+ 'mobile' => '/^340(?:2(?:01|2[067]|36|44|77)|3(?:32|44)|4(?:4[38]|7[34])|5(?:1[34]|55)|6(?:26|4[23]|9[023])|7(?:[17]\\d|27)|884|998)\\d{4}$/',
+ 'tollfree' => '/^8(?:00|55|66|77|88)[2-9]\\d{6}$/',
+ 'premium' => '/^900[2-9]\\d{6}$/',
+ 'personal' => '/^5(?:00|33|44)[2-9]\\d{6}$/',
+ 'emergency' => '/^911$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7}(?:\\d{3})?$/',
+ 'tollfree' => '/^\\d{10}$/',
+ 'premium' => '/^\\d{10}$/',
+ 'personal' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/VN.php b/module/Zend/I18n/src/Validator/PhoneNumber/VN.php
new file mode 100644
index 00000000..2a4e54f8
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/VN.php
@@ -0,0 +1,32 @@
+ '84',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[17]\\d{6,9}|[2-69]\\d{7,9}|8\\d{6,8}$/',
+ 'fixed' => '/^(?:2(?:[025-79]|1[0189]|[348][01])|3(?:[0136-9]|[25][01])|4\\d|5(?:[01][01]|[2-9])|6(?:[0-46-8]|5[01])|7(?:[02-79]|[18][01])|8[1-9])\\d{7}$/',
+ 'mobile' => '/^(?:9\\d|1(?:2\\d|6[2-9]|8[68]|99))\\d{7}$/',
+ 'tollfree' => '/^1800\\d{4,6}$/',
+ 'premium' => '/^1900\\d{4,6}$/',
+ 'uan' => '/^[17]99\\d{4}|69\\d{5,6}|80\\d{5}$/',
+ 'emergency' => '/^11[345]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{7,10}$/',
+ 'fixed' => '/^\\d{9,10}$/',
+ 'mobile' => '/^\\d{9,10}$/',
+ 'tollfree' => '/^\\d{8,10}$/',
+ 'premium' => '/^\\d{8,10}$/',
+ 'uan' => '/^\\d{7,8}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/VU.php b/module/Zend/I18n/src/Validator/PhoneNumber/VU.php
new file mode 100644
index 00000000..ad94eaee
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/VU.php
@@ -0,0 +1,28 @@
+ '678',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-57-9]\\d{4,6}$/',
+ 'fixed' => '/^(?:2[2-9]\\d|3(?:[5-7]\\d|8[0-8])|48[4-9]|88\\d)\\d{2}$/',
+ 'mobile' => '/^(?:5(?:7[2-5]|[3-69]\\d)|7[013-7]\\d)\\d{4}$/',
+ 'uan' => '/^3[03]\\d{3}|900\\d{4}$/',
+ 'emergency' => '/^112$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,7}$/',
+ 'fixed' => '/^\\d{5}$/',
+ 'mobile' => '/^\\d{7}$/',
+ 'uan' => '/^\\d{5,7}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/WF.php b/module/Zend/I18n/src/Validator/PhoneNumber/WF.php
new file mode 100644
index 00000000..4fe44d48
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/WF.php
@@ -0,0 +1,24 @@
+ '681',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[5-7]\\d{5}$/',
+ 'fixed' => '/^(?:50|68|72)\\d{4}$/',
+ 'mobile' => '/^(?:50|68|72)\\d{4}$/',
+ 'emergency' => '/^1[578]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6}$/',
+ 'emergency' => '/^\\d{2}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/WS.php b/module/Zend/I18n/src/Validator/PhoneNumber/WS.php
new file mode 100644
index 00000000..f93523e6
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/WS.php
@@ -0,0 +1,28 @@
+ '685',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[2-8]\\d{4,6}$/',
+ 'fixed' => '/^(?:[2-5]\\d|6[1-9]|84\\d{2})\\d{3}$/',
+ 'mobile' => '/^(?:60|7[25-7]\\d)\\d{4}$/',
+ 'tollfree' => '/^800\\d{3}$/',
+ 'emergency' => '/^99[4-6]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,7}$/',
+ 'fixed' => '/^\\d{5,7}$/',
+ 'mobile' => '/^\\d{6,7}$/',
+ 'tollfree' => '/^\\d{6}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/XK.php b/module/Zend/I18n/src/Validator/PhoneNumber/XK.php
new file mode 100644
index 00000000..85bb0ef5
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/XK.php
@@ -0,0 +1,34 @@
+ '383',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[126-9]\\d{4,11}|3(?:[0-79]\\d{3,10}|8[2-9]\\d{2,9})$/',
+ 'fixed' => '/^(?:1(?:[02-9][2-9]|1[1-9])\\d|2(?:[0-24-7][2-9]\\d|[389](?:0[2-9]|[2-9]\\d))|3(?:[0-8][2-9]\\d|9(?:[2-9]\\d|0[2-9])))\\d{3,8}$/',
+ 'mobile' => '/^6(?:[0-689]|7\\d)\\d{6,7}$/',
+ 'tollfree' => '/^800\\d{3,9}$/',
+ 'premium' => '/^(?:90[0169]|78\\d)\\d{3,7}$/',
+ 'uan' => '/^7[06]\\d{4,10}$/',
+ 'shortcode' => '/^1(?:1(?:[013-9]|\\d(2,4))|[89]\\d{1,4})$/',
+ 'emergency' => '/^112|9[234]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,12}$/',
+ 'fixed' => '/^\\d{5,12}$/',
+ 'mobile' => '/^\\d{8,10}$/',
+ 'tollfree' => '/^\\d{6,12}$/',
+ 'premium' => '/^\\d{6,12}$/',
+ 'uan' => '/^\\d{6,12}$/',
+ 'shortcode' => '/^\\d{3,6}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/YE.php b/module/Zend/I18n/src/Validator/PhoneNumber/YE.php
new file mode 100644
index 00000000..fad5f7ce
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/YE.php
@@ -0,0 +1,26 @@
+ '967',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-7]\\d{6,8}$/',
+ 'fixed' => '/^(?:1(?:7\\d|[2-68])|2[2-68]|3[2358]|4[2-58]|5[2-6]|6[3-58]|7[24-68])\\d{5}$/',
+ 'mobile' => '/^7[0137]\\d{7}$/',
+ 'emergency' => '/^19[1459]$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{6,9}$/',
+ 'fixed' => '/^\\d{6,8}$/',
+ 'mobile' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/YT.php b/module/Zend/I18n/src/Validator/PhoneNumber/YT.php
new file mode 100644
index 00000000..1f70705a
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/YT.php
@@ -0,0 +1,25 @@
+ '262',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[268]\\d{8}$/',
+ 'fixed' => '/^2696[0-4]\\d{4}$/',
+ 'mobile' => '/^639\\d{6}$/',
+ 'tollfree' => '/^80\\d{7}$/',
+ 'emergency' => '/^1(?:12|5)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{2,3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/ZA.php b/module/Zend/I18n/src/Validator/PhoneNumber/ZA.php
new file mode 100644
index 00000000..a64baac8
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/ZA.php
@@ -0,0 +1,35 @@
+ '27',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[1-79]\\d{8}|8(?:[067]\\d{7}|[1-4]\\d{3,7})$/',
+ 'fixed' => '/^(?:1[0-8]|2[0-378]|3[1-69]|4\\d|5[1346-8])\\d{7}$/',
+ 'mobile' => '/^(?:6[0-5]|7[0-46-9])\\d{7}|8[1-4]\\d{3,7}$/',
+ 'tollfree' => '/^80\\d{7}$/',
+ 'premium' => '/^86[2-9]\\d{6}|90\\d{7}$/',
+ 'shared' => '/^860\\d{6}$/',
+ 'voip' => '/^87\\d{7}$/',
+ 'uan' => '/^861\\d{6}$/',
+ 'emergency' => '/^1(?:01(?:11|77)|12)$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{5,9}$/',
+ 'fixed' => '/^\\d{9}$/',
+ 'tollfree' => '/^\\d{9}$/',
+ 'premium' => '/^\\d{9}$/',
+ 'shared' => '/^\\d{9}$/',
+ 'voip' => '/^\\d{9}$/',
+ 'uan' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3,5}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/ZM.php b/module/Zend/I18n/src/Validator/PhoneNumber/ZM.php
new file mode 100644
index 00000000..c7a8c546
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/ZM.php
@@ -0,0 +1,25 @@
+ '260',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^[289]\\d{8}$/',
+ 'fixed' => '/^21[1-8]\\d{6}$/',
+ 'mobile' => '/^9(?:5[05]|6\\d|7[13-9])\\d{6}$/',
+ 'tollfree' => '/^800\\d{6}$/',
+ 'emergency' => '/^(?:112|99[139])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{9}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PhoneNumber/ZW.php b/module/Zend/I18n/src/Validator/PhoneNumber/ZW.php
new file mode 100644
index 00000000..693873db
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PhoneNumber/ZW.php
@@ -0,0 +1,27 @@
+ '263',
+ 'patterns' => [
+ 'national' => [
+ 'general' => '/^2(?:[012457-9]\\d{3,8}|6\\d{3,6})|[13-79]\\d{4,8}|86\\d{8}$/',
+ 'fixed' => '/^(?:1[3-9]|2(?:0[45]|[16]|2[28]|[49]8?|58[23]|7[246]|8[1346-9])|3(?:08?|17?|3[78]|[2456]|7[1569]|8[379])|5(?:[07-9]|1[78]|483|5(?:7?|8))|6(?:0|28|37?|[45][68][78]|98?)|848)\\d{3,6}|(?:2(?:27|5|7[135789]|8[25])|3[39]|5[1-46]|6[126-8])\\d{4,6}|2(?:0|70)\\d{5,6}|(?:4\\d|9[2-8])\\d{4,7}$/',
+ 'mobile' => '/^7[137]\\d{7}|86(?:22|44)\\d{6}$/',
+ 'voip' => '/^86(?:1[12]|30|8[367]|99)\\d{6}$/',
+ 'emergency' => '/^(?:112|99[3459])$/',
+ ],
+ 'possible' => [
+ 'general' => '/^\\d{3,10}$/',
+ 'mobile' => '/^\\d{9,10}$/',
+ 'voip' => '/^\\d{10}$/',
+ 'emergency' => '/^\\d{3}$/',
+ ],
+ ],
+];
diff --git a/module/Zend/I18n/src/Validator/PostCode.php b/module/Zend/I18n/src/Validator/PostCode.php
new file mode 100644
index 00000000..7af07dd8
--- /dev/null
+++ b/module/Zend/I18n/src/Validator/PostCode.php
@@ -0,0 +1,396 @@
+ "Invalid type given. String or integer expected",
+ self::NO_MATCH => "The input does not appear to be a postal code",
+ self::SERVICE => "The input does not appear to be a postal code",
+ self::SERVICEFAILURE => "An exception has been raised while validating the input",
+ ];
+
+ /**
+ * Optional Locale to use
+ *
+ * @var string|null
+ */
+ protected $locale;
+
+ /**
+ * Optional Manual postal code format
+ *
+ * @var string|null
+ */
+ protected $format;
+
+ /**
+ * Optional Service callback for additional validation
+ *
+ * @var mixed|null
+ */
+ protected $service;
+
+ // @codingStandardsIgnoreStart
+ /**
+ * Postal Code regexes by territory
+ *
+ * @var array
+ */
+ protected static $postCodeRegex = [
+ 'GB' => 'GIR[ ]?0AA|^((AB|AL|B|BA|BB|BD|BH|BL|BN|BR|BS|BT|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}))$|^BFPO[ ]?\d{1,4}',
+ 'JE' => 'JE\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}',
+ 'GG' => 'GY\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}',
+ 'IM' => 'IM\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}',
+ 'US' => '\d{5}([ \-]\d{4})?',
+ 'CA' => '[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][ ]?\d[ABCEGHJ-NPRSTV-Z]\d',
+ 'DE' => '\d{5}',
+ 'JP' => '\d{3}-\d{4}',
+ 'FR' => '(?!(0{2})|(9(6|9))[ ]?\d{3})(\d{2}[ ]?\d{3})',
+ 'AU' => '\d{4}',
+ 'IT' => '\d{5}',
+ 'CH' => '\d{4}',
+ 'AT' => '\d{4}',
+ 'ES' => '\d{5}',
+ 'NL' => '\d{4}[ ]?[A-Z]{2}',
+ 'BE' => '\d{4}',
+ 'DK' => '\d{4}',
+ 'SE' => '\d{3}[ ]?\d{2}',
+ 'NO' => '(?!0000)\d{4}',
+ 'BR' => '\d{5}[\-]?\d{3}',
+ 'PT' => '\d{4}([\-]\d{3})?',
+ 'FI' => '\d{5}',
+ 'AX' => '22\d{3}',
+ 'KR' => '\d{3}[\-]\d{3}',
+ 'CN' => '\d{6}',
+ 'TW' => '\d{3}(\d{2})?',
+ 'SG' => '\d{6}',
+ 'DZ' => '\d{5}',
+ 'AD' => 'AD\d{3}',
+ 'AR' => '([A-HJ-NP-Z])?\d{4}([A-Z]{3})?',
+ 'AM' => '(37)?\d{4}',
+ 'AZ' => '\d{4}',
+ 'BH' => '((1[0-2]|[2-9])\d{2})?',
+ 'BD' => '\d{4}',
+ 'BB' => '(BB\d{5})?',
+ 'BY' => '\d{6}',
+ 'BM' => '[A-Z]{2}[ ]?[A-Z0-9]{2}',
+ 'BA' => '\d{5}',
+ 'IO' => 'BBND 1ZZ',
+ 'BN' => '[A-Z]{2}[ ]?\d{4}',
+ 'BG' => '\d{4}',
+ 'KH' => '\d{5}',
+ 'CV' => '\d{4}',
+ 'CL' => '\d{7}',
+ 'CR' => '\d{4,5}|\d{3}-\d{4}',
+ 'HR' => '\d{5}',
+ 'CY' => '\d{4}',
+ 'CZ' => '\d{3}[ ]?\d{2}',
+ 'DO' => '\d{5}',
+ 'EC' => '([A-Z]\d{4}[A-Z]|(?:[A-Z]{2})?\d{6})?',
+ 'EG' => '\d{5}',
+ 'EE' => '\d{5}',
+ 'FO' => '\d{3}',
+ 'GE' => '\d{4}',
+ 'GR' => '\d{3}[ ]?\d{2}',
+ 'GL' => '39\d{2}',
+ 'GT' => '\d{5}',
+ 'HT' => '\d{4}',
+ 'HN' => '(?:\d{5})?',
+ 'HU' => '\d{4}',
+ 'IS' => '\d{3}',
+ 'IN' => '\d{6}',
+ 'ID' => '\d{5}',
+ 'IE' => '[\dA-Z]{3} ?[\dA-Z]{4}',
+ 'IL' => '\d{5}',
+ 'JO' => '\d{5}',
+ 'KZ' => '\d{6}',
+ 'KE' => '\d{5}',
+ 'KW' => '\d{5}',
+ 'LA' => '\d{5}',
+ 'LV' => '(LV-)?\d{4}',
+ 'LB' => '(\d{4}([ ]?\d{4})?)?',
+ 'LI' => '(948[5-9])|(949[0-7])',
+ 'LT' => '\d{5}',
+ 'LU' => '\d{4}',
+ 'MK' => '\d{4}',
+ 'MY' => '\d{5}',
+ 'MV' => '\d{5}',
+ 'MT' => '[A-Z]{3}[ ]?\d{2,4}',
+ 'MU' => '\d{5}',
+ 'MX' => '\d{5}',
+ 'MD' => '\d{4}',
+ 'MC' => '980\d{2}',
+ 'MA' => '\d{5}',
+ 'NP' => '\d{5}',
+ 'NZ' => '\d{4}',
+ 'NI' => '((\d{4}-)?\d{3}-\d{3}(-\d{1})?)?',
+ 'NG' => '(\d{6})?',
+ 'OM' => '(PC )?\d{3}',
+ 'PK' => '\d{5}',
+ 'PY' => '\d{4}',
+ 'PH' => '\d{4}',
+ 'PL' => '\d{2}-\d{3}',
+ 'PR' => '00[679]\d{2}([ \-]\d{4})?',
+ 'RO' => '\d{6}',
+ 'RU' => '\d{6}',
+ 'SM' => '4789\d',
+ 'SA' => '\d{5}',
+ 'SN' => '\d{5}',
+ 'SK' => '\d{3}[ ]?\d{2}',
+ 'SI' => '\d{4}',
+ 'ZA' => '\d{4}',
+ 'LK' => '\d{5}',
+ 'TJ' => '\d{6}',
+ 'TH' => '\d{5}',
+ 'TN' => '\d{4}',
+ 'TR' => '\d{5}',
+ 'TM' => '\d{6}',
+ 'UA' => '\d{5}',
+ 'UY' => '\d{5}',
+ 'UZ' => '\d{6}',
+ 'VA' => '00120',
+ 'VE' => '\d{4}',
+ 'ZM' => '\d{5}',
+ 'AS' => '96799',
+ 'CC' => '6799',
+ 'CK' => '\d{4}',
+ 'RS' => '\d{5}',
+ 'ME' => '8\d{4}',
+ 'CS' => '\d{5}',
+ 'YU' => '\d{5}',
+ 'CX' => '6798',
+ 'ET' => '\d{4}',
+ 'FK' => 'FIQQ 1ZZ',
+ 'NF' => '2899',
+ 'FM' => '(9694[1-4])([ \-]\d{4})?',
+ 'GF' => '9[78]3\d{2}',
+ 'GN' => '\d{3}',
+ 'GP' => '9[78][01]\d{2}',
+ 'GS' => 'SIQQ 1ZZ',
+ 'GU' => '969[123]\d([ \-]\d{4})?',
+ 'GW' => '\d{4}',
+ 'HM' => '\d{4}',
+ 'IQ' => '\d{5}',
+ 'KG' => '\d{6}',
+ 'LR' => '\d{4}',
+ 'LS' => '\d{3}',
+ 'MG' => '\d{3}',
+ 'MH' => '969[67]\d([ \-]\d{4})?',
+ 'MN' => '\d{6}',
+ 'MP' => '9695[012]([ \-]\d{4})?',
+ 'MQ' => '9[78]2\d{2}',
+ 'NC' => '988\d{2}',
+ 'NE' => '\d{4}',
+ 'VI' => '008(([0-4]\d)|(5[01]))([ \-]\d{4})?',
+ 'PF' => '987\d{2}',
+ 'PG' => '\d{3}',
+ 'PM' => '9[78]5\d{2}',
+ 'PN' => 'PCRN 1ZZ',
+ 'PW' => '96940',
+ 'RE' => '9[78]4\d{2}',
+ 'SH' => '(ASCN|STHL) 1ZZ',
+ 'SJ' => '\d{4}',
+ 'SO' => '\d{5}',
+ 'SZ' => '[HLMS]\d{3}',
+ 'TC' => 'TKCA 1ZZ',
+ 'WF' => '986\d{2}',
+ 'YT' => '976\d{2}',
+ 'VN' => '\d{6}',
+ ];
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * Constructor for the PostCode validator
+ *
+ * Accepts a string locale and/or "format".
+ *
+ * @param array|Traversable $options
+ * @throws Exception\ExtensionNotLoadedException if ext/intl is not present
+ */
+ public function __construct($options = [])
+ {
+ if (! extension_loaded('intl')) {
+ throw new I18nException\ExtensionNotLoadedException(sprintf(
+ '%s component requires the intl PHP extension',
+ __NAMESPACE__
+ ));
+ }
+
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (array_key_exists('locale', $options)) {
+ $this->setLocale($options['locale']);
+ } else {
+ $this->setLocale(Locale::getDefault());
+ }
+ if (array_key_exists('format', $options)) {
+ $this->setFormat($options['format']);
+ }
+ if (array_key_exists('service', $options)) {
+ $this->setService($options['service']);
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set locale
+ *
+ * @return string|null The set locale
+ */
+ public function getLocale()
+ {
+ return $this->locale;
+ }
+
+ /**
+ * Sets the locale to use
+ *
+ * @param string|null $locale
+ * @return PostCode Provides fluid interface
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = $locale;
+ return $this;
+ }
+
+ /**
+ * Returns the set postal code format
+ *
+ * @return string|null
+ */
+ public function getFormat()
+ {
+ return $this->format;
+ }
+
+ /**
+ * Sets a self defined postal format as regex
+ *
+ * @param string $format
+ * @return PostCode Provides fluid interface
+ */
+ public function setFormat($format)
+ {
+ $this->format = $format;
+ return $this;
+ }
+
+ /**
+ * Returns the actual set service
+ *
+ * @return mixed|null
+ */
+ public function getService()
+ {
+ return $this->service;
+ }
+
+ /**
+ * Sets a new callback for service validation
+ *
+ * @param mixed $service
+ * @return PostCode Provides fluid interface
+ */
+ public function setService($service)
+ {
+ $this->service = $service;
+ return $this;
+ }
+
+ /**
+ * Returns true if and only if $value is a valid postalcode
+ *
+ * @param string $value
+ * @return bool
+ * @throws Exception\InvalidArgumentException
+ */
+ public function isValid($value)
+ {
+ if (! is_string($value) && ! is_int($value)) {
+ $this->error(self::INVALID);
+ return false;
+ }
+
+ $this->setValue($value);
+
+ $service = $this->getService();
+ $locale = $this->getLocale();
+ $format = $this->getFormat();
+ if ((null === $format || '' === $format) && ! empty($locale)) {
+ $region = Locale::getRegion($locale);
+ if ('' === $region) {
+ throw new Exception\InvalidArgumentException("Locale must contain a region");
+ }
+ if (isset(static::$postCodeRegex[$region])) {
+ $format = static::$postCodeRegex[$region];
+ }
+ }
+ if (null === $format || '' === $format) {
+ throw new Exception\InvalidArgumentException("A postcode-format string has to be given for validation");
+ }
+
+ if ($format[0] !== '/') {
+ $format = '/^' . $format;
+ }
+ if ($format[strlen($format) - 1] !== '/') {
+ $format .= '$/';
+ }
+
+ if (! empty($service)) {
+ if (! is_callable($service)) {
+ throw new Exception\InvalidArgumentException('Invalid callback given');
+ }
+
+ try {
+ $callback = new Callback($service);
+ $callback->setOptions([
+ 'format' => $format,
+ 'locale' => $locale,
+ ]);
+ if (! $callback->isValid($value)) {
+ $this->error(self::SERVICE, $value);
+ return false;
+ }
+ } catch (\Exception $e) {
+ $this->error(self::SERVICEFAILURE, $value);
+ return false;
+ }
+ }
+
+ if (! preg_match($format, $value)) {
+ $this->error(self::NO_MATCH);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/module/Zend/I18n/src/View/Helper/AbstractTranslatorHelper.php b/module/Zend/I18n/src/View/Helper/AbstractTranslatorHelper.php
new file mode 100644
index 00000000..5e7cacd6
--- /dev/null
+++ b/module/Zend/I18n/src/View/Helper/AbstractTranslatorHelper.php
@@ -0,0 +1,126 @@
+translator = $translator;
+ if (null !== $textDomain) {
+ $this->setTranslatorTextDomain($textDomain);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns translator used in helper
+ *
+ * @return Translator|null
+ */
+ public function getTranslator()
+ {
+ if (! $this->isTranslatorEnabled()) {
+ return;
+ }
+
+ return $this->translator;
+ }
+
+ /**
+ * Checks if the helper has a translator
+ *
+ * @return bool
+ */
+ public function hasTranslator()
+ {
+ return (bool) $this->getTranslator();
+ }
+
+ /**
+ * Sets whether translator is enabled and should be used
+ *
+ * @param bool $enabled
+ * @return AbstractTranslatorHelper
+ */
+ public function setTranslatorEnabled($enabled = true)
+ {
+ $this->translatorEnabled = (bool) $enabled;
+ return $this;
+ }
+
+ /**
+ * Returns whether translator is enabled and should be used
+ *
+ * @return bool
+ */
+ public function isTranslatorEnabled()
+ {
+ return $this->translatorEnabled;
+ }
+
+ /**
+ * Set translation text domain
+ *
+ * @param string $textDomain
+ * @return AbstractTranslatorHelper
+ */
+ public function setTranslatorTextDomain($textDomain = 'default')
+ {
+ $this->translatorTextDomain = $textDomain;
+ return $this;
+ }
+
+ /**
+ * Return the translation text domain
+ *
+ * @return string
+ */
+ public function getTranslatorTextDomain()
+ {
+ return $this->translatorTextDomain;
+ }
+}
diff --git a/module/Zend/I18n/src/View/Helper/CurrencyFormat.php b/module/Zend/I18n/src/View/Helper/CurrencyFormat.php
new file mode 100644
index 00000000..33a3ce07
--- /dev/null
+++ b/module/Zend/I18n/src/View/Helper/CurrencyFormat.php
@@ -0,0 +1,293 @@
+getLocale();
+ }
+ if (null === $currencyCode) {
+ $currencyCode = $this->getCurrencyCode();
+ }
+ if (null === $showDecimals) {
+ $showDecimals = $this->shouldShowDecimals();
+ }
+ if (null === $pattern) {
+ $pattern = $this->getCurrencyPattern();
+ }
+
+ return $this->formatCurrency($number, $currencyCode, $showDecimals, $locale, $pattern);
+ }
+
+ /**
+ * Format a number
+ *
+ * @param float $number
+ * @param string $currencyCode
+ * @param bool $showDecimals
+ * @param string $locale
+ * @param string $pattern
+ * @return string
+ */
+ protected function formatCurrency(
+ $number,
+ $currencyCode,
+ $showDecimals,
+ $locale,
+ $pattern
+ ) {
+ $formatterId = md5($locale);
+
+ if (! isset($this->formatters[$formatterId])) {
+ $this->formatters[$formatterId] = new NumberFormatter(
+ $locale,
+ NumberFormatter::CURRENCY
+ );
+ }
+
+ if ($pattern !== null) {
+ $this->formatters[$formatterId]->setPattern($pattern);
+ }
+
+ if ($showDecimals) {
+ $this->formatters[$formatterId]->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
+ $this->correctionNeeded = false;
+ } else {
+ $this->formatters[$formatterId]->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
+ $defaultCurrencyCode = $this->formatters[$formatterId]->getTextAttribute(NumberFormatter::CURRENCY_CODE);
+ $this->correctionNeeded = $defaultCurrencyCode !== $currencyCode;
+ }
+
+ $formattedNumber = $this->formatters[$formatterId]->formatCurrency($number, $currencyCode);
+
+ if ($this->correctionNeeded) {
+ $formattedNumber = $this->fixICUBugForNoDecimals(
+ $formattedNumber,
+ $this->formatters[$formatterId],
+ $locale,
+ $currencyCode
+ );
+ }
+
+ return $formattedNumber;
+ }
+
+ /**
+ * The 3-letter ISO 4217 currency code indicating the currency to use
+ *
+ * @param string $currencyCode
+ * @return CurrencyFormat
+ */
+ public function setCurrencyCode($currencyCode)
+ {
+ $this->currencyCode = $currencyCode;
+ return $this;
+ }
+
+ /**
+ * Get the 3-letter ISO 4217 currency code indicating the currency to use
+ *
+ * @return string
+ */
+ public function getCurrencyCode()
+ {
+ return $this->currencyCode;
+ }
+
+ /**
+ * Set the currency pattern
+ *
+ * @param string $currencyPattern
+ * @return CurrencyFormat
+ */
+ public function setCurrencyPattern($currencyPattern)
+ {
+ $this->currencyPattern = $currencyPattern;
+ return $this;
+ }
+
+ /**
+ * Get the currency pattern
+ *
+ * @return string
+ */
+ public function getCurrencyPattern()
+ {
+ return $this->currencyPattern;
+ }
+
+ /**
+ * Set locale to use instead of the default
+ *
+ * @param string $locale
+ * @return CurrencyFormat
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = (string) $locale;
+ return $this;
+ }
+
+ /**
+ * Get the locale to use
+ *
+ * @return string|null
+ */
+ public function getLocale()
+ {
+ if ($this->locale === null) {
+ $this->locale = Locale::getDefault();
+ }
+
+ return $this->locale;
+ }
+
+ /**
+ * Set if the view helper should show two decimals
+ *
+ * @param bool $showDecimals
+ * @return CurrencyFormat
+ */
+ public function setShouldShowDecimals($showDecimals)
+ {
+ $this->showDecimals = (bool) $showDecimals;
+ return $this;
+ }
+
+ /**
+ * Get if the view helper should show two decimals
+ *
+ * @return bool
+ */
+ public function shouldShowDecimals()
+ {
+ return $this->showDecimals;
+ }
+
+
+
+ /**
+ * @param string $formattedNumber
+ * @param NumberFormatter $formatter
+ * @param string $locale
+ * @param string $currencyCode
+ *
+ * @return string
+ */
+ private function fixICUBugForNoDecimals($formattedNumber, NumberFormatter $formatter, $locale, $currencyCode)
+ {
+ $pattern = sprintf(
+ '/\%s\d+(\s?%s)?$/u',
+ $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL),
+ preg_quote($this->getCurrencySymbol($locale, $currencyCode))
+ );
+
+ return preg_replace($pattern, '$1', $formattedNumber);
+ }
+
+
+
+ /**
+ * @param string $locale
+ * @param string $currencyCode
+ *
+ * @return string
+ */
+ private function getCurrencySymbol($locale, $currencyCode)
+ {
+ $numberFormatter = new NumberFormatter($locale . '@currency=' . $currencyCode, NumberFormatter::CURRENCY);
+
+ return $numberFormatter->getSymbol(NumberFormatter::CURRENCY_SYMBOL);
+ }
+}
diff --git a/module/Zend/I18n/src/View/Helper/DateFormat.php b/module/Zend/I18n/src/View/Helper/DateFormat.php
new file mode 100644
index 00000000..23c76884
--- /dev/null
+++ b/module/Zend/I18n/src/View/Helper/DateFormat.php
@@ -0,0 +1,154 @@
+getLocale();
+ }
+
+ $timezone = $this->getTimezone();
+ $formatterId = md5($dateType . "\0" . $timeType . "\0" . $locale ."\0" . $pattern);
+
+ if (! isset($this->formatters[$formatterId])) {
+ $this->formatters[$formatterId] = new IntlDateFormatter(
+ $locale,
+ $dateType,
+ $timeType,
+ $timezone,
+ IntlDateFormatter::GREGORIAN,
+ $pattern
+ );
+ }
+
+ return $this->formatters[$formatterId]->format($date);
+ }
+
+ /**
+ * Set locale to use instead of the default
+ *
+ * @param string $locale
+ * @return DateFormat
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = (string) $locale;
+ return $this;
+ }
+
+ /**
+ * Get the locale to use
+ *
+ * @return string|null
+ */
+ public function getLocale()
+ {
+ if ($this->locale === null) {
+ $this->locale = Locale::getDefault();
+ }
+
+ return $this->locale;
+ }
+
+ /**
+ * Set timezone to use instead of the default
+ *
+ * @param string $timezone
+ * @return DateFormat
+ */
+ public function setTimezone($timezone)
+ {
+ $this->timezone = (string) $timezone;
+
+ // The method setTimeZoneId is deprecated as of PHP 5.5.0
+ $setTimeZoneMethodName = (PHP_VERSION_ID < 50500) ? 'setTimeZoneId' : 'setTimeZone';
+
+ foreach ($this->formatters as $formatter) {
+ $formatter->$setTimeZoneMethodName($this->timezone);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the timezone to use
+ *
+ * @return string|null
+ */
+ public function getTimezone()
+ {
+ if (! $this->timezone) {
+ return date_default_timezone_get();
+ }
+
+ return $this->timezone;
+ }
+}
diff --git a/module/Zend/I18n/src/View/Helper/NumberFormat.php b/module/Zend/I18n/src/View/Helper/NumberFormat.php
new file mode 100644
index 00000000..0cb97e92
--- /dev/null
+++ b/module/Zend/I18n/src/View/Helper/NumberFormat.php
@@ -0,0 +1,254 @@
+getLocale();
+ }
+ if (null === $formatStyle) {
+ $formatStyle = $this->getFormatStyle();
+ }
+ if (null === $formatType) {
+ $formatType = $this->getFormatType();
+ }
+ if (! is_int($decimals) || $decimals < 0) {
+ $decimals = $this->getDecimals();
+ }
+ if (! is_array($textAttributes)) {
+ $textAttributes = $this->getTextAttributes();
+ }
+
+ $formatterId = md5(
+ $formatStyle . "\0" . $locale . "\0" . $decimals . "\0"
+ . md5(serialize($textAttributes))
+ );
+
+ if (isset($this->formatters[$formatterId])) {
+ $formatter = $this->formatters[$formatterId];
+ } else {
+ $formatter = new NumberFormatter($locale, $formatStyle);
+
+ if ($decimals !== null) {
+ $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
+ $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
+ }
+
+ foreach ($textAttributes as $textAttribute => $value) {
+ $formatter->setTextAttribute($textAttribute, $value);
+ }
+
+ $this->formatters[$formatterId] = $formatter;
+ }
+
+ return $formatter->format($number, $formatType);
+ }
+
+ /**
+ * Set format style to use instead of the default
+ *
+ * @param int $formatStyle
+ * @return NumberFormat
+ */
+ public function setFormatStyle($formatStyle)
+ {
+ $this->formatStyle = (int) $formatStyle;
+ return $this;
+ }
+
+ /**
+ * Get the format style to use
+ *
+ * @return int
+ */
+ public function getFormatStyle()
+ {
+ if (null === $this->formatStyle) {
+ $this->formatStyle = NumberFormatter::DECIMAL;
+ }
+
+ return $this->formatStyle;
+ }
+
+ /**
+ * Set format type to use instead of the default
+ *
+ * @param int $formatType
+ * @return NumberFormat
+ */
+ public function setFormatType($formatType)
+ {
+ $this->formatType = (int) $formatType;
+ return $this;
+ }
+
+ /**
+ * Get the format type to use
+ *
+ * @return int
+ */
+ public function getFormatType()
+ {
+ if (null === $this->formatType) {
+ $this->formatType = NumberFormatter::TYPE_DEFAULT;
+ }
+ return $this->formatType;
+ }
+
+ /**
+ * Set number of decimals to use instead of the default.
+ *
+ * @param int $decimals
+ * @return NumberFormat
+ */
+ public function setDecimals($decimals)
+ {
+ $this->decimals = $decimals;
+ return $this;
+ }
+
+ /**
+ * Get number of decimals.
+ *
+ * @return int
+ */
+ public function getDecimals()
+ {
+ return $this->decimals;
+ }
+
+ /**
+ * Set locale to use instead of the default.
+ *
+ * @param string $locale
+ * @return NumberFormat
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = (string) $locale;
+ return $this;
+ }
+
+ /**
+ * Get the locale to use
+ *
+ * @return string|null
+ */
+ public function getLocale()
+ {
+ if ($this->locale === null) {
+ $this->locale = Locale::getDefault();
+ }
+
+ return $this->locale;
+ }
+
+ /**
+ * @return array
+ */
+ public function getTextAttributes()
+ {
+ return $this->textAttributes;
+ }
+
+ /**
+ * @param array $textAttributes
+ * @return NumberFormat
+ */
+ public function setTextAttributes(array $textAttributes)
+ {
+ $this->textAttributes = $textAttributes;
+ return $this;
+ }
+}
diff --git a/module/Zend/I18n/src/View/Helper/Plural.php b/module/Zend/I18n/src/View/Helper/Plural.php
new file mode 100644
index 00000000..e7d73ceb
--- /dev/null
+++ b/module/Zend/I18n/src/View/Helper/Plural.php
@@ -0,0 +1,102 @@
+getPluralRule()) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'No plural rule was set'
+ ));
+ }
+
+ if (! is_array($strings)) {
+ $strings = (array) $strings;
+ }
+
+ $pluralIndex = $this->getPluralRule()->evaluate($number);
+
+ return $strings[$pluralIndex];
+ }
+
+ /**
+ * Set the plural rule to use
+ *
+ * @param PluralRule|string $pluralRule
+ * @return Plural
+ */
+ public function setPluralRule($pluralRule)
+ {
+ if (! $pluralRule instanceof PluralRule) {
+ $pluralRule = PluralRule::fromString($pluralRule);
+ }
+
+ $this->rule = $pluralRule;
+
+ return $this;
+ }
+
+ /**
+ * Get the plural rule to use
+ *
+ * @return PluralRule
+ */
+ public function getPluralRule()
+ {
+ return $this->rule;
+ }
+}
diff --git a/module/Zend/I18n/src/View/Helper/Translate.php b/module/Zend/I18n/src/View/Helper/Translate.php
new file mode 100644
index 00000000..bfd9d20a
--- /dev/null
+++ b/module/Zend/I18n/src/View/Helper/Translate.php
@@ -0,0 +1,40 @@
+getTranslator();
+ if (null === $translator) {
+ throw new Exception\RuntimeException('Translator has not been set');
+ }
+ if (null === $textDomain) {
+ $textDomain = $this->getTranslatorTextDomain();
+ }
+
+ return $translator->translate($message, $textDomain, $locale);
+ }
+}
diff --git a/module/Zend/I18n/src/View/Helper/TranslatePlural.php b/module/Zend/I18n/src/View/Helper/TranslatePlural.php
new file mode 100644
index 00000000..eee3efea
--- /dev/null
+++ b/module/Zend/I18n/src/View/Helper/TranslatePlural.php
@@ -0,0 +1,47 @@
+getTranslator();
+ if (null === $translator) {
+ throw new Exception\RuntimeException('Translator has not been set');
+ }
+ if (null === $textDomain) {
+ $textDomain = $this->getTranslatorTextDomain();
+ }
+
+ return $translator->translatePlural($singular, $plural, $number, $textDomain, $locale);
+ }
+}
diff --git a/module/Zend/I18n/src/View/HelperConfig.php b/module/Zend/I18n/src/View/HelperConfig.php
new file mode 100644
index 00000000..4cb9ea6d
--- /dev/null
+++ b/module/Zend/I18n/src/View/HelperConfig.php
@@ -0,0 +1,106 @@
+ Helper\CurrencyFormat::class,
+ 'currencyFormat' => Helper\CurrencyFormat::class,
+ 'CurrencyFormat' => Helper\CurrencyFormat::class,
+ 'dateformat' => Helper\DateFormat::class,
+ 'dateFormat' => Helper\DateFormat::class,
+ 'DateFormat' => Helper\DateFormat::class,
+ 'numberformat' => Helper\NumberFormat::class,
+ 'numberFormat' => Helper\NumberFormat::class,
+ 'NumberFormat' => Helper\NumberFormat::class,
+ 'plural' => Helper\Plural::class,
+ 'Plural' => Helper\Plural::class,
+ 'translate' => Helper\Translate::class,
+ 'Translate' => Helper\Translate::class,
+ 'translateplural' => Helper\TranslatePlural::class,
+ 'translatePlural' => Helper\TranslatePlural::class,
+ 'TranslatePlural' => Helper\TranslatePlural::class,
+ ];
+
+ /**
+ * Factories for included helpers.
+ * @var array
+ */
+ protected $factories = [
+ Helper\CurrencyFormat::class => InvokableFactory::class,
+ Helper\DateFormat::class => InvokableFactory::class,
+ Helper\NumberFormat::class => InvokableFactory::class,
+ Helper\Plural::class => InvokableFactory::class,
+ Helper\Translate::class => InvokableFactory::class,
+ Helper\TranslatePlural::class => InvokableFactory::class,
+ // Legacy (v2) due to alias resolution; canonical form of resolved
+ // alias is used to look up the factory, while the non-normalized
+ // resolved alias is used as the requested name passed to the factory.
+ 'zendi18nviewhelpercurrencyformat' => InvokableFactory::class,
+ 'zendi18nviewhelperdateformat' => InvokableFactory::class,
+ 'zendi18nviewhelpernumberformat' => InvokableFactory::class,
+ 'zendi18nviewhelperplural' => InvokableFactory::class,
+ 'zendi18nviewhelpertranslate' => InvokableFactory::class,
+ 'zendi18nviewhelpertranslateplural' => InvokableFactory::class,
+ ];
+
+ /**
+ * Configure the provided service manager instance with the configuration
+ * in this class.
+ *
+ * @param ServiceManager $serviceManager
+ * @return ServiceManager
+ */
+ public function configureServiceManager(ServiceManager $serviceManager)
+ {
+ if (method_exists($serviceManager, 'configure')) {
+ $serviceManager->configure($this->toArray());
+ return $serviceManager;
+ }
+
+ foreach ($this->factories as $name => $factory) {
+ $serviceManager->setFactory($name, $factory);
+ }
+ foreach ($this->aliases as $alias => $target) {
+ $serviceManager->setAlias($alias, $target);
+ }
+
+ return $serviceManager;
+ }
+
+ /**
+ * Cast configuration to an array.
+ *
+ * Provided for v3 compatibility
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return [
+ 'aliases' => $this->aliases,
+ 'factories' => $this->factories,
+ ];
+ }
+}
diff --git a/module/Zend/View/LICENSE.md b/module/Zend/View/LICENSE.md
new file mode 100644
index 00000000..dbb1b49c
--- /dev/null
+++ b/module/Zend/View/LICENSE.md
@@ -0,0 +1,28 @@
+Copyright (c) 2005-2015, Zend Technologies USA, Inc.
+
+All 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.
+
+- Neither the name of Zend Technologies USA, Inc. nor the names of its
+ contributors may 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.
diff --git a/module/Zend/View/README.md b/module/Zend/View/README.md
new file mode 100644
index 00000000..a5bbc8d7
--- /dev/null
+++ b/module/Zend/View/README.md
@@ -0,0 +1,11 @@
+# zend-view
+
+[![Build Status](https://secure.travis-ci.org/zendframework/zend-view.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-view)
+[![Coverage Status](https://coveralls.io/repos/zendframework/zend-view/badge.svg?branch=master)](https://coveralls.io/r/zendframework/zend-view?branch=master)
+
+zend-view provides the “View” layer of the Zend Framework MVC system. It is a
+multi-tiered system allowing a variety of mechanisms for extension,
+substitution, and more.
+
+- File issues at https://github.com/zendframework/zend-view/issues
+- Documentation is at https://zendframework.github.io/zend-view/
diff --git a/module/Zend/View/src/Exception/BadMethodCallException.php b/module/Zend/View/src/Exception/BadMethodCallException.php
new file mode 100644
index 00000000..8d3e7b1e
--- /dev/null
+++ b/module/Zend/View/src/Exception/BadMethodCallException.php
@@ -0,0 +1,17 @@
+view = $view;
+ return $this;
+ }
+
+ /**
+ * Get the view object
+ *
+ * @return null|Renderer
+ */
+ public function getView()
+ {
+ return $this->view;
+ }
+}
diff --git a/module/Zend/View/src/Helper/AbstractHtmlElement.php b/module/Zend/View/src/Helper/AbstractHtmlElement.php
new file mode 100644
index 00000000..a5675ed8
--- /dev/null
+++ b/module/Zend/View/src/Helper/AbstractHtmlElement.php
@@ -0,0 +1,122 @@
+closingBracket) {
+ if ($this->isXhtml()) {
+ $this->closingBracket = ' />';
+ } else {
+ $this->closingBracket = '>';
+ }
+ }
+
+ return $this->closingBracket;
+ }
+
+ /**
+ * Is doctype XHTML?
+ *
+ * @return bool
+ */
+ protected function isXhtml()
+ {
+ return $this->getView()->plugin('doctype')->isXhtml();
+ }
+
+ /**
+ * Converts an associative array to a string of tag attributes.
+ *
+ * @access public
+ *
+ * @param array $attribs From this array, each key-value pair is
+ * converted to an attribute name and value.
+ *
+ * @return string The XHTML for the attributes.
+ */
+ protected function htmlAttribs($attribs)
+ {
+ $xhtml = '';
+ $escaper = $this->getView()->plugin('escapehtml');
+ $escapeHtmlAttr = $this->getView()->plugin('escapehtmlattr');
+
+ foreach ((array) $attribs as $key => $val) {
+ $key = $escaper($key);
+
+ if (0 === strpos($key, 'on') || ('constraints' == $key)) {
+ // Don't escape event attributes; _do_ substitute double quotes with singles
+ if (! is_scalar($val)) {
+ // non-scalar data should be cast to JSON first
+ $val = \Zend\Json\Json::encode($val);
+ }
+ } else {
+ if (is_array($val)) {
+ $val = implode(' ', $val);
+ }
+ }
+
+ $val = $escapeHtmlAttr($val);
+
+ if ('id' == $key) {
+ $val = $this->normalizeId($val);
+ }
+
+ if (strpos($val, '"') !== false) {
+ $xhtml .= " $key='$val'";
+ } else {
+ $xhtml .= " $key=\"$val\"";
+ }
+ }
+
+ return $xhtml;
+ }
+
+ /**
+ * Normalize an ID
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function normalizeId($value)
+ {
+ if (false !== strpos($value, '[')) {
+ if ('[]' == substr($value, -2)) {
+ $value = substr($value, 0, strlen($value) - 2);
+ }
+ $value = trim($value, ']');
+ $value = str_replace('][', '-', $value);
+ $value = str_replace('[', '-', $value);
+ }
+
+ return $value;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Asset.php b/module/Zend/View/src/Helper/Asset.php
new file mode 100644
index 00000000..d7342822
--- /dev/null
+++ b/module/Zend/View/src/Helper/Asset.php
@@ -0,0 +1,54 @@
+resourceMap)) {
+ throw new Exception\InvalidArgumentException('Asset is not defined.');
+ }
+
+ return $this->resourceMap[$asset];
+ }
+
+ /**
+ * @param array $resourceMap
+ * @return $this
+ */
+ public function setResourceMap(array $resourceMap)
+ {
+ $this->resourceMap = $resourceMap;
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getResourceMap()
+ {
+ return $this->resourceMap;
+ }
+}
diff --git a/module/Zend/View/src/Helper/BasePath.php b/module/Zend/View/src/Helper/BasePath.php
new file mode 100644
index 00000000..d71a3453
--- /dev/null
+++ b/module/Zend/View/src/Helper/BasePath.php
@@ -0,0 +1,59 @@
+basePath) {
+ throw new Exception\RuntimeException('No base path provided');
+ }
+
+ if (null !== $file) {
+ $file = '/' . ltrim($file, '/');
+ }
+
+ return $this->basePath . $file;
+ }
+
+ /**
+ * Set the base path.
+ *
+ * @param string $basePath
+ * @return self
+ */
+ public function setBasePath($basePath)
+ {
+ $this->basePath = rtrim($basePath, '/');
+ return $this;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Cycle.php b/module/Zend/View/src/Helper/Cycle.php
new file mode 100644
index 00000000..5f71a783
--- /dev/null
+++ b/module/Zend/View/src/Helper/Cycle.php
@@ -0,0 +1,220 @@
+ []];
+
+ /**
+ * Actual name of cycle
+ *
+ * @var string
+ */
+ protected $name = self::DEFAULT_NAME;
+
+ /**
+ * Pointers
+ *
+ * @var array
+ */
+ protected $pointers = [self::DEFAULT_NAME => -1];
+
+ /**
+ * Add elements to alternate
+ *
+ * @param array $data
+ * @param string $name
+ * @return Cycle
+ */
+ public function __invoke(array $data = [], $name = self::DEFAULT_NAME)
+ {
+ if (! empty($data)) {
+ $this->data[$name] = $data;
+ }
+
+ $this->setName($name);
+ return $this;
+ }
+
+ /**
+ * Cast to string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Turn helper into string
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return (string) $this->data[$this->name][$this->key()];
+ }
+
+ /**
+ * Add elements to alternate
+ *
+ * @param array $data
+ * @param string $name
+ * @return Cycle
+ */
+ public function assign(array $data, $name = self::DEFAULT_NAME)
+ {
+ $this->setName($name);
+ $this->data[$name] = $data;
+ $this->rewind();
+ return $this;
+ }
+
+ /**
+ * Sets actual name of cycle
+ *
+ * @param $name
+ * @return Cycle
+ */
+ public function setName($name = self::DEFAULT_NAME)
+ {
+ $this->name = $name;
+
+ if (! isset($this->data[$this->name])) {
+ $this->data[$this->name] = [];
+ }
+
+ if (! isset($this->pointers[$this->name])) {
+ $this->rewind();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Gets actual name of cycle
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Return all elements
+ *
+ * @return array
+ */
+ public function getAll()
+ {
+ return $this->data[$this->name];
+ }
+
+ /**
+ * Move to next value
+ *
+ * @return Cycle
+ */
+ public function next()
+ {
+ $count = count($this->data[$this->name]);
+
+ if ($this->pointers[$this->name] == ($count - 1)) {
+ $this->pointers[$this->name] = 0;
+ } else {
+ $this->pointers[$this->name] = ++$this->pointers[$this->name];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Move to previous value
+ *
+ * @return Cycle
+ */
+ public function prev()
+ {
+ $count = count($this->data[$this->name]);
+
+ if ($this->pointers[$this->name] <= 0) {
+ $this->pointers[$this->name] = $count - 1;
+ } else {
+ $this->pointers[$this->name] = --$this->pointers[$this->name];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return iteration number
+ *
+ * @return int
+ */
+ public function key()
+ {
+ if ($this->pointers[$this->name] < 0) {
+ return 0;
+ }
+
+ return $this->pointers[$this->name];
+ }
+
+ /**
+ * Rewind pointer
+ *
+ * @return Cycle
+ */
+ public function rewind()
+ {
+ $this->pointers[$this->name] = -1;
+ return $this;
+ }
+
+ /**
+ * Check if element is valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return isset($this->data[$this->name][$this->key()]);
+ }
+
+ /**
+ * Return current element
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ return $this->data[$this->name][$this->key()];
+ }
+}
diff --git a/module/Zend/View/src/Helper/DeclareVars.php b/module/Zend/View/src/Helper/DeclareVars.php
new file mode 100644
index 00000000..a503d519
--- /dev/null
+++ b/module/Zend/View/src/Helper/DeclareVars.php
@@ -0,0 +1,80 @@
+
+ * $this->declareVars(
+ * 'varName1',
+ * 'varName2',
+ * array('varName3' => 'defaultValue',
+ * 'varName4' => array()
+ * )
+ * );
+ *
+ *
+ * @param string|array variable number of arguments, all string names of variables to test
+ * @return void
+ */
+ public function __invoke()
+ {
+ $view = $this->getView();
+ $args = func_get_args();
+ foreach ($args as $key) {
+ if (is_array($key)) {
+ foreach ($key as $name => $value) {
+ $this->declareVar($name, $value);
+ }
+ } elseif (! isset($view->vars()->$key)) {
+ $this->declareVar($key);
+ }
+ }
+ }
+
+ /**
+ * Set a view variable
+ *
+ * Checks to see if a $key is set in the view object; if not, sets it to $value.
+ *
+ * @param string $key
+ * @param string $value Defaults to an empty string
+ * @return void
+ */
+ protected function declareVar($key, $value = '')
+ {
+ $view = $this->getView();
+ $vars = $view->vars();
+ if (! isset($vars->$key)) {
+ $vars->$key = $value;
+ }
+ }
+}
diff --git a/module/Zend/View/src/Helper/Doctype.php b/module/Zend/View/src/Helper/Doctype.php
new file mode 100644
index 00000000..0227e17f
--- /dev/null
+++ b/module/Zend/View/src/Helper/Doctype.php
@@ -0,0 +1,232 @@
+setDoctype($this->defaultDoctype);
+ }
+ $this->registry = static::$registeredDoctypes;
+ }
+
+ /**
+ * Set or retrieve doctype
+ *
+ * @param string $doctype
+ * @throws Exception\DomainException
+ * @return Doctype
+ */
+ public function __invoke($doctype = null)
+ {
+ if (null !== $doctype) {
+ switch ($doctype) {
+ case self::XHTML11:
+ case self::XHTML1_STRICT:
+ case self::XHTML1_TRANSITIONAL:
+ case self::XHTML1_FRAMESET:
+ case self::XHTML_BASIC1:
+ case self::XHTML1_RDFA:
+ case self::XHTML1_RDFA11:
+ case self::XHTML5:
+ case self::HTML4_STRICT:
+ case self::HTML4_LOOSE:
+ case self::HTML4_FRAMESET:
+ case self::HTML5:
+ $this->setDoctype($doctype);
+ break;
+ default:
+ if (0 !== strpos($doctype, 'setDoctype($type);
+ $this->registry['doctypes'][$type] = $doctype;
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * String representation of doctype
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $doctypes = $this->getDoctypes();
+
+ return $doctypes[$this->getDoctype()];
+ }
+
+ /**
+ * Register the default doctypes we understand
+ *
+ * @return void
+ */
+ protected static function registerDefaultDoctypes()
+ {
+ // @codingStandardsIgnoreStart
+ static::$registeredDoctypes = new ArrayObject([
+ 'doctypes' => [
+ self::XHTML11 => '',
+ self::XHTML1_STRICT => '',
+ self::XHTML1_TRANSITIONAL => '',
+ self::XHTML1_FRAMESET => '',
+ self::XHTML1_RDFA => '',
+ self::XHTML1_RDFA11 => '',
+ self::XHTML_BASIC1 => '',
+ self::XHTML5 => '',
+ self::HTML4_STRICT => '',
+ self::HTML4_LOOSE => '',
+ self::HTML4_FRAMESET => '',
+ self::HTML5 => '',
+ ],
+ ]);
+ // @codingStandardsIgnoreEnd
+ }
+
+ /**
+ * Unset the static doctype registry
+ *
+ * Mainly useful for testing purposes. Sets {@link $registeredDoctypes} to
+ * a null value.
+ *
+ * @return void
+ */
+ public static function unsetDoctypeRegistry()
+ {
+ static::$registeredDoctypes = null;
+ }
+
+ /**
+ * Set doctype
+ *
+ * @param string $doctype
+ * @return Doctype
+ */
+ public function setDoctype($doctype)
+ {
+ $this->registry['doctype'] = $doctype;
+ return $this;
+ }
+
+ /**
+ * Retrieve doctype
+ *
+ * @return string
+ */
+ public function getDoctype()
+ {
+ if (! isset($this->registry['doctype'])) {
+ $this->setDoctype($this->defaultDoctype);
+ }
+
+ return $this->registry['doctype'];
+ }
+
+ /**
+ * Get doctype => string mappings
+ *
+ * @return array
+ */
+ public function getDoctypes()
+ {
+ return $this->registry['doctypes'];
+ }
+
+ /**
+ * Is doctype XHTML?
+ *
+ * @return bool
+ */
+ public function isXhtml()
+ {
+ return (bool) stristr($this->getDoctype(), 'xhtml');
+ }
+
+ /**
+ * Is doctype HTML5? (HeadMeta uses this for validation)
+ *
+ * @return bool
+ */
+ public function isHtml5()
+ {
+ return (bool) stristr($this->__invoke(), '');
+ }
+
+ /**
+ * Is doctype RDFa?
+ *
+ * @return bool
+ */
+ public function isRdfa()
+ {
+ return ($this->isHtml5() || stristr($this->getDoctype(), 'rdfa'));
+ }
+}
diff --git a/module/Zend/View/src/Helper/EscapeCss.php b/module/Zend/View/src/Helper/EscapeCss.php
new file mode 100644
index 00000000..86b5d6cb
--- /dev/null
+++ b/module/Zend/View/src/Helper/EscapeCss.php
@@ -0,0 +1,27 @@
+getEscaper()->escapeCss($value);
+ }
+}
diff --git a/module/Zend/View/src/Helper/EscapeHtml.php b/module/Zend/View/src/Helper/EscapeHtml.php
new file mode 100644
index 00000000..9bcf1ecf
--- /dev/null
+++ b/module/Zend/View/src/Helper/EscapeHtml.php
@@ -0,0 +1,27 @@
+getEscaper()->escapeHtml($value);
+ }
+}
diff --git a/module/Zend/View/src/Helper/EscapeHtmlAttr.php b/module/Zend/View/src/Helper/EscapeHtmlAttr.php
new file mode 100644
index 00000000..5ed9fe51
--- /dev/null
+++ b/module/Zend/View/src/Helper/EscapeHtmlAttr.php
@@ -0,0 +1,27 @@
+getEscaper()->escapeHtmlAttr($value);
+ }
+}
diff --git a/module/Zend/View/src/Helper/EscapeJs.php b/module/Zend/View/src/Helper/EscapeJs.php
new file mode 100644
index 00000000..1ccc0398
--- /dev/null
+++ b/module/Zend/View/src/Helper/EscapeJs.php
@@ -0,0 +1,27 @@
+getEscaper()->escapeJs($value);
+ }
+}
diff --git a/module/Zend/View/src/Helper/EscapeUrl.php b/module/Zend/View/src/Helper/EscapeUrl.php
new file mode 100644
index 00000000..9b9ae038
--- /dev/null
+++ b/module/Zend/View/src/Helper/EscapeUrl.php
@@ -0,0 +1,27 @@
+getEscaper()->escapeUrl($value);
+ }
+}
diff --git a/module/Zend/View/src/Helper/Escaper/AbstractHelper.php b/module/Zend/View/src/Helper/Escaper/AbstractHelper.php
new file mode 100644
index 00000000..99643e7d
--- /dev/null
+++ b/module/Zend/View/src/Helper/Escaper/AbstractHelper.php
@@ -0,0 +1,151 @@
+escape($value);
+ }
+
+ if (is_array($value)) {
+ if (! (self::RECURSE_ARRAY & $recurse)) {
+ throw new Exception\InvalidArgumentException(
+ 'Array provided to Escape helper, but flags do not allow recursion'
+ );
+ }
+ foreach ($value as $k => $v) {
+ $value[$k] = $this->__invoke($v, $recurse);
+ }
+ return $value;
+ }
+
+ if (is_object($value)) {
+ if (! (self::RECURSE_OBJECT & $recurse)) {
+ // Attempt to cast it to a string
+ if (method_exists($value, '__toString')) {
+ return $this->escape((string) $value);
+ }
+ throw new Exception\InvalidArgumentException(
+ 'Object provided to Escape helper, but flags do not allow recursion'
+ );
+ }
+ if (method_exists($value, 'toArray')) {
+ return $this->__invoke($value->toArray(), $recurse | self::RECURSE_ARRAY);
+ }
+ return $this->__invoke((array) $value, $recurse | self::RECURSE_ARRAY);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Escape a value for current escaping strategy
+ *
+ * @param string $value
+ * @return string
+ */
+ abstract protected function escape($value);
+
+ /**
+ * Set the encoding to use for escape operations
+ *
+ * @param string $encoding
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractHelper
+ */
+ public function setEncoding($encoding)
+ {
+ if (null !== $this->escaper) {
+ throw new Exception\InvalidArgumentException(
+ 'Character encoding settings cannot be changed once the Helper has been used or '
+ . ' if a Zend\Escaper\Escaper object (with preset encoding option) is set.'
+ );
+ }
+
+ $this->encoding = $encoding;
+
+ return $this;
+ }
+
+ /**
+ * Get the encoding to use for escape operations
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set instance of Escaper
+ *
+ * @param Escaper\Escaper $escaper
+ * @return AbstractHelper
+ */
+ public function setEscaper(Escaper\Escaper $escaper)
+ {
+ $this->escaper = $escaper;
+ $this->encoding = $escaper->getEncoding();
+
+ return $this;
+ }
+
+ /**
+ * Get instance of Escaper
+ *
+ * @return null|Escaper\Escaper
+ */
+ public function getEscaper()
+ {
+ if (null === $this->escaper) {
+ $this->setEscaper(new Escaper\Escaper($this->getEncoding()));
+ }
+
+ return $this->escaper;
+ }
+}
diff --git a/module/Zend/View/src/Helper/FlashMessenger.php b/module/Zend/View/src/Helper/FlashMessenger.php
new file mode 100644
index 00000000..bcbd2be5
--- /dev/null
+++ b/module/Zend/View/src/Helper/FlashMessenger.php
@@ -0,0 +1,353 @@
+ 'info',
+ 'error' => 'error',
+ 'success' => 'success',
+ 'default' => 'default',
+ 'warning' => 'warning',
+ ];
+
+ /**
+ * Templates for the open/close/separators for message tags
+ *
+ * @var string
+ */
+ protected $messageCloseString = '';
+ protected $messageOpenFormat = '';
+ protected $messageSeparatorString = ' ';
+
+ /**
+ * Flag whether to escape messages
+ *
+ * @var bool
+ */
+ protected $autoEscape = true;
+
+ /**
+ * Html escape helper
+ *
+ * @var EscapeHtml
+ */
+ protected $escapeHtmlHelper;
+
+ /**
+ * Flash messenger plugin
+ *
+ * @var V2PluginFlashMessenger|PluginFlashMessenger
+ */
+ protected $pluginFlashMessenger;
+
+ /**
+ * Returns the flash messenger plugin controller
+ *
+ * @param string|null $namespace
+ * @return FlashMessenger|V2PluginFlashMessenger|PluginFlashMessenger
+ */
+ public function __invoke($namespace = null)
+ {
+ if (null === $namespace) {
+ return $this;
+ }
+ $flashMessenger = $this->getPluginFlashMessenger();
+
+ return $flashMessenger->getMessagesFromNamespace($namespace);
+ }
+
+ /**
+ * Proxy the flash messenger plugin controller
+ *
+ * @param string $method
+ * @param array $argv
+ * @return mixed
+ */
+ public function __call($method, $argv)
+ {
+ $flashMessenger = $this->getPluginFlashMessenger();
+ return call_user_func_array([$flashMessenger, $method], $argv);
+ }
+
+ /**
+ * Render Messages
+ *
+ * @param string $namespace
+ * @param array $classes
+ * @param null|bool $autoEscape
+ * @return string
+ */
+ public function render($namespace = 'default', array $classes = [], $autoEscape = null)
+ {
+ $flashMessenger = $this->getPluginFlashMessenger();
+ $messages = $flashMessenger->getMessagesFromNamespace($namespace);
+ return $this->renderMessages($namespace, $messages, $classes, $autoEscape);
+ }
+
+ /**
+ * Render Current Messages
+ *
+ * @param string $namespace
+ * @param array $classes
+ * @param bool|null $autoEscape
+ * @return string
+ */
+ public function renderCurrent($namespace = 'default', array $classes = [], $autoEscape = null)
+ {
+ $flashMessenger = $this->getPluginFlashMessenger();
+ $messages = $flashMessenger->getCurrentMessagesFromNamespace($namespace);
+ return $this->renderMessages($namespace, $messages, $classes, $autoEscape);
+ }
+
+ /**
+ * Render Messages
+ *
+ * @param string $namespace
+ * @param array $messages
+ * @param array $classes
+ * @param bool|null $autoEscape
+ * @return string
+ */
+ protected function renderMessages(
+ $namespace = 'default',
+ array $messages = [],
+ array $classes = [],
+ $autoEscape = null
+ ) {
+ if (empty($messages)) {
+ return '';
+ }
+
+ // Prepare classes for opening tag
+ if (empty($classes)) {
+ if (isset($this->classMessages[$namespace])) {
+ $classes = $this->classMessages[$namespace];
+ } else {
+ $classes = $this->classMessages['default'];
+ }
+ $classes = [$classes];
+ }
+
+ if (null === $autoEscape) {
+ $autoEscape = $this->getAutoEscape();
+ }
+
+ // Flatten message array
+ $escapeHtml = $this->getEscapeHtmlHelper();
+ $messagesToPrint = [];
+ $translator = $this->getTranslator();
+ $translatorTextDomain = $this->getTranslatorTextDomain();
+ array_walk_recursive(
+ $messages,
+ function ($item) use (& $messagesToPrint, $escapeHtml, $autoEscape, $translator, $translatorTextDomain) {
+ if ($translator !== null) {
+ $item = $translator->translate(
+ $item,
+ $translatorTextDomain
+ );
+ }
+
+ if ($autoEscape) {
+ $messagesToPrint[] = $escapeHtml($item);
+ return;
+ }
+
+ $messagesToPrint[] = $item;
+ }
+ );
+
+ if (empty($messagesToPrint)) {
+ return '';
+ }
+
+ // Generate markup
+ $markup = sprintf($this->getMessageOpenFormat(), ' class="' . implode(' ', $classes) . '"');
+ $markup .= implode(
+ sprintf($this->getMessageSeparatorString(), ' class="' . implode(' ', $classes) . '"'),
+ $messagesToPrint
+ );
+ $markup .= $this->getMessageCloseString();
+ return $markup;
+ }
+
+ /**
+ * Set whether or not auto escaping should be used
+ *
+ * @param bool $autoEscape
+ * @return self
+ */
+ public function setAutoEscape($autoEscape = true)
+ {
+ $this->autoEscape = (bool) $autoEscape;
+ return $this;
+ }
+
+ /**
+ * Return whether auto escaping is enabled or disabled
+ *
+ * return bool
+ */
+ public function getAutoEscape()
+ {
+ return $this->autoEscape;
+ }
+
+ /**
+ * Set the string used to close message representation
+ *
+ * @param string $messageCloseString
+ * @return FlashMessenger
+ */
+ public function setMessageCloseString($messageCloseString)
+ {
+ $this->messageCloseString = (string) $messageCloseString;
+ return $this;
+ }
+
+ /**
+ * Get the string used to close message representation
+ *
+ * @return string
+ */
+ public function getMessageCloseString()
+ {
+ return $this->messageCloseString;
+ }
+
+ /**
+ * Set the formatted string used to open message representation
+ *
+ * @param string $messageOpenFormat
+ * @return FlashMessenger
+ */
+ public function setMessageOpenFormat($messageOpenFormat)
+ {
+ $this->messageOpenFormat = (string) $messageOpenFormat;
+ return $this;
+ }
+
+ /**
+ * Get the formatted string used to open message representation
+ *
+ * @return string
+ */
+ public function getMessageOpenFormat()
+ {
+ return $this->messageOpenFormat;
+ }
+
+ /**
+ * Set the string used to separate messages
+ *
+ * @param string $messageSeparatorString
+ * @return FlashMessenger
+ */
+ public function setMessageSeparatorString($messageSeparatorString)
+ {
+ $this->messageSeparatorString = (string) $messageSeparatorString;
+ return $this;
+ }
+
+ /**
+ * Get the string used to separate messages
+ *
+ * @return string
+ */
+ public function getMessageSeparatorString()
+ {
+ return $this->messageSeparatorString;
+ }
+
+ /**
+ * Set the flash messenger plugin
+ *
+ * @param V2PluginFlashMessenger|PluginFlashMessenger $pluginFlashMessenger
+ * @return FlashMessenger
+ * @throws InvalidArgumentException for an invalid $pluginFlashMessenger
+ */
+ public function setPluginFlashMessenger($pluginFlashMessenger)
+ {
+ if (! $pluginFlashMessenger instanceof V2PluginFlashMessenger
+ && ! $pluginFlashMessenger instanceof PluginFlashMessenger
+ ) {
+ throw new InvalidArgumentException(sprintf(
+ '%s expects either a %s or %s instance; received %s',
+ __METHOD__,
+ V2PluginFlashMessenger::class,
+ PluginFlashMessenger::class,
+ (is_object($pluginFlashMessenger) ? get_class($pluginFlashMessenger) : gettype($pluginFlashMessenger))
+ ));
+ }
+
+ $this->pluginFlashMessenger = $pluginFlashMessenger;
+ return $this;
+ }
+
+ /**
+ * Get the flash messenger plugin
+ *
+ * @return V2PluginFlashMessenger|PluginFlashMessenger
+ */
+ public function getPluginFlashMessenger()
+ {
+ if (null === $this->pluginFlashMessenger) {
+ $this->setPluginFlashMessenger(
+ class_exists(PluginFlashMessenger::class)
+ ? new PluginFlashMessenger()
+ : new V2PluginFlashMessenger()
+ );
+ }
+
+ return $this->pluginFlashMessenger;
+ }
+
+ /**
+ * Retrieve the escapeHtml helper
+ *
+ * @return EscapeHtml
+ */
+ protected function getEscapeHtmlHelper()
+ {
+ if ($this->escapeHtmlHelper) {
+ return $this->escapeHtmlHelper;
+ }
+
+ if (method_exists($this->getView(), 'plugin')) {
+ $this->escapeHtmlHelper = $this->view->plugin('escapehtml');
+ }
+
+ if (! $this->escapeHtmlHelper instanceof EscapeHtml) {
+ $this->escapeHtmlHelper = new EscapeHtml();
+ }
+
+ return $this->escapeHtmlHelper;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Gravatar.php b/module/Zend/View/src/Helper/Gravatar.php
new file mode 100644
index 00000000..ca7b3c11
--- /dev/null
+++ b/module/Zend/View/src/Helper/Gravatar.php
@@ -0,0 +1,402 @@
+ 80,
+ 'default_img' => self::DEFAULT_MM,
+ 'rating' => self::RATING_G,
+ 'secure' => null,
+ ];
+
+ /**
+ * Returns an avatar from gravatar's service.
+ *
+ * $options may include the following:
+ * - 'img_size' int height of img to return
+ * - 'default_img' string img to return if email address has not found
+ * - 'rating' string rating parameter for avatar
+ * - 'secure' bool load from the SSL or Non-SSL location
+ *
+ * @see http://pl.gravatar.com/site/implement/url
+ * @see http://pl.gravatar.com/site/implement/url More information about gravatar's service.
+ * @param string|null $email Email address.
+ * @param null|array $options Options
+ * @param array $attributes Attributes for image tag (title, alt etc.)
+ * @return Gravatar
+ */
+ public function __invoke($email = "", $options = [], $attributes = [])
+ {
+ if (! empty($email)) {
+ $this->setEmail($email);
+ }
+ if (! empty($options)) {
+ $this->setOptions($options);
+ }
+ if (! empty($attributes)) {
+ $this->setAttributes($attributes);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return valid image tag
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getImgTag();
+ }
+
+ /**
+ * Configure state
+ *
+ * @param array $options
+ * @return Gravatar
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ $method = 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
+ if (method_exists($this, $method)) {
+ $this->{$method}($value);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get avatar url (including size, rating and default image options)
+ *
+ * @return string
+ */
+ protected function getAvatarUrl()
+ {
+ $src = $this->getGravatarUrl()
+ . '/' . ($this->emailIsHashed ? $this->getEmail() : md5($this->getEmail()))
+ . '?s=' . $this->getImgSize()
+ . '&d=' . $this->getDefaultImg()
+ . '&r=' . $this->getRating();
+ return $src;
+ }
+
+ /**
+ * Get URL to gravatar's service.
+ *
+ * @return string URL
+ */
+ protected function getGravatarUrl()
+ {
+ return ($this->getSecure() === false) ? self::GRAVATAR_URL : self::GRAVATAR_URL_SECURE;
+ }
+
+ /**
+ * Return valid image tag
+ *
+ * @return string
+ */
+ public function getImgTag()
+ {
+ $this->setSrcAttribForImg();
+ $html = ' htmlAttribs($this->getAttributes())
+ . $this->getClosingBracket();
+
+ return $html;
+ }
+
+ /**
+ * Set attributes for image tag
+ *
+ * Warning! You shouldn't set src attribute for image tag.
+ * This attribute is overwritten in protected method setSrcAttribForImg().
+ * This method(_setSrcAttribForImg) is called in public method getImgTag().
+ *
+ * @param array $attributes
+ * @return Gravatar
+ */
+ public function setAttributes(array $attributes)
+ {
+ $this->attributes = $attributes;
+ return $this;
+ }
+
+ /**
+ * Set attribs for image tag
+ *
+ * @param array $attribs
+ * @return Gravatar
+ *
+ * @deprecated Please use Zend\View\Helper\Gravatar::setAttributes
+ */
+ public function setAttribs(array $attribs)
+ {
+ trigger_error(sprintf(
+ '%s is deprecated; please use %s::setAttributes',
+ __METHOD__,
+ __CLASS__
+ ), E_USER_DEPRECATED);
+
+ $this->setAttributes($attribs);
+ return $this;
+ }
+
+ /**
+ * Get attributes of image
+ *
+ * Warning!
+ * If you set src attribute, you get it, but this value will be overwritten in
+ * protected method setSrcAttribForImg(). And finally your get other src
+ * value!
+ *
+ * @return array
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Get attribs of image
+ *
+ * Warning!
+ * If you set src attrib, you get it, but this value will be overwritten in
+ * protected method setSrcAttribForImg(). And finally your get other src
+ * value!
+ *
+ * @return array
+ *
+ * @deprecated Please use Zend\View\Helper\Gravatar::getAttributes
+ */
+ public function getAttribs()
+ {
+ trigger_error(sprintf(
+ '%s is deprecated; please use %s::getAttributes',
+ __METHOD__,
+ __CLASS__
+ ), E_USER_DEPRECATED);
+
+ return $this->getAttributes();
+ }
+
+ /**
+ * Set default img
+ *
+ * Can be either an absolute URL to an image, or one of the DEFAULT_* constants
+ *
+ * @link http://pl.gravatar.com/site/implement/url More information about default image.
+ * @param string $defaultImg
+ * @return Gravatar
+ */
+ public function setDefaultImg($defaultImg)
+ {
+ $this->options['default_img'] = urlencode($defaultImg);
+ return $this;
+ }
+
+ /**
+ * Get default img
+ *
+ * @return string
+ */
+ public function getDefaultImg()
+ {
+ return $this->options['default_img'];
+ }
+
+ /**
+ * Set email address
+ *
+ * @param string $email
+ * @return Gravatar
+ */
+ public function setEmail($email)
+ {
+ $this->emailIsHashed = (bool) preg_match('/^[A-Za-z0-9]{32}$/', $email);
+ $this->email = strtolower(trim($email));
+ return $this;
+ }
+
+ /**
+ * Get email address
+ *
+ * @return string
+ */
+ public function getEmail()
+ {
+ return $this->email;
+ }
+
+ /**
+ * Set img size in pixels
+ *
+ * @param int $imgSize Size of img must be between 1 and 512
+ * @return Gravatar
+ */
+ public function setImgSize($imgSize)
+ {
+ $this->options['img_size'] = (int) $imgSize;
+ return $this;
+ }
+
+ /**
+ * Get img size
+ *
+ * @return int The img size
+ */
+ public function getImgSize()
+ {
+ return $this->options['img_size'];
+ }
+
+ /**
+ * Set rating value
+ *
+ * Must be one of the RATING_* constants
+ *
+ * @link http://pl.gravatar.com/site/implement/url More information about rating.
+ * @param string $rating Value for rating. Allowed values are: g, px, r,x
+ * @return Gravatar
+ * @throws Exception\DomainException
+ */
+ public function setRating($rating)
+ {
+ switch ($rating) {
+ case self::RATING_G:
+ case self::RATING_PG:
+ case self::RATING_R:
+ case self::RATING_X:
+ $this->options['rating'] = $rating;
+ break;
+ default:
+ throw new Exception\DomainException(sprintf(
+ 'The rating value "%s" is not allowed',
+ $rating
+ ));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get rating value
+ *
+ * @return string
+ */
+ public function getRating()
+ {
+ return $this->options['rating'];
+ }
+
+ /**
+ * Load from an SSL or No-SSL location?
+ *
+ * @param bool $flag
+ * @return Gravatar
+ */
+ public function setSecure($flag)
+ {
+ $this->options['secure'] = ($flag === null) ? null : (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get an SSL or a No-SSL location
+ *
+ * @return bool
+ */
+ public function getSecure()
+ {
+ if ($this->options['secure'] === null) {
+ return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
+ }
+
+ return $this->options['secure'];
+ }
+
+ /**
+ * Set src attrib for image.
+ *
+ * You shouldn't set an own url value!
+ * It sets value, uses protected method getAvatarUrl.
+ *
+ * If already exists, it will be overwritten.
+ *
+ * @return void
+ */
+ protected function setSrcAttribForImg()
+ {
+ $attributes = $this->getAttributes();
+ $attributes['src'] = $this->getAvatarUrl();
+ $this->setAttributes($attributes);
+ }
+}
diff --git a/module/Zend/View/src/Helper/HeadLink.php b/module/Zend/View/src/Helper/HeadLink.php
new file mode 100644
index 00000000..4a1f955f
--- /dev/null
+++ b/module/Zend/View/src/Helper/HeadLink.php
@@ -0,0 +1,505 @@
+setSeparator(PHP_EOL);
+ }
+
+ /**
+ * Proxy to __invoke()
+ *
+ * Allows calling $helper->headLink(), but, more importantly, chaining calls
+ * like ->appendStylesheet()->headLink().
+ *
+ * @param array $attributes
+ * @param string $placement
+ * @return HeadLink
+ */
+ public function headLink(array $attributes = null, $placement = Placeholder\Container\AbstractContainer::APPEND)
+ {
+ return call_user_func_array([$this, '__invoke'], func_get_args());
+ }
+
+ /**
+ * headLink() - View Helper Method
+ *
+ * Returns current object instance. Optionally, allows passing array of
+ * values to build link.
+ *
+ * @param array $attributes
+ * @param string $placement
+ * @return HeadLink
+ */
+ public function __invoke(array $attributes = null, $placement = Placeholder\Container\AbstractContainer::APPEND)
+ {
+ if (null !== $attributes) {
+ $item = $this->createData($attributes);
+ switch ($placement) {
+ case Placeholder\Container\AbstractContainer::SET:
+ $this->set($item);
+ break;
+ case Placeholder\Container\AbstractContainer::PREPEND:
+ $this->prepend($item);
+ break;
+ case Placeholder\Container\AbstractContainer::APPEND:
+ default:
+ $this->append($item);
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overload method access
+ *
+ * Items that may be added in the future:
+ * - Navigation? need to find docs on this
+ * - public function appendStart()
+ * - public function appendContents()
+ * - public function appendPrev()
+ * - public function appendNext()
+ * - public function appendIndex()
+ * - public function appendEnd()
+ * - public function appendGlossary()
+ * - public function appendAppendix()
+ * - public function appendHelp()
+ * - public function appendBookmark()
+ * - Other?
+ * - public function appendCopyright()
+ * - public function appendChapter()
+ * - public function appendSection()
+ * - public function appendSubsection()
+ *
+ * @param mixed $method
+ * @param mixed $args
+ * @throws Exception\BadMethodCallException
+ * @return void
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match(
+ '/^(?Pset|(ap|pre)pend|offsetSet)(?PStylesheet|Alternate|Prev|Next)$/',
+ $method,
+ $matches
+ )) {
+ $argc = count($args);
+ $action = $matches['action'];
+ $type = $matches['type'];
+ $index = null;
+
+ if ('offsetSet' == $action) {
+ if (0 < $argc) {
+ $index = array_shift($args);
+ --$argc;
+ }
+ }
+
+ if (1 > $argc) {
+ throw new Exception\BadMethodCallException(
+ sprintf('%s requires at least one argument', $method)
+ );
+ }
+
+ if (is_array($args[0])) {
+ $item = $this->createData($args[0]);
+ } else {
+ $dataMethod = 'createData' . $type;
+ $item = $this->$dataMethod($args);
+ }
+
+ if ($item) {
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+ }
+
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Check if value is valid
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ protected function isValid($value)
+ {
+ if (! $value instanceof stdClass) {
+ return false;
+ }
+
+ $vars = get_object_vars($value);
+ $keys = array_keys($vars);
+ $intersection = array_intersect($this->itemKeys, $keys);
+ if (empty($intersection)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * append()
+ *
+ * @param array $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function append($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'append() expects a data token; please use one of the custom append*() methods'
+ );
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * offsetSet()
+ *
+ * @param string|int $index
+ * @param array $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function offsetSet($index, $value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'offsetSet() expects a data token; please use one of the custom offsetSet*() methods'
+ );
+ }
+
+ return $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * prepend()
+ *
+ * @param array $value
+ * @throws Exception\InvalidArgumentException
+ * @return HeadLink
+ */
+ public function prepend($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'prepend() expects a data token; please use one of the custom prepend*() methods'
+ );
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * set()
+ *
+ * @param array $value
+ * @throws Exception\InvalidArgumentException
+ * @return HeadLink
+ */
+ public function set($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'set() expects a data token; please use one of the custom set*() methods'
+ );
+ }
+
+ return $this->getContainer()->set($value);
+ }
+
+ /**
+ * Create HTML link element from data item
+ *
+ * @param stdClass $item
+ * @return string
+ */
+ public function itemToString(stdClass $item)
+ {
+ $attributes = (array) $item;
+ $link = ' itemKeys as $itemKey) {
+ if (isset($attributes[$itemKey])) {
+ if (is_array($attributes[$itemKey])) {
+ foreach ($attributes[$itemKey] as $key => $value) {
+ $link .= sprintf(
+ ' %s="%s"',
+ $key,
+ ($this->autoEscape) ? $this->escapeAttribute($value) : $value
+ );
+ }
+ } else {
+ $link .= sprintf(
+ ' %s="%s"',
+ $itemKey,
+ ($this->autoEscape) ? $this->escapeAttribute($attributes[$itemKey]) : $attributes[$itemKey]
+ );
+ }
+ }
+ }
+
+ if (method_exists($this->view, 'plugin')) {
+ $link .= ($this->view->plugin('doctype')->isXhtml()) ? ' />' : '>';
+ } else {
+ $link .= ' />';
+ }
+
+ if (($link == ' ') || ($link == ' ')) {
+ return '';
+ }
+
+ if (isset($attributes['conditionalStylesheet'])
+ && ! empty($attributes['conditionalStylesheet'])
+ && is_string($attributes['conditionalStylesheet'])
+ ) {
+ // inner wrap with comment end and start if !IE
+ if (str_replace(' ', '', $attributes['conditionalStylesheet']) === '!IE') {
+ $link = '' . $link . '';
+ }
+
+ return $link;
+ }
+
+ /**
+ * Render link elements as string
+ *
+ * @param string|int $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = [];
+ $this->getContainer()->ksort();
+ foreach ($this as $item) {
+ $items[] = $this->itemToString($item);
+ }
+
+ return $indent . implode($this->escape($this->getSeparator()) . $indent, $items);
+ }
+
+ /**
+ * Create data item for stack
+ *
+ * @param array $attributes
+ * @return stdClass
+ */
+ public function createData(array $attributes)
+ {
+ return (object) $attributes;
+ }
+
+ /**
+ * Create item for stylesheet link item
+ *
+ * @param array $args
+ * @return stdClass|false Returns false if stylesheet is a duplicate
+ */
+ public function createDataStylesheet(array $args)
+ {
+ $rel = 'stylesheet';
+ $type = 'text/css';
+ $media = 'screen';
+ $conditionalStylesheet = false;
+ $href = array_shift($args);
+
+ if ($this->isDuplicateStylesheet($href)) {
+ return false;
+ }
+
+ if ($args) {
+ $media = array_shift($args);
+ if (is_array($media)) {
+ $media = implode(',', $media);
+ } else {
+ $media = (string) $media;
+ }
+ }
+ if ($args) {
+ $conditionalStylesheet = array_shift($args);
+ if (! empty($conditionalStylesheet) && is_string($conditionalStylesheet)) {
+ $conditionalStylesheet = (string) $conditionalStylesheet;
+ } else {
+ $conditionalStylesheet = null;
+ }
+ }
+
+ if ($args && is_array($args[0])) {
+ $extras = array_shift($args);
+ $extras = (array) $extras;
+ } else {
+ $extras = [];
+ }
+
+ $attributes = compact('rel', 'type', 'href', 'media', 'conditionalStylesheet', 'extras');
+
+ return $this->createData($attributes);
+ }
+
+ /**
+ * Is the linked stylesheet a duplicate?
+ *
+ * @param string $uri
+ * @return bool
+ */
+ protected function isDuplicateStylesheet($uri)
+ {
+ foreach ($this->getContainer() as $item) {
+ if (($item->rel == 'stylesheet') && ($item->href == $uri)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Create item for alternate link item
+ *
+ * @param array $args
+ * @throws Exception\InvalidArgumentException
+ * @return stdClass
+ */
+ public function createDataAlternate(array $args)
+ {
+ if (3 > count($args)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Alternate tags require 3 arguments; %s provided',
+ count($args)
+ ));
+ }
+
+ $rel = 'alternate';
+ $href = array_shift($args);
+ $type = array_shift($args);
+ $title = array_shift($args);
+
+ if ($args && is_array($args[0])) {
+ $extras = array_shift($args);
+ $extras = (array) $extras;
+
+ if (isset($extras['media']) && is_array($extras['media'])) {
+ $extras['media'] = implode(',', $extras['media']);
+ }
+ } else {
+ $extras = [];
+ }
+
+ $href = (string) $href;
+ $type = (string) $type;
+ $title = (string) $title;
+
+ $attributes = compact('rel', 'href', 'type', 'title', 'extras');
+
+ return $this->createData($attributes);
+ }
+
+ /**
+ * Create item for a prev relationship (mainly used for pagination)
+ *
+ * @param array $args
+ * @return stdClass
+ */
+ public function createDataPrev(array $args)
+ {
+ $rel = 'prev';
+ $href = (string) array_shift($args);
+
+ $attributes = compact('rel', 'href');
+
+ return $this->createData($attributes);
+ }
+
+ /**
+ * Create item for a prev relationship (mainly used for pagination)
+ *
+ * @param array $args
+ * @return stdClass
+ */
+ public function createDataNext(array $args)
+ {
+ $rel = 'next';
+ $href = (string) array_shift($args);
+
+ $attributes = compact('rel', 'href');
+
+ return $this->createData($attributes);
+ }
+}
diff --git a/module/Zend/View/src/Helper/HeadMeta.php b/module/Zend/View/src/Helper/HeadMeta.php
new file mode 100644
index 00000000..194b5fd4
--- /dev/null
+++ b/module/Zend/View/src/Helper/HeadMeta.php
@@ -0,0 +1,482 @@
+setSeparator(PHP_EOL);
+ }
+
+ /**
+ * Retrieve object instance; optionally add meta tag
+ *
+ * @param string $content
+ * @param string $keyValue
+ * @param string $keyType
+ * @param array $modifiers
+ * @param string $placement
+ * @return HeadMeta
+ */
+ public function __invoke(
+ $content = null,
+ $keyValue = null,
+ $keyType = 'name',
+ $modifiers = [],
+ $placement = Placeholder\Container\AbstractContainer::APPEND
+ ) {
+ if ((null !== $content) && (null !== $keyValue)) {
+ $item = $this->createData($keyType, $keyValue, $content, $modifiers);
+ $action = strtolower($placement);
+ switch ($action) {
+ case 'append':
+ case 'prepend':
+ case 'set':
+ $this->$action($item);
+ break;
+ default:
+ $this->append($item);
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overload method access
+ *
+ * @param string $method
+ * @param array $args
+ * @throws Exception\BadMethodCallException
+ * @return HeadMeta
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match(
+ '/^(?Pset|(pre|ap)pend|offsetSet)(?PName|HttpEquiv|Property|Itemprop)$/',
+ $method,
+ $matches
+ )) {
+ $action = $matches['action'];
+ $type = $this->normalizeType($matches['type']);
+ $argc = count($args);
+ $index = null;
+
+ if ('offsetSet' == $action) {
+ if (0 < $argc) {
+ $index = array_shift($args);
+ --$argc;
+ }
+ }
+
+ if (2 > $argc) {
+ throw new Exception\BadMethodCallException(
+ 'Too few arguments provided; requires key value, and content'
+ );
+ }
+
+ if (3 > $argc) {
+ $args[] = [];
+ }
+
+ $item = $this->createData($type, $args[0], $args[1], $args[2]);
+
+ if ('offsetSet' == $action) {
+ return $this->offsetSet($index, $item);
+ }
+
+ $this->$action($item);
+
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Render placeholder as string
+ *
+ * @param string|int $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = [];
+ $this->getContainer()->ksort();
+
+ $isHtml5 = $this->view->plugin('doctype')->isHtml5();
+
+ try {
+ foreach ($this as $item) {
+ $content = $this->itemToString($item);
+
+ if ($isHtml5 && $item->type == 'charset') {
+ array_unshift($items, $content);
+ continue;
+ }
+
+ $items[] = $content;
+ }
+ } catch (Exception\InvalidArgumentException $e) {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ return '';
+ }
+
+ return $indent . implode($this->escape($this->getSeparator()) . $indent, $items);
+ }
+
+ /**
+ * Create data item for inserting into stack
+ *
+ * @param string $type
+ * @param string $typeValue
+ * @param string $content
+ * @param array $modifiers
+ * @return stdClass
+ */
+ public function createData($type, $typeValue, $content, array $modifiers)
+ {
+ $data = new stdClass;
+ $data->type = $type;
+ $data->$type = $typeValue;
+ $data->content = $content;
+ $data->modifiers = $modifiers;
+
+ return $data;
+ }
+
+ /**
+ * Build meta HTML string
+ *
+ * @param stdClass $item
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public function itemToString(stdClass $item)
+ {
+ if (! in_array($item->type, $this->typeKeys)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid type "%s" provided for meta',
+ $item->type
+ ));
+ }
+ $type = $item->type;
+
+ $modifiersString = '';
+ foreach ($item->modifiers as $key => $value) {
+ if ($this->view->plugin('doctype')->isHtml5()
+ && $key == 'scheme'
+ ) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid modifier "scheme" provided; not supported by HTML5'
+ );
+ }
+ if (! in_array($key, $this->modifierKeys)) {
+ continue;
+ }
+ $modifiersString .= sprintf('%s="%s"', $key, $this->autoEscape ? $this->escapeAttribute($value) : $value);
+ }
+
+ $modifiersString = rtrim($modifiersString);
+
+ if ('' != $modifiersString) {
+ $modifiersString = ' ' . $modifiersString;
+ }
+
+ if (method_exists($this->view, 'plugin')) {
+ if ($this->view->plugin('doctype')->isHtml5()
+ && $type == 'charset'
+ ) {
+ $tpl = ($this->view->plugin('doctype')->isXhtml())
+ ? ' '
+ : ' ';
+ } elseif ($this->view->plugin('doctype')->isXhtml()) {
+ $tpl = ' ';
+ } else {
+ $tpl = ' ';
+ }
+ } else {
+ $tpl = ' ';
+ }
+
+ $meta = sprintf(
+ $tpl,
+ $type,
+ $this->autoEscape ? $this->escapeAttribute($item->$type) : $item->$type,
+ $this->autoEscape ? $this->escapeAttribute($item->content) : $item->content,
+ $modifiersString
+ );
+
+ if (isset($item->modifiers['conditional'])
+ && ! empty($item->modifiers['conditional'])
+ && is_string($item->modifiers['conditional'])
+ ) {
+ // inner wrap with comment end and start if !IE
+ if (str_replace(' ', '', $item->modifiers['conditional']) === '!IE') {
+ $meta = '' . $meta . '';
+ }
+
+ return $meta;
+ }
+
+ /**
+ * Normalize type attribute of meta
+ *
+ * @param string $type type in CamelCase
+ * @throws Exception\DomainException
+ * @return string
+ */
+ protected function normalizeType($type)
+ {
+ switch ($type) {
+ case 'Name':
+ return 'name';
+ case 'HttpEquiv':
+ return 'http-equiv';
+ case 'Property':
+ return 'property';
+ case 'Itemprop':
+ return 'itemprop';
+ default:
+ throw new Exception\DomainException(sprintf(
+ 'Invalid type "%s" passed to normalizeType',
+ $type
+ ));
+ }
+ }
+
+ /**
+ * Determine if item is valid
+ *
+ * @param stdClass $item
+ * @return bool
+ */
+ protected function isValid($item)
+ {
+ if ((! $item instanceof stdClass)
+ || ! isset($item->type)
+ || ! isset($item->modifiers)
+ ) {
+ return false;
+ }
+
+ $doctype = $this->view->plugin('doctype');
+ if ($item->type === 'charset' && $doctype->isXhtml()) {
+ return false;
+ }
+
+ if (! isset($item->content)
+ && (! $doctype->isHtml5()
+ || (! $doctype->isHtml5() && $item->type !== 'charset'))
+ ) {
+ return false;
+ }
+
+ // is only supported with doctype html
+ if (! $doctype->isHtml5()
+ && $item->type === 'itemprop'
+ ) {
+ return false;
+ }
+
+ // is only supported with doctype RDFa
+ if (! $doctype->isRdfa()
+ && $item->type === 'property'
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Append
+ *
+ * @param stdClass $value
+ * @return View\Helper\Placeholder\Container\AbstractContainer
+ * @throws Exception\InvalidArgumentException
+ */
+ public function append($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to append'
+ );
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * OffsetSet
+ *
+ * @param string|int $index
+ * @param string $value
+ * @throws Exception\InvalidArgumentException
+ */
+ public function offsetSet($index, $value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to offsetSet; please use offsetSetName() or offsetSetHttpEquiv()'
+ );
+ }
+
+ return $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * OffsetUnset
+ *
+ * @param string|int $index
+ * @throws Exception\InvalidArgumentException
+ */
+ public function offsetUnset($index)
+ {
+ if (! in_array($index, $this->getContainer()->getKeys())) {
+ throw new Exception\InvalidArgumentException('Invalid index passed to offsetUnset()');
+ }
+
+ return $this->getContainer()->offsetUnset($index);
+ }
+
+ /**
+ * Prepend
+ *
+ * @param stdClass $value
+ * @throws Exception\InvalidArgumentException
+ * @return View\Helper\Placeholder\Container\AbstractContainer
+ */
+ public function prepend($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to prepend'
+ );
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * Set
+ *
+ * @param stdClass $value
+ * @throws Exception\InvalidArgumentException
+ * @return View\Helper\Placeholder\Container\AbstractContainer
+ */
+ public function set($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException('Invalid value passed to set');
+ }
+
+ $container = $this->getContainer();
+ foreach ($container->getArrayCopy() as $index => $item) {
+ if ($item->type == $value->type && $item->{$item->type} == $value->{$value->type}) {
+ $this->offsetUnset($index);
+ }
+ }
+
+ return $this->append($value);
+ }
+
+ /**
+ * Create an HTML5-style meta charset tag. Something like
+ *
+ * Not valid in a non-HTML5 doctype
+ *
+ * @param string $charset
+ * @param Exception\InvalidArgumentException
+ * @return HeadMeta Provides a fluent interface
+ */
+ public function setCharset($charset)
+ {
+ $item = new stdClass;
+ $item->type = 'charset';
+ $item->charset = $charset;
+ $item->content = null;
+ $item->modifiers = [];
+
+ if (! $this->isValid($item)) {
+ throw new Exception\InvalidArgumentException(
+ 'XHTML* doctype has no attribute charset; please use appendHttpEquiv()'
+ );
+ }
+
+ $this->set($item);
+
+ return $this;
+ }
+}
diff --git a/module/Zend/View/src/Helper/HeadScript.php b/module/Zend/View/src/Helper/HeadScript.php
new file mode 100644
index 00000000..374fbb72
--- /dev/null
+++ b/module/Zend/View/src/Helper/HeadScript.php
@@ -0,0 +1,541 @@
+setSeparator(PHP_EOL);
+ }
+
+ /**
+ * Return headScript object
+ *
+ * Returns headScript helper object; optionally, allows specifying a script
+ * or script file to include.
+ *
+ * @param string $mode Script or file
+ * @param string $spec Script/url
+ * @param string $placement Append, prepend, or set
+ * @param array $attrs Array of script attributes
+ * @param string $type Script type and/or array of script attributes
+ * @return HeadScript
+ */
+ public function __invoke(
+ $mode = self::FILE,
+ $spec = null,
+ $placement = 'APPEND',
+ array $attrs = [],
+ $type = 'text/javascript'
+ ) {
+ if ((null !== $spec) && is_string($spec)) {
+ $action = ucfirst(strtolower($mode));
+ $placement = strtolower($placement);
+ switch ($placement) {
+ case 'set':
+ case 'prepend':
+ case 'append':
+ $action = $placement . $action;
+ break;
+ default:
+ $action = 'append' . $action;
+ break;
+ }
+ $this->$action($spec, $type, $attrs);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overload method access
+ *
+ * @param string $method Method to call
+ * @param array $args Arguments of method
+ * @throws Exception\BadMethodCallException if too few arguments or invalid method
+ * @return HeadScript
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match('/^(?Pset|(ap|pre)pend|offsetSet)(?PFile|Script)$/', $method, $matches)) {
+ if (1 > count($args)) {
+ throw new Exception\BadMethodCallException(sprintf(
+ 'Method "%s" requires at least one argument',
+ $method
+ ));
+ }
+
+ $action = $matches['action'];
+ $mode = strtolower($matches['mode']);
+ $type = 'text/javascript';
+ $attrs = [];
+
+ if ('offsetSet' == $action) {
+ $index = array_shift($args);
+ if (1 > count($args)) {
+ throw new Exception\BadMethodCallException(sprintf(
+ 'Method "%s" requires at least two arguments, an index and source',
+ $method
+ ));
+ }
+ }
+
+ $content = $args[0];
+
+ if (isset($args[1])) {
+ $type = (string) $args[1];
+ }
+ if (isset($args[2])) {
+ $attrs = (array) $args[2];
+ }
+
+ switch ($mode) {
+ case 'script':
+ $item = $this->createData($type, $attrs, $content);
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+ break;
+ case 'file':
+ default:
+ if (! $this->isDuplicate($content)) {
+ $attrs['src'] = $content;
+ $item = $this->createData($type, $attrs);
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+ }
+ break;
+ }
+
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Retrieve string representation
+ *
+ * @param string|int $indent Amount of whitespaces or string to use for indention
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ if ($this->view) {
+ $useCdata = $this->view->plugin('doctype')->isXhtml();
+ } else {
+ $useCdata = $this->useCdata;
+ }
+
+ $escapeStart = ($useCdata) ? '//' : '//-->';
+
+ $items = [];
+ $this->getContainer()->ksort();
+ foreach ($this as $item) {
+ if (! $this->isValid($item)) {
+ continue;
+ }
+
+ $items[] = $this->itemToString($item, $indent, $escapeStart, $escapeEnd);
+ }
+
+ return implode($this->getSeparator(), $items);
+ }
+
+ /**
+ * Start capture action
+ *
+ * @param mixed $captureType Type of capture
+ * @param string $type Type of script
+ * @param array $attrs Attributes of capture
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function captureStart(
+ $captureType = Placeholder\Container\AbstractContainer::APPEND,
+ $type = 'text/javascript',
+ $attrs = []
+ ) {
+ if ($this->captureLock) {
+ throw new Exception\RuntimeException('Cannot nest headScript captures');
+ }
+
+ $this->captureLock = true;
+ $this->captureType = $captureType;
+ $this->captureScriptType = $type;
+ $this->captureScriptAttrs = $attrs;
+ ob_start();
+ }
+
+ /**
+ * End capture action and store
+ *
+ * @return void
+ */
+ public function captureEnd()
+ {
+ $content = ob_get_clean();
+ $type = $this->captureScriptType;
+ $attrs = $this->captureScriptAttrs;
+ $this->captureScriptType = null;
+ $this->captureScriptAttrs = null;
+ $this->captureLock = false;
+
+ switch ($this->captureType) {
+ case Placeholder\Container\AbstractContainer::SET:
+ case Placeholder\Container\AbstractContainer::PREPEND:
+ case Placeholder\Container\AbstractContainer::APPEND:
+ $action = strtolower($this->captureType) . 'Script';
+ break;
+ default:
+ $action = 'appendScript';
+ break;
+ }
+
+ $this->$action($content, $type, $attrs);
+ }
+
+ /**
+ * Create data item containing all necessary components of script
+ *
+ * @param string $type Type of data
+ * @param array $attributes Attributes of data
+ * @param string $content Content of data
+ * @return stdClass
+ */
+ public function createData($type, array $attributes, $content = null)
+ {
+ $data = new stdClass();
+ $data->type = $type;
+ $data->attributes = $attributes;
+ $data->source = $content;
+
+ return $data;
+ }
+
+ /**
+ * Is the file specified a duplicate?
+ *
+ * @param string $file Name of file to check
+ * @return bool
+ */
+ protected function isDuplicate($file)
+ {
+ foreach ($this->getContainer() as $item) {
+ if (($item->source === null)
+ && array_key_exists('src', $item->attributes)
+ && ($file == $item->attributes['src'])
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Is the script provided valid?
+ *
+ * @param mixed $value Is the given script valid?
+ * @return bool
+ */
+ protected function isValid($value)
+ {
+ if ((! $value instanceof stdClass)
+ || ! isset($value->type)
+ || (! isset($value->source)
+ && ! isset($value->attributes))
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Create script HTML
+ *
+ * @param mixed $item Item to convert
+ * @param string $indent String to add before the item
+ * @param string $escapeStart Starting sequence
+ * @param string $escapeEnd Ending sequence
+ * @return string
+ */
+ public function itemToString($item, $indent, $escapeStart, $escapeEnd)
+ {
+ $attrString = '';
+ if (! empty($item->attributes)) {
+ foreach ($item->attributes as $key => $value) {
+ if ((! $this->arbitraryAttributesAllowed() && ! in_array($key, $this->optionalAttributes))
+ || in_array($key, ['conditional', 'noescape'])) {
+ continue;
+ }
+ if ('defer' == $key) {
+ $value = 'defer';
+ }
+ if ('async' == $key) {
+ $value = 'async';
+ }
+ $attrString .= sprintf(
+ ' %s="%s"',
+ $key,
+ ($this->autoEscape) ? $this->escapeAttribute($value) : $value
+ );
+ }
+ }
+
+ $addScriptEscape = ! (isset($item->attributes['noescape'])
+ && filter_var($item->attributes['noescape'], FILTER_VALIDATE_BOOLEAN));
+
+ if (empty($item->type) && $this->view && $this->view->plugin('doctype')->isHtml5()) {
+ $html = '';
+
+ if (isset($item->attributes['conditional'])
+ && ! empty($item->attributes['conditional'])
+ && is_string($item->attributes['conditional'])
+ ) {
+ // inner wrap with comment end and start if !IE
+ if (str_replace(' ', '', $item->attributes['conditional']) === '!IE') {
+ $html = '' . $html . '';
+ } else {
+ $html = $indent . $html;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Override append
+ *
+ * @param string $value Append script or file
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function append($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid argument passed to append(); '
+ . 'please use one of the helper methods, appendScript() or appendFile()'
+ );
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * Override prepend
+ *
+ * @param string $value Prepend script or file
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function prepend($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid argument passed to prepend(); '
+ . 'please use one of the helper methods, prependScript() or prependFile()'
+ );
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * Override set
+ *
+ * @param string $value Set script or file
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function set($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid argument passed to set(); please use one of the helper methods, setScript() or setFile()'
+ );
+ }
+
+ return $this->getContainer()->set($value);
+ }
+
+ /**
+ * Override offsetSet
+ *
+ * @param string|int $index Set script of file offset
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function offsetSet($index, $value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid argument passed to offsetSet(); '
+ . 'please use one of the helper methods, offsetSetScript() or offsetSetFile()'
+ );
+ }
+
+ return $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * Set flag indicating if arbitrary attributes are allowed
+ *
+ * @param bool $flag Set flag
+ * @return HeadScript
+ */
+ public function setAllowArbitraryAttributes($flag)
+ {
+ $this->arbitraryAttributes = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Are arbitrary attributes allowed?
+ *
+ * @return bool
+ */
+ public function arbitraryAttributesAllowed()
+ {
+ return $this->arbitraryAttributes;
+ }
+}
diff --git a/module/Zend/View/src/Helper/HeadStyle.php b/module/Zend/View/src/Helper/HeadStyle.php
new file mode 100644
index 00000000..50a670e1
--- /dev/null
+++ b/module/Zend/View/src/Helper/HeadStyle.php
@@ -0,0 +1,406 @@
+setSeparator(PHP_EOL);
+ }
+
+ /**
+ * Return headStyle object
+ *
+ * Returns headStyle helper object; optionally, allows specifying
+ *
+ * @param string $content Stylesheet contents
+ * @param string $placement Append, prepend, or set
+ * @param string|array $attributes Optional attributes to utilize
+ * @return HeadStyle
+ */
+ public function __invoke($content = null, $placement = 'APPEND', $attributes = [])
+ {
+ if ((null !== $content) && is_string($content)) {
+ switch (strtoupper($placement)) {
+ case 'SET':
+ $action = 'setStyle';
+ break;
+ case 'PREPEND':
+ $action = 'prependStyle';
+ break;
+ case 'APPEND':
+ default:
+ $action = 'appendStyle';
+ break;
+ }
+ $this->$action($content, $attributes);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overload method calls
+ *
+ * @param string $method
+ * @param array $args
+ * @throws Exception\BadMethodCallException When no $content provided or invalid method
+ * @return void
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match('/^(?Pset|(ap|pre)pend|offsetSet)(Style)$/', $method, $matches)) {
+ $index = null;
+ $argc = count($args);
+ $action = $matches['action'];
+
+ if ('offsetSet' == $action) {
+ if (0 < $argc) {
+ $index = array_shift($args);
+ --$argc;
+ }
+ }
+
+ if (1 > $argc) {
+ throw new Exception\BadMethodCallException(sprintf(
+ 'Method "%s" requires minimally content for the stylesheet',
+ $method
+ ));
+ }
+
+ $content = $args[0];
+ $attrs = [];
+ if (isset($args[1])) {
+ $attrs = (array) $args[1];
+ }
+
+ $item = $this->createData($content, $attrs);
+
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Create string representation of placeholder
+ *
+ * @param string|int $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = [];
+ $this->getContainer()->ksort();
+ foreach ($this as $item) {
+ if (! $this->isValid($item)) {
+ continue;
+ }
+ $items[] = $this->itemToString($item, $indent);
+ }
+
+ $return = $indent . implode($this->getSeparator() . $indent, $items);
+ $return = preg_replace("/(\r\n?|\n)/", '$1' . $indent, $return);
+
+ return $return;
+ }
+
+ /**
+ * Start capture action
+ *
+ * @param string $type
+ * @param string $attrs
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function captureStart($type = Placeholder\Container\AbstractContainer::APPEND, $attrs = null)
+ {
+ if ($this->captureLock) {
+ throw new Exception\RuntimeException('Cannot nest headStyle captures');
+ }
+
+ $this->captureLock = true;
+ $this->captureAttrs = $attrs;
+ $this->captureType = $type;
+ ob_start();
+ }
+
+ /**
+ * End capture action and store
+ *
+ * @return void
+ */
+ public function captureEnd()
+ {
+ $content = ob_get_clean();
+ $attrs = $this->captureAttrs;
+ $this->captureAttrs = null;
+ $this->captureLock = false;
+
+ switch ($this->captureType) {
+ case Placeholder\Container\AbstractContainer::SET:
+ $this->setStyle($content, $attrs);
+ break;
+ case Placeholder\Container\AbstractContainer::PREPEND:
+ $this->prependStyle($content, $attrs);
+ break;
+ case Placeholder\Container\AbstractContainer::APPEND:
+ default:
+ $this->appendStyle($content, $attrs);
+ break;
+ }
+ }
+
+ /**
+ * Create data item for use in stack
+ *
+ * @param string $content
+ * @param array $attributes
+ * @return stdClass
+ */
+ public function createData($content, array $attributes)
+ {
+ if (! isset($attributes['media'])) {
+ $attributes['media'] = 'screen';
+ } elseif (is_array($attributes['media'])) {
+ $attributes['media'] = implode(',', $attributes['media']);
+ }
+
+ $data = new stdClass();
+ $data->content = $content;
+ $data->attributes = $attributes;
+
+ return $data;
+ }
+
+ /**
+ * Determine if a value is a valid style tag
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ protected function isValid($value)
+ {
+ if ((! $value instanceof stdClass) || ! isset($value->content) || ! isset($value->attributes)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Convert content and attributes into valid style tag
+ *
+ * @param stdClass $item Item to render
+ * @param string $indent Indentation to use
+ * @return string
+ */
+ public function itemToString(stdClass $item, $indent)
+ {
+ $attrString = '';
+ if (! empty($item->attributes)) {
+ $enc = 'UTF-8';
+ if ($this->view instanceof View\Renderer\RendererInterface
+ && method_exists($this->view, 'getEncoding')
+ ) {
+ $enc = $this->view->getEncoding();
+ }
+ $escaper = $this->getEscaper($enc);
+ foreach ($item->attributes as $key => $value) {
+ if (! in_array($key, $this->optionalAttributes)) {
+ continue;
+ }
+ if ('media' == $key) {
+ if (false === strpos($value, ',')) {
+ if (! in_array($value, $this->mediaTypes)) {
+ continue;
+ }
+ } else {
+ $mediaTypes = explode(',', $value);
+ $value = '';
+ foreach ($mediaTypes as $type) {
+ $type = trim($type);
+ if (! in_array($type, $this->mediaTypes)) {
+ continue;
+ }
+ $value .= $type .',';
+ }
+ $value = substr($value, 0, -1);
+ }
+ }
+ $attrString .= sprintf(' %s="%s"', $key, $escaper->escapeHtmlAttr($value));
+ }
+ }
+
+ $escapeStart = $indent . '' . PHP_EOL;
+ if (isset($item->attributes['conditional'])
+ && ! empty($item->attributes['conditional'])
+ && is_string($item->attributes['conditional'])
+ ) {
+ $escapeStart = null;
+ $escapeEnd = null;
+ }
+
+ $html = '';
+
+ if (null == $escapeStart && null == $escapeEnd) {
+ // inner wrap with comment end and start if !IE
+ if (str_replace(' ', '', $item->attributes['conditional']) === '!IE') {
+ $html = '' . $html . '';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Override append to enforce style creation
+ *
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function append($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to append; please use appendStyle()'
+ );
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * Override offsetSet to enforce style creation
+ *
+ * @param string|int $index
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function offsetSet($index, $value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to offsetSet; please use offsetSetStyle()'
+ );
+ }
+
+ return $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * Override prepend to enforce style creation
+ *
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function prepend($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to prepend; please use prependStyle()'
+ );
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * Override set to enforce style creation
+ *
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function set($value)
+ {
+ if (! $this->isValid($value)) {
+ throw new Exception\InvalidArgumentException('Invalid value passed to set; please use setStyle()');
+ }
+
+ return $this->getContainer()->set($value);
+ }
+}
diff --git a/module/Zend/View/src/Helper/HeadTitle.php b/module/Zend/View/src/Helper/HeadTitle.php
new file mode 100644
index 00000000..5143cf75
--- /dev/null
+++ b/module/Zend/View/src/Helper/HeadTitle.php
@@ -0,0 +1,165 @@
+getDefaultAttachOrder())
+ ? Placeholder\Container\AbstractContainer::APPEND
+ : $this->getDefaultAttachOrder();
+ }
+
+ $title = (string) $title;
+ if ($title !== '') {
+ if ($setType == Placeholder\Container\AbstractContainer::SET) {
+ $this->set($title);
+ } elseif ($setType == Placeholder\Container\AbstractContainer::PREPEND) {
+ $this->prepend($title);
+ } else {
+ $this->append($title);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Render title (wrapped by title tag)
+ *
+ * @param string|null $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $output = $this->renderTitle();
+
+ return $indent . '' . $output . ' ';
+ }
+
+ /**
+ * Render title string
+ *
+ * @return string
+ */
+ public function renderTitle()
+ {
+ $items = [];
+
+ $itemCallback = $this->getTitleItemCallback();
+ foreach ($this as $item) {
+ $items[] = $itemCallback($item);
+ }
+
+ $separator = $this->getSeparator();
+ $output = '';
+
+ $prefix = $this->getPrefix();
+ if ($prefix) {
+ $output .= $prefix;
+ }
+
+ $output .= implode($separator, $items);
+
+ $postfix = $this->getPostfix();
+ if ($postfix) {
+ $output .= $postfix;
+ }
+
+ $output = ($this->autoEscape) ? $this->escape($output) : $output;
+
+ return $output;
+ }
+
+ /**
+ * Set a default order to add titles
+ *
+ * @param string $setType
+ * @throws Exception\DomainException
+ * @return HeadTitle
+ */
+ public function setDefaultAttachOrder($setType)
+ {
+ if (! in_array($setType, [
+ Placeholder\Container\AbstractContainer::APPEND,
+ Placeholder\Container\AbstractContainer::SET,
+ Placeholder\Container\AbstractContainer::PREPEND
+ ])) {
+ throw new Exception\DomainException(
+ "You must use a valid attach order: 'PREPEND', 'APPEND' or 'SET'"
+ );
+ }
+ $this->defaultAttachOrder = $setType;
+
+ return $this;
+ }
+
+ /**
+ * Get the default attach order, if any.
+ *
+ * @return mixed
+ */
+ public function getDefaultAttachOrder()
+ {
+ return $this->defaultAttachOrder;
+ }
+
+
+ /**
+ * Create and return a callback for normalizing title items.
+ *
+ * If translation is not enabled, or no translator is present, returns a
+ * callable that simply returns the provided item; otherwise, returns a
+ * callable that returns a translation of the provided item.
+ *
+ * @return callable
+ */
+ private function getTitleItemCallback()
+ {
+ if (! $this->isTranslatorEnabled() || ! $this->hasTranslator()) {
+ return function ($item) {
+ return $item;
+ };
+ }
+
+ $translator = $this->getTranslator();
+ $textDomain = $this->getTranslatorTextDomain();
+ return function ($item) use ($translator, $textDomain) {
+ return $translator->translate($item, $textDomain);
+ };
+ }
+}
diff --git a/module/Zend/View/src/Helper/HelperInterface.php b/module/Zend/View/src/Helper/HelperInterface.php
new file mode 100644
index 00000000..a3deba70
--- /dev/null
+++ b/module/Zend/View/src/Helper/HelperInterface.php
@@ -0,0 +1,30 @@
+ $data, 'quality' => 'high'], $params);
+
+ $htmlObject = $this->getView()->plugin('htmlObject');
+ return $htmlObject($data, self::TYPE, $attribs, $params, $content);
+ }
+}
diff --git a/module/Zend/View/src/Helper/HtmlList.php b/module/Zend/View/src/Helper/HtmlList.php
new file mode 100644
index 00000000..8e3a4e9e
--- /dev/null
+++ b/module/Zend/View/src/Helper/HtmlList.php
@@ -0,0 +1,68 @@
+getView()->plugin('escapeHtml');
+ $item = $escaper($item);
+ }
+ $list .= '' . $item . ' ' . PHP_EOL;
+ } else {
+ $itemLength = 5 + strlen(PHP_EOL);
+ if ($itemLength < strlen($list)) {
+ $list = substr($list, 0, strlen($list) - $itemLength)
+ . $this($item, $ordered, $attribs, $escape) . ' ' . PHP_EOL;
+ } else {
+ $list .= '' . $this($item, $ordered, $attribs, $escape) . ' ' . PHP_EOL;
+ }
+ }
+ }
+
+ if ($attribs) {
+ $attribs = $this->htmlAttribs($attribs);
+ } else {
+ $attribs = '';
+ }
+
+ $tag = ($ordered) ? 'ol' : 'ul';
+
+ return '<' . $tag . $attribs . '>' . PHP_EOL . $list . '' . $tag . '>' . PHP_EOL;
+ }
+}
diff --git a/module/Zend/View/src/Helper/HtmlObject.php b/module/Zend/View/src/Helper/HtmlObject.php
new file mode 100644
index 00000000..c61e5519
--- /dev/null
+++ b/module/Zend/View/src/Helper/HtmlObject.php
@@ -0,0 +1,71 @@
+ $data, 'type' => $type], $attribs);
+
+ // Params
+ $paramHtml = [];
+ $closingBracket = $this->getClosingBracket();
+
+ foreach ($params as $param => $options) {
+ if (is_string($options)) {
+ $options = ['value' => $options];
+ }
+
+ $options = array_merge(['name' => $param], $options);
+
+ $paramHtml[] = ' htmlAttribs($options) . $closingBracket;
+ }
+
+ // Content
+ if (is_array($content)) {
+ $content = implode(PHP_EOL, $content);
+ }
+
+ // Object header
+ $xhtml = 'htmlAttribs($attribs) . '>' . PHP_EOL
+ . implode(PHP_EOL, $paramHtml) . PHP_EOL
+ . ($content ? $content . PHP_EOL : '')
+ . ' ';
+
+ return $xhtml;
+ }
+}
diff --git a/module/Zend/View/src/Helper/HtmlPage.php b/module/Zend/View/src/Helper/HtmlPage.php
new file mode 100644
index 00000000..a49d3177
--- /dev/null
+++ b/module/Zend/View/src/Helper/HtmlPage.php
@@ -0,0 +1,51 @@
+ self::ATTRIB_CLASSID];
+
+ /**
+ * Output a html object tag
+ *
+ * @param string $data The html url
+ * @param array $attribs Attribs for the object tag
+ * @param array $params Params for in the object tag
+ * @param string $content Alternative content
+ * @return string
+ */
+ public function __invoke($data, array $attribs = [], array $params = [], $content = null)
+ {
+ // Attribs
+ $attribs = array_merge($this->attribs, $attribs);
+
+ // Params
+ $params = array_merge(['data' => $data], $params);
+
+ $htmlObject = $this->getView()->plugin('htmlObject');
+ return $htmlObject($data, self::TYPE, $attribs, $params, $content);
+ }
+}
diff --git a/module/Zend/View/src/Helper/HtmlQuicktime.php b/module/Zend/View/src/Helper/HtmlQuicktime.php
new file mode 100644
index 00000000..55af8a9f
--- /dev/null
+++ b/module/Zend/View/src/Helper/HtmlQuicktime.php
@@ -0,0 +1,56 @@
+ self::ATTRIB_CLASSID, 'codebase' => self::ATTRIB_CODEBASE];
+
+ /**
+ * Output a quicktime movie object tag
+ *
+ * @param string $data The quicktime file
+ * @param array $attribs Attribs for the object tag
+ * @param array $params Params for in the object tag
+ * @param string $content Alternative content
+ * @return string
+ */
+ public function __invoke($data, array $attribs = [], array $params = [], $content = null)
+ {
+ // Attrs
+ $attribs = array_merge($this->attribs, $attribs);
+
+ // Params
+ $params = array_merge(['src' => $data], $params);
+
+ $htmlObject = $this->getView()->plugin('htmlObject');
+ return $htmlObject($data, self::TYPE, $attribs, $params, $content);
+ }
+}
diff --git a/module/Zend/View/src/Helper/HtmlTag.php b/module/Zend/View/src/Helper/HtmlTag.php
new file mode 100644
index 00000000..2e9cfce2
--- /dev/null
+++ b/module/Zend/View/src/Helper/HtmlTag.php
@@ -0,0 +1,148 @@
+ tag (both opening and closing) of a web page, to which some custom
+ * attributes can be added dynamically.
+ *
+ * @author Nikola Posa
+ */
+class HtmlTag extends AbstractHtmlElement
+{
+ /**
+ * Attributes for the tag.
+ *
+ * @var array
+ */
+ protected $attributes = [];
+
+ /**
+ * Whether to pre-set appropriate attributes in accordance
+ * with the currently set DOCTYPE.
+ *
+ * @var bool
+ */
+ protected $useNamespaces = false;
+
+ /**
+ * @var bool
+ */
+ private $handledNamespaces = false;
+
+ /**
+ * Retrieve object instance; optionally add attributes.
+ *
+ * @param array $attribs
+ * @return self
+ */
+ public function __invoke(array $attribs = [])
+ {
+ if (! empty($attribs)) {
+ $this->setAttributes($attribs);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set new attribute.
+ *
+ * @param string $attrName
+ * @param string $attrValue
+ * @return self
+ */
+ public function setAttribute($attrName, $attrValue)
+ {
+ $this->attributes[$attrName] = $attrValue;
+ return $this;
+ }
+
+ /**
+ * Add new or overwrite the existing attributes.
+ *
+ * @param array $attribs
+ * @return self
+ */
+ public function setAttributes(array $attribs)
+ {
+ foreach ($attribs as $name => $value) {
+ $this->setAttribute($name, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * @param bool $useNamespaces
+ * @return self
+ */
+ public function setUseNamespaces($useNamespaces)
+ {
+ $this->useNamespaces = (bool) $useNamespaces;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getUseNamespaces()
+ {
+ return $this->useNamespaces;
+ }
+
+ /**
+ * Render opening tag.
+ *
+ * @return string
+ */
+ public function openTag()
+ {
+ $this->handleNamespaceAttributes();
+
+ return sprintf('', $this->htmlAttribs($this->attributes));
+ }
+
+ protected function handleNamespaceAttributes()
+ {
+ if ($this->useNamespaces && ! $this->handledNamespaces) {
+ if (method_exists($this->view, 'plugin')) {
+ $doctypeAttributes = [];
+
+ if ($this->view->plugin('doctype')->isXhtml()) {
+ $doctypeAttributes = ['xmlns' => 'http://www.w3.org/1999/xhtml'];
+ }
+
+ if (! empty($doctypeAttributes)) {
+ $this->attributes = array_merge($doctypeAttributes, $this->attributes);
+ }
+ }
+
+ $this->handledNamespaces = true;
+ }
+ }
+
+ /**
+ * Render closing tag.
+ *
+ * @return string
+ */
+ public function closeTag()
+ {
+ return '';
+ }
+}
diff --git a/module/Zend/View/src/Helper/Identity.php b/module/Zend/View/src/Helper/Identity.php
new file mode 100644
index 00000000..cbd36430
--- /dev/null
+++ b/module/Zend/View/src/Helper/Identity.php
@@ -0,0 +1,69 @@
+authenticationService instanceof AuthenticationServiceInterface) {
+ throw new Exception\RuntimeException('No AuthenticationServiceInterface instance provided');
+ }
+
+ if (! $this->authenticationService->hasIdentity()) {
+ return;
+ }
+
+ return $this->authenticationService->getIdentity();
+ }
+
+ /**
+ * Set AuthenticationService instance
+ *
+ * @param AuthenticationServiceInterface $authenticationService
+ * @return Identity
+ */
+ public function setAuthenticationService(AuthenticationServiceInterface $authenticationService)
+ {
+ $this->authenticationService = $authenticationService;
+ return $this;
+ }
+
+ /**
+ * Get AuthenticationService instance
+ *
+ * @return AuthenticationServiceInterface
+ */
+ public function getAuthenticationService()
+ {
+ return $this->authenticationService;
+ }
+}
diff --git a/module/Zend/View/src/Helper/InlineScript.php b/module/Zend/View/src/Helper/InlineScript.php
new file mode 100644
index 00000000..c69639b0
--- /dev/null
+++ b/module/Zend/View/src/Helper/InlineScript.php
@@ -0,0 +1,40 @@
+response instanceof Response) {
+ $headers = $this->response->getHeaders();
+ $headers->addHeaderLine('Content-Type', 'application/json');
+ }
+
+ return $data;
+ }
+
+ /**
+ * Set the response object
+ *
+ * @param Response $response
+ * @return Json
+ */
+ public function setResponse(Response $response)
+ {
+ $this->response = $response;
+ return $this;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Layout.php b/module/Zend/View/src/Helper/Layout.php
new file mode 100644
index 00000000..2a458ee2
--- /dev/null
+++ b/module/Zend/View/src/Helper/Layout.php
@@ -0,0 +1,98 @@
+getRoot();
+ }
+
+ return $this->setTemplate($template);
+ }
+
+ /**
+ * Get layout template
+ *
+ * @return string
+ */
+ public function getLayout()
+ {
+ return $this->getRoot()->getTemplate();
+ }
+
+ /**
+ * Get the root view model
+ *
+ * @throws Exception\RuntimeException
+ * @return null|Model
+ */
+ protected function getRoot()
+ {
+ $helper = $this->getViewModelHelper();
+
+ if (! $helper->hasRoot()) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: no view model currently registered as root in renderer',
+ __METHOD__
+ ));
+ }
+
+ return $helper->getRoot();
+ }
+
+ /**
+ * Set layout template
+ *
+ * @param string $template
+ * @return Layout
+ */
+ public function setTemplate($template)
+ {
+ $this->getRoot()->setTemplate((string) $template);
+ return $this;
+ }
+
+ /**
+ * Retrieve the view model helper
+ *
+ * @return ViewModel
+ */
+ protected function getViewModelHelper()
+ {
+ if (null === $this->viewModelHelper) {
+ $this->viewModelHelper = $this->getView()->plugin('view_model');
+ }
+
+ return $this->viewModelHelper;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Navigation.php b/module/Zend/View/src/Helper/Navigation.php
new file mode 100644
index 00000000..5119be28
--- /dev/null
+++ b/module/Zend/View/src/Helper/Navigation.php
@@ -0,0 +1,350 @@
+setContainer($container);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Magic overload: Proxy to other navigation helpers or the container
+ *
+ * Examples of usage from a view script or layout:
+ *
+ * // proxy to Menu helper and render container:
+ * echo $this->navigation()->menu();
+ *
+ * // proxy to Breadcrumbs helper and set indentation:
+ * $this->navigation()->breadcrumbs()->setIndent(8);
+ *
+ * // proxy to container and find all pages with 'blog' route:
+ * $blogPages = $this->navigation()->findAllByRoute('blog');
+ *
+ *
+ * @param string $method helper name or method name in container
+ * @param array $arguments [optional] arguments to pass
+ * @throws \Zend\View\Exception\ExceptionInterface if proxying to a helper, and the
+ * helper is not an instance of the
+ * interface specified in
+ * {@link findHelper()}
+ * @throws \Zend\Navigation\Exception\ExceptionInterface if method does not exist in container
+ * @return mixed returns what the proxied call returns
+ */
+ public function __call($method, array $arguments = [])
+ {
+ // check if call should proxy to another helper
+ $helper = $this->findHelper($method, false);
+ if ($helper) {
+ if (method_exists($helper, 'setServiceLocator') && $this->getServiceLocator()) {
+ $helper->setServiceLocator($this->getServiceLocator());
+ }
+ return call_user_func_array($helper, $arguments);
+ }
+
+ // default behaviour: proxy call to container
+ return parent::__call($method, $arguments);
+ }
+
+ /**
+ * Renders helper
+ *
+ * @param AbstractContainer $container
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function render($container = null)
+ {
+ return $this->findHelper($this->getDefaultProxy())->render($container);
+ }
+
+ /**
+ * Returns the helper matching $proxy
+ *
+ * The helper must implement the interface
+ * {@link Zend\View\Helper\Navigation\Helper}.
+ *
+ * @param string $proxy helper name
+ * @param bool $strict [optional] whether exceptions should be
+ * thrown if something goes
+ * wrong. Default is true.
+ * @throws Exception\RuntimeException if $strict is true and helper cannot be found
+ * @return \Zend\View\Helper\Navigation\HelperInterface helper instance
+ */
+ public function findHelper($proxy, $strict = true)
+ {
+ $plugins = $this->getPluginManager();
+ if (! $plugins->has($proxy)) {
+ if ($strict) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Failed to find plugin for %s',
+ $proxy
+ ));
+ }
+ return false;
+ }
+
+ $helper = $plugins->get($proxy);
+ $container = $this->getContainer();
+ $hash = spl_object_hash($container) . spl_object_hash($helper);
+
+ if (! isset($this->injected[$hash])) {
+ $helper->setContainer();
+ $this->inject($helper);
+ $this->injected[$hash] = true;
+ } else {
+ if ($this->getInjectContainer()) {
+ $helper->setContainer($container);
+ }
+ }
+
+ return $helper;
+ }
+
+ /**
+ * Injects container, ACL, and translator to the given $helper if this
+ * helper is configured to do so
+ *
+ * @param NavigationHelper $helper helper instance
+ * @return void
+ */
+ protected function inject(NavigationHelper $helper)
+ {
+ if ($this->getInjectContainer() && ! $helper->hasContainer()) {
+ $helper->setContainer($this->getContainer());
+ }
+
+ if ($this->getInjectAcl()) {
+ if (! $helper->hasAcl()) {
+ $helper->setAcl($this->getAcl());
+ }
+ if (! $helper->hasRole()) {
+ $helper->setRole($this->getRole());
+ }
+ }
+
+ if ($this->getInjectTranslator() && ! $helper->hasTranslator()) {
+ $helper->setTranslator(
+ $this->getTranslator(),
+ $this->getTranslatorTextDomain()
+ );
+ }
+ }
+
+ /**
+ * Sets the default proxy to use in {@link render()}
+ *
+ * @param string $proxy default proxy
+ * @return Navigation
+ */
+ public function setDefaultProxy($proxy)
+ {
+ $this->defaultProxy = (string) $proxy;
+ return $this;
+ }
+
+ /**
+ * Returns the default proxy to use in {@link render()}
+ *
+ * @return string
+ */
+ public function getDefaultProxy()
+ {
+ return $this->defaultProxy;
+ }
+
+ /**
+ * Sets whether container should be injected when proxying
+ *
+ * @param bool $injectContainer
+ * @return Navigation
+ */
+ public function setInjectContainer($injectContainer = true)
+ {
+ $this->injectContainer = (bool) $injectContainer;
+ return $this;
+ }
+
+ /**
+ * Returns whether container should be injected when proxying
+ *
+ * @return bool
+ */
+ public function getInjectContainer()
+ {
+ return $this->injectContainer;
+ }
+
+ /**
+ * Sets whether ACL should be injected when proxying
+ *
+ * @param bool $injectAcl
+ * @return Navigation
+ */
+ public function setInjectAcl($injectAcl = true)
+ {
+ $this->injectAcl = (bool) $injectAcl;
+ return $this;
+ }
+
+ /**
+ * Returns whether ACL should be injected when proxying
+ *
+ * @return bool
+ */
+ public function getInjectAcl()
+ {
+ return $this->injectAcl;
+ }
+
+ /**
+ * Sets whether translator should be injected when proxying
+ *
+ * @param bool $injectTranslator
+ * @return Navigation
+ */
+ public function setInjectTranslator($injectTranslator = true)
+ {
+ $this->injectTranslator = (bool) $injectTranslator;
+ return $this;
+ }
+
+ /**
+ * Returns whether translator should be injected when proxying
+ *
+ * @return bool
+ */
+ public function getInjectTranslator()
+ {
+ return $this->injectTranslator;
+ }
+
+ /**
+ * Set manager for retrieving navigation helpers
+ *
+ * @param Navigation\PluginManager $plugins
+ * @return Navigation
+ */
+ public function setPluginManager(Navigation\PluginManager $plugins)
+ {
+ $renderer = $this->getView();
+ if ($renderer) {
+ $plugins->setRenderer($renderer);
+ }
+ $this->plugins = $plugins;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve plugin loader for navigation helpers
+ *
+ * Lazy-loads an instance of Navigation\HelperLoader if none currently
+ * registered.
+ *
+ * @return Navigation\PluginManager
+ */
+ public function getPluginManager()
+ {
+ if (null === $this->plugins) {
+ $this->setPluginManager(new Navigation\PluginManager($this->getServiceLocator()));
+ }
+
+ return $this->plugins;
+ }
+
+ /**
+ * Set the View object
+ *
+ * @param Renderer $view
+ * @return self
+ */
+ public function setView(Renderer $view)
+ {
+ parent::setView($view);
+ if ($view && $this->plugins) {
+ $this->plugins->setRenderer($view);
+ }
+ return $this;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Navigation/AbstractHelper.php b/module/Zend/View/src/Helper/Navigation/AbstractHelper.php
new file mode 100644
index 00000000..71febc27
--- /dev/null
+++ b/module/Zend/View/src/Helper/Navigation/AbstractHelper.php
@@ -0,0 +1,903 @@
+getContainer(), $method],
+ $arguments
+ );
+ }
+
+ /**
+ * Magic overload: Proxy to {@link render()}.
+ *
+ * This method will trigger an E_USER_ERROR if rendering the helper causes
+ * an exception to be thrown.
+ *
+ * Implements {@link HelperInterface::__toString()}.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ return $this->render();
+ } catch (\Exception $e) {
+ $msg = get_class($e) . ': ' . $e->getMessage();
+ trigger_error($msg, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ /**
+ * Finds the deepest active page in the given container
+ *
+ * @param Navigation\AbstractContainer $container container to search
+ * @param int|null $minDepth [optional] minimum depth
+ * required for page to be
+ * valid. Default is to use
+ * {@link getMinDepth()}. A
+ * null value means no minimum
+ * depth required.
+ * @param int|null $maxDepth [optional] maximum depth
+ * a page can have to be
+ * valid. Default is to use
+ * {@link getMaxDepth()}. A
+ * null value means no maximum
+ * depth required.
+ * @return array an associative array with
+ * the values 'depth' and
+ * 'page', or an empty array
+ * if not found
+ */
+ public function findActive($container, $minDepth = null, $maxDepth = -1)
+ {
+ $this->parseContainer($container);
+ if (! is_int($minDepth)) {
+ $minDepth = $this->getMinDepth();
+ }
+ if ((! is_int($maxDepth) || $maxDepth < 0) && null !== $maxDepth) {
+ $maxDepth = $this->getMaxDepth();
+ }
+
+ $found = null;
+ $foundDepth = -1;
+ $iterator = new RecursiveIteratorIterator(
+ $container,
+ RecursiveIteratorIterator::CHILD_FIRST
+ );
+
+ /** @var \Zend\Navigation\Page\AbstractPage $page */
+ foreach ($iterator as $page) {
+ $currDepth = $iterator->getDepth();
+ if ($currDepth < $minDepth || ! $this->accept($page)) {
+ // page is not accepted
+ continue;
+ }
+
+ if ($page->isActive(false) && $currDepth > $foundDepth) {
+ // found an active page at a deeper level than before
+ $found = $page;
+ $foundDepth = $currDepth;
+ }
+ }
+
+ if (is_int($maxDepth) && $foundDepth > $maxDepth) {
+ while ($foundDepth > $maxDepth) {
+ if (--$foundDepth < $minDepth) {
+ $found = null;
+ break;
+ }
+
+ $found = $found->getParent();
+ if (! $found instanceof AbstractPage) {
+ $found = null;
+ break;
+ }
+ }
+ }
+
+ if ($found) {
+ return ['page' => $found, 'depth' => $foundDepth];
+ }
+
+ return [];
+ }
+
+ /**
+ * Verifies container and eventually fetches it from service locator if it is a string
+ *
+ * @param Navigation\AbstractContainer|string|null $container
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function parseContainer(&$container = null)
+ {
+ if (null === $container) {
+ return;
+ }
+
+ if (is_string($container)) {
+ $services = $this->getServiceLocator();
+ if (! $services) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Attempted to set container with alias "%s" but no ServiceLocator was set',
+ $container
+ ));
+ }
+
+ // Fallback
+ if (in_array($container, ['default', 'navigation'], true)) {
+ // Uses class name
+ if ($services->has(Navigation\Navigation::class)) {
+ $container = $services->get(Navigation\Navigation::class);
+ return;
+ }
+
+ // Uses old service name
+ if ($services->has('navigation')) {
+ $container = $services->get('navigation');
+ return;
+ }
+ }
+
+ /**
+ * Load the navigation container from the root service locator
+ */
+ $container = $services->get($container);
+ return;
+ }
+
+ if (! $container instanceof Navigation\AbstractContainer) {
+ throw new Exception\InvalidArgumentException(
+ 'Container must be a string alias or an instance of '
+ . 'Zend\Navigation\AbstractContainer'
+ );
+ }
+ }
+
+ // Iterator filter methods:
+
+ /**
+ * Determines whether a page should be accepted when iterating
+ *
+ * Default listener may be 'overridden' by attaching listener to 'isAllowed'
+ * method. Listener must be 'short circuited' if overriding default ACL
+ * listener.
+ *
+ * Rules:
+ * - If a page is not visible it is not accepted, unless RenderInvisible has
+ * been set to true
+ * - If $useAcl is true (default is true):
+ * - Page is accepted if listener returns true, otherwise false
+ * - If page is accepted and $recursive is true, the page
+ * will not be accepted if it is the descendant of a non-accepted page
+ *
+ * @param AbstractPage $page page to check
+ * @param bool $recursive [optional] if true, page will not be
+ * accepted if it is the descendant of
+ * a page that is not accepted. Default
+ * is true
+ *
+ * @return bool Whether page should be accepted
+ */
+ public function accept(AbstractPage $page, $recursive = true)
+ {
+ $accept = true;
+
+ if (! $page->isVisible(false) && ! $this->getRenderInvisible()) {
+ $accept = false;
+ } elseif ($this->getUseAcl()) {
+ $acl = $this->getAcl();
+ $role = $this->getRole();
+ $params = ['acl' => $acl, 'page' => $page, 'role' => $role];
+ $accept = $this->isAllowed($params);
+ }
+
+ if ($accept && $recursive) {
+ $parent = $page->getParent();
+
+ if ($parent instanceof AbstractPage) {
+ $accept = $this->accept($parent, true);
+ }
+ }
+
+ return $accept;
+ }
+
+ /**
+ * Determines whether a page should be allowed given certain parameters
+ *
+ * @param array $params
+ * @return bool
+ */
+ protected function isAllowed($params)
+ {
+ $events = $this->getEventManager() ?: $this->createEventManager();
+ $results = $events->trigger(__FUNCTION__, $this, $params);
+ return $results->last();
+ }
+
+ // Util methods:
+
+ /**
+ * Retrieve whitespace representation of $indent
+ *
+ * @param int|string $indent
+ * @return string
+ */
+ protected function getWhitespace($indent)
+ {
+ if (is_int($indent)) {
+ $indent = str_repeat(' ', $indent);
+ }
+
+ return (string) $indent;
+ }
+
+ /**
+ * Converts an associative array to a string of tag attributes.
+ *
+ * Overloads {@link View\Helper\AbstractHtmlElement::htmlAttribs()}.
+ *
+ * @param array $attribs an array where each key-value pair is converted
+ * to an attribute name and value
+ * @return string
+ */
+ protected function htmlAttribs($attribs)
+ {
+ // filter out null values and empty string values
+ foreach ($attribs as $key => $value) {
+ if ($value === null || (is_string($value) && ! strlen($value))) {
+ unset($attribs[$key]);
+ }
+ }
+
+ return parent::htmlAttribs($attribs);
+ }
+
+ /**
+ * Returns an HTML string containing an 'a' element for the given page
+ *
+ * @param AbstractPage $page page to generate HTML for
+ * @return string HTML string (Label )
+ */
+ public function htmlify(AbstractPage $page)
+ {
+ $label = $this->translate($page->getLabel(), $page->getTextDomain());
+ $title = $this->translate($page->getTitle(), $page->getTextDomain());
+
+ // get attribs for anchor element
+ $attribs = [
+ 'id' => $page->getId(),
+ 'title' => $title,
+ 'class' => $page->getClass(),
+ 'href' => $page->getHref(),
+ 'target' => $page->getTarget()
+ ];
+
+ /** @var \Zend\View\Helper\EscapeHtml $escaper */
+ $escaper = $this->view->plugin('escapeHtml');
+ $label = $escaper($label);
+
+ return 'htmlAttribs($attribs) . '>' . $label . ' ';
+ }
+
+ /**
+ * Translate a message (for label, title, …)
+ *
+ * @param string $message ID of the message to translate
+ * @param string $textDomain Text domain (category name for the translations)
+ * @return string Translated message
+ */
+ protected function translate($message, $textDomain = null)
+ {
+ if (! is_string($message) || empty($message)) {
+ return $message;
+ }
+
+ if (! $this->isTranslatorEnabled() || ! $this->hasTranslator()) {
+ return $message;
+ }
+
+ $translator = $this->getTranslator();
+ $textDomain = $textDomain ?: $this->getTranslatorTextDomain();
+
+ return $translator->translate($message, $textDomain);
+ }
+
+ /**
+ * Normalize an ID
+ *
+ * Overrides {@link View\Helper\AbstractHtmlElement::normalizeId()}.
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function normalizeId($value)
+ {
+ $prefix = get_class($this);
+ $prefix = strtolower(trim(substr($prefix, strrpos($prefix, '\\')), '\\'));
+
+ return $prefix . '-' . $value;
+ }
+
+ /**
+ * Sets ACL to use when iterating pages
+ *
+ * Implements {@link HelperInterface::setAcl()}.
+ *
+ * @param Acl\AclInterface $acl ACL object.
+ * @return AbstractHelper
+ */
+ public function setAcl(Acl\AclInterface $acl = null)
+ {
+ $this->acl = $acl;
+ return $this;
+ }
+
+ /**
+ * Returns ACL or null if it isn't set using {@link setAcl()} or
+ * {@link setDefaultAcl()}
+ *
+ * Implements {@link HelperInterface::getAcl()}.
+ *
+ * @return Acl\AclInterface|null ACL object or null
+ */
+ public function getAcl()
+ {
+ if ($this->acl === null && static::$defaultAcl !== null) {
+ return static::$defaultAcl;
+ }
+
+ return $this->acl;
+ }
+
+ /**
+ * Checks if the helper has an ACL instance
+ *
+ * Implements {@link HelperInterface::hasAcl()}.
+ *
+ * @return bool
+ */
+ public function hasAcl()
+ {
+ if ($this->acl instanceof Acl\Acl
+ || static::$defaultAcl instanceof Acl\Acl
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the event manager.
+ *
+ * @param EventManagerInterface $events
+ * @return AbstractHelper
+ */
+ public function setEventManager(EventManagerInterface $events)
+ {
+ $events->setIdentifiers([
+ __CLASS__,
+ get_called_class(),
+ ]);
+
+ $this->events = $events;
+
+ if ($events->getSharedManager()) {
+ $this->setDefaultListeners();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the event manager, if present.
+ *
+ * Internally, the helper will lazy-load an EM instance the first time it
+ * requires one, but ideally it should be injected during instantiation.
+ *
+ * @return null|EventManagerInterface
+ */
+ public function getEventManager()
+ {
+ return $this->events;
+ }
+
+ /**
+ * Sets navigation container the helper operates on by default
+ *
+ * Implements {@link HelperInterface::setContainer()}.
+ *
+ * @param string|Navigation\AbstractContainer $container Default is null, meaning container will be reset.
+ * @return AbstractHelper
+ */
+ public function setContainer($container = null)
+ {
+ $this->parseContainer($container);
+ $this->container = $container;
+
+ return $this;
+ }
+
+ /**
+ * Returns the navigation container helper operates on by default
+ *
+ * Implements {@link HelperInterface::getContainer()}.
+ *
+ * If no container is set, a new container will be instantiated and
+ * stored in the helper.
+ *
+ * @return Navigation\AbstractContainer navigation container
+ */
+ public function getContainer()
+ {
+ if (null === $this->container) {
+ $this->container = new Navigation\Navigation();
+ }
+
+ return $this->container;
+ }
+
+ /**
+ * Checks if the helper has a container
+ *
+ * Implements {@link HelperInterface::hasContainer()}.
+ *
+ * @return bool
+ */
+ public function hasContainer()
+ {
+ return null !== $this->container;
+ }
+
+ /**
+ * Set the indentation string for using in {@link render()}, optionally a
+ * number of spaces to indent with
+ *
+ * @param string|int $indent
+ * @return AbstractHelper
+ */
+ public function setIndent($indent)
+ {
+ $this->indent = $this->getWhitespace($indent);
+ return $this;
+ }
+
+ /**
+ * Returns indentation
+ *
+ * @return string
+ */
+ public function getIndent()
+ {
+ return $this->indent;
+ }
+
+ /**
+ * Sets the maximum depth a page can have to be included when rendering
+ *
+ * @param int $maxDepth Default is null, which sets no maximum depth.
+ * @return AbstractHelper
+ */
+ public function setMaxDepth($maxDepth = null)
+ {
+ if (null === $maxDepth || is_int($maxDepth)) {
+ $this->maxDepth = $maxDepth;
+ } else {
+ $this->maxDepth = (int) $maxDepth;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns maximum depth a page can have to be included when rendering
+ *
+ * @return int|null
+ */
+ public function getMaxDepth()
+ {
+ return $this->maxDepth;
+ }
+
+ /**
+ * Sets the minimum depth a page must have to be included when rendering
+ *
+ * @param int $minDepth Default is null, which sets no minimum depth.
+ * @return AbstractHelper
+ */
+ public function setMinDepth($minDepth = null)
+ {
+ if (null === $minDepth || is_int($minDepth)) {
+ $this->minDepth = $minDepth;
+ } else {
+ $this->minDepth = (int) $minDepth;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns minimum depth a page must have to be included when rendering
+ *
+ * @return int|null
+ */
+ public function getMinDepth()
+ {
+ if (! is_int($this->minDepth) || $this->minDepth < 0) {
+ return 0;
+ }
+
+ return $this->minDepth;
+ }
+
+ /**
+ * Render invisible items?
+ *
+ * @param bool $renderInvisible
+ * @return AbstractHelper
+ */
+ public function setRenderInvisible($renderInvisible = true)
+ {
+ $this->renderInvisible = (bool) $renderInvisible;
+ return $this;
+ }
+
+ /**
+ * Return renderInvisible flag
+ *
+ * @return bool
+ */
+ public function getRenderInvisible()
+ {
+ return $this->renderInvisible;
+ }
+
+ /**
+ * Sets ACL role(s) to use when iterating pages
+ *
+ * Implements {@link HelperInterface::setRole()}.
+ *
+ * @param mixed $role [optional] role to set. Expects a string, an
+ * instance of type {@link Acl\Role\RoleInterface}, or null. Default
+ * is null, which will set no role.
+ * @return AbstractHelper
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setRole($role = null)
+ {
+ if (null === $role || is_string($role) ||
+ $role instanceof Acl\Role\RoleInterface
+ ) {
+ $this->role = $role;
+ } else {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '$role must be a string, null, or an instance of '
+ . 'Zend\Permissions\Role\RoleInterface; %s given',
+ (is_object($role) ? get_class($role) : gettype($role))
+ ));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns ACL role to use when iterating pages, or null if it isn't set
+ * using {@link setRole()} or {@link setDefaultRole()}
+ *
+ * Implements {@link HelperInterface::getRole()}.
+ *
+ * @return string|Acl\Role\RoleInterface|null
+ */
+ public function getRole()
+ {
+ if ($this->role === null && static::$defaultRole !== null) {
+ return static::$defaultRole;
+ }
+
+ return $this->role;
+ }
+
+ /**
+ * Checks if the helper has an ACL role
+ *
+ * Implements {@link HelperInterface::hasRole()}.
+ *
+ * @return bool
+ */
+ public function hasRole()
+ {
+ if ($this->role instanceof Acl\Role\RoleInterface
+ || is_string($this->role)
+ || static::$defaultRole instanceof Acl\Role\RoleInterface
+ || is_string(static::$defaultRole)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the service locator.
+ *
+ * Used internally to pull named navigation containers to render.
+ *
+ * @param ContainerInterface $serviceLocator
+ * @return AbstractHelper
+ */
+ public function setServiceLocator(ContainerInterface $serviceLocator)
+ {
+ // If we are provided a plugin manager, we should pull the parent
+ // context from it.
+ // @todo We should update tests and code to ensure that this situation
+ // doesn't happen in the future.
+ if ($serviceLocator instanceof AbstractPluginManager
+ && ! method_exists($serviceLocator, 'configure')
+ && $serviceLocator->getServiceLocator()
+ ) {
+ $serviceLocator = $serviceLocator->getServiceLocator();
+ }
+
+ // v3 variant; likely won't be needed.
+ if ($serviceLocator instanceof AbstractPluginManager
+ && method_exists($serviceLocator, 'configure')
+ ) {
+ $r = new ReflectionProperty($serviceLocator, 'creationContext');
+ $r->setAccessible(true);
+ $serviceLocator = $r->getValue($serviceLocator);
+ }
+
+ $this->serviceLocator = $serviceLocator;
+ return $this;
+ }
+
+ /**
+ * Get the service locator.
+ *
+ * Used internally to pull named navigation containers to render.
+ *
+ * @return ContainerInterface
+ */
+ public function getServiceLocator()
+ {
+ return $this->serviceLocator;
+ }
+
+ /**
+ * Sets whether ACL should be used
+ *
+ * Implements {@link HelperInterface::setUseAcl()}.
+ *
+ * @param bool $useAcl
+ * @return AbstractHelper
+ */
+ public function setUseAcl($useAcl = true)
+ {
+ $this->useAcl = (bool) $useAcl;
+ return $this;
+ }
+
+ /**
+ * Returns whether ACL should be used
+ *
+ * Implements {@link HelperInterface::getUseAcl()}.
+ *
+ * @return bool
+ */
+ public function getUseAcl()
+ {
+ return $this->useAcl;
+ }
+
+ // Static methods:
+
+ /**
+ * Sets default ACL to use if another ACL is not explicitly set
+ *
+ * @param Acl\AclInterface $acl [optional] ACL object. Default is null, which
+ * sets no ACL object.
+ * @return void
+ */
+ public static function setDefaultAcl(Acl\AclInterface $acl = null)
+ {
+ static::$defaultAcl = $acl;
+ }
+
+ /**
+ * Sets default ACL role(s) to use when iterating pages if not explicitly
+ * set later with {@link setRole()}
+ *
+ * @param mixed $role [optional] role to set. Expects null, string, or an
+ * instance of {@link Acl\Role\RoleInterface}. Default is null, which
+ * sets no default role.
+ * @return void
+ * @throws Exception\InvalidArgumentException if role is invalid
+ */
+ public static function setDefaultRole($role = null)
+ {
+ if (null === $role
+ || is_string($role)
+ || $role instanceof Acl\Role\RoleInterface
+ ) {
+ static::$defaultRole = $role;
+ } else {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '$role must be null|string|Zend\Permissions\Role\RoleInterface; received "%s"',
+ (is_object($role) ? get_class($role) : gettype($role))
+ ));
+ }
+ }
+
+ /**
+ * Attaches default ACL listeners, if ACLs are in use
+ */
+ protected function setDefaultListeners()
+ {
+ if (! $this->getUseAcl()) {
+ return;
+ }
+
+ $events = $this->getEventManager() ?: $this->createEventManager();
+
+ if (! $events->getSharedManager()) {
+ return;
+ }
+
+ $events->getSharedManager()->attach(
+ 'Zend\View\Helper\Navigation\AbstractHelper',
+ 'isAllowed',
+ ['Zend\View\Helper\Navigation\Listener\AclListener', 'accept']
+ );
+ }
+
+ /**
+ * Create and return an event manager instance.
+ *
+ * Ensures that the returned event manager has a shared manager
+ * composed.
+ *
+ * @return EventManager
+ */
+ private function createEventManager()
+ {
+ $r = new ReflectionClass(EventManager::class);
+ if ($r->hasMethod('setSharedManager')) {
+ $events = new EventManager();
+ $events->setSharedManager(new SharedEventManager());
+ } else {
+ $events = new EventManager(new SharedEventManager());
+ }
+
+ $this->setEventManager($events);
+ return $events;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Navigation/Breadcrumbs.php b/module/Zend/View/src/Helper/Navigation/Breadcrumbs.php
new file mode 100644
index 00000000..9944bc88
--- /dev/null
+++ b/module/Zend/View/src/Helper/Navigation/Breadcrumbs.php
@@ -0,0 +1,312 @@
+setContainer($container);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Renders helper.
+ *
+ * Implements {@link HelperInterface::render()}.
+ *
+ * @param AbstractContainer $container [optional] container to render. Default is
+ * to render the container registered in the helper.
+ * @return string
+ */
+ public function render($container = null)
+ {
+ $partial = $this->getPartial();
+ if ($partial) {
+ return $this->renderPartial($container, $partial);
+ }
+
+ return $this->renderStraight($container);
+ }
+
+ /**
+ * Renders breadcrumbs by chaining 'a' elements with the separator
+ * registered in the helper.
+ *
+ * @param AbstractContainer $container [optional] container to render. Default is
+ * to render the container registered in the helper.
+ * @return string
+ */
+ public function renderStraight($container = null)
+ {
+ $this->parseContainer($container);
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+ // find deepest active
+ if (! $active = $this->findActive($container)) {
+ return '';
+ }
+
+ $active = $active['page'];
+
+ // put the deepest active page last in breadcrumbs
+ if ($this->getLinkLast()) {
+ $html = $this->htmlify($active);
+ } else {
+ /** @var \Zend\View\Helper\EscapeHtml $escaper */
+ $escaper = $this->view->plugin('escapeHtml');
+ $html = $escaper(
+ $this->translate($active->getLabel(), $active->getTextDomain())
+ );
+ }
+
+ // walk back to root
+ while ($parent = $active->getParent()) {
+ if ($parent instanceof AbstractPage) {
+ // prepend crumb to html
+ $html = $this->htmlify($parent)
+ . $this->getSeparator()
+ . $html;
+ }
+
+ if ($parent === $container) {
+ // at the root of the given container
+ break;
+ }
+
+ $active = $parent;
+ }
+
+ return strlen($html) ? $this->getIndent() . $html : '';
+ }
+
+ /**
+ * Renders the given $container by invoking the partial view helper.
+ *
+ * The container will simply be passed on as a model to the view script
+ * as-is, and will be available in the partial script as 'container', e.g.
+ * echo 'Number of pages: ', count($this->container);
.
+ *
+ * @param null|AbstractContainer $container [optional] container to pass to view
+ * script. Default is to use the container registered in the helper.
+ * @param null|string|array $partial [optional] partial view script to use.
+ * Default is to use the partial registered in the helper. If an array
+ * is given, the first value is used for the partial view script.
+ * @return string
+ * @throws Exception\RuntimeException if no partial provided
+ * @throws Exception\InvalidArgumentException if partial is invalid array
+ */
+ public function renderPartial($container = null, $partial = null)
+ {
+ return $this->renderPartialModel([], $container, $partial);
+ }
+
+ /**
+ * Renders the given $container by invoking the partial view helper with the given parameters as the model.
+ *
+ * The container will simply be passed on as a model to the view script
+ * as-is, and will be available in the partial script as 'container', e.g.
+ * echo 'Number of pages: ', count($this->container);
.
+ *
+ * Any parameters provided will be passed to the partial via the view model.
+ *
+ * @param null|AbstractContainer $container [optional] container to pass to view
+ * script. Default is to use the container registered in the helper.
+ * @param null|string|array $partial [optional] partial view script to use.
+ * Default is to use the partial registered in the helper. If an array
+ * is given, the first value is used for the partial view script.
+ * @return string
+ * @throws Exception\RuntimeException if no partial provided
+ * @throws Exception\InvalidArgumentException if partial is invalid array
+ */
+ public function renderPartialWithParams(array $params = [], $container = null, $partial = null)
+ {
+ return $this->renderPartialModel($params, $container, $partial);
+ }
+
+ /**
+ * Sets whether last page in breadcrumbs should be hyperlinked.
+ *
+ * @param bool $linkLast whether last page should be hyperlinked
+ * @return Breadcrumbs
+ */
+ public function setLinkLast($linkLast)
+ {
+ $this->linkLast = (bool) $linkLast;
+ return $this;
+ }
+
+ /**
+ * Returns whether last page in breadcrumbs should be hyperlinked.
+ *
+ * @return bool
+ */
+ public function getLinkLast()
+ {
+ return $this->linkLast;
+ }
+
+ /**
+ * Sets which partial view script to use for rendering menu.
+ *
+ * @param string|array $partial partial view script or null. If an array is
+ * given, the first value is used for the partial view script.
+ * @return Breadcrumbs
+ */
+ public function setPartial($partial)
+ {
+ if (null === $partial || is_string($partial) || is_array($partial)) {
+ $this->partial = $partial;
+ }
+ return $this;
+ }
+
+ /**
+ * Returns partial view script to use for rendering menu.
+ *
+ * @return string|array|null
+ */
+ public function getPartial()
+ {
+ return $this->partial;
+ }
+
+ /**
+ * Sets breadcrumb separator.
+ *
+ * @param string $separator separator string
+ * @return Breadcrumbs
+ */
+ public function setSeparator($separator)
+ {
+ if (is_string($separator)) {
+ $this->separator = $separator;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns breadcrumb separator.
+ *
+ * @return string breadcrumb separator
+ */
+ public function getSeparator()
+ {
+ return $this->separator;
+ }
+
+ /**
+ * Render a partial with the given "model".
+ *
+ * @param array $params
+ * @param null|AbstractContainer $container
+ * @param null|string|array $partial
+ * @return string
+ * @throws Exception\RuntimeException if no partial provided
+ * @throws Exception\InvalidArgumentException if partial is invalid array
+ */
+ protected function renderPartialModel(array $params, $container, $partial)
+ {
+ $this->parseContainer($container);
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+ if (null === $partial) {
+ $partial = $this->getPartial();
+ }
+ if (empty($partial)) {
+ throw new Exception\RuntimeException(
+ 'Unable to render breadcrumbs: No partial view script provided'
+ );
+ }
+ $model = array_merge($params, ['pages' => []], ['separator' => $this->getSeparator()]);
+ $active = $this->findActive($container);
+ if ($active) {
+ $active = $active['page'];
+ $model['pages'][] = $active;
+ while ($parent = $active->getParent()) {
+ if (! $parent instanceof AbstractPage) {
+ break;
+ }
+
+ $model['pages'][] = $parent;
+ if ($parent === $container) {
+ // break if at the root of the given container
+ break;
+ }
+ $active = $parent;
+ }
+ $model['pages'] = array_reverse($model['pages']);
+ }
+
+ /** @var \Zend\View\Helper\Partial $partialHelper */
+ $partialHelper = $this->view->plugin('partial');
+ if (is_array($partial)) {
+ if (count($partial) != 2) {
+ throw new Exception\InvalidArgumentException(
+ 'Unable to render breadcrumbs: A view partial supplied as '
+ . 'an array must contain one value: the partial view script'
+ );
+ }
+
+ return $partialHelper($partial[0], $model);
+ }
+
+ return $partialHelper($partial, $model);
+ }
+}
diff --git a/module/Zend/View/src/Helper/Navigation/HelperInterface.php b/module/Zend/View/src/Helper/Navigation/HelperInterface.php
new file mode 100644
index 00000000..8e46157d
--- /dev/null
+++ b/module/Zend/View/src/Helper/Navigation/HelperInterface.php
@@ -0,0 +1,143 @@
+ elements
+ */
+class Links extends AbstractHelper
+{
+ /**
+ * Constants used for specifying which link types to find and render
+ *
+ * @var int
+ */
+ const RENDER_ALTERNATE = 0x0001;
+ const RENDER_STYLESHEET = 0x0002;
+ const RENDER_START = 0x0004;
+ const RENDER_NEXT = 0x0008;
+ const RENDER_PREV = 0x0010;
+ const RENDER_CONTENTS = 0x0020;
+ const RENDER_INDEX = 0x0040;
+ const RENDER_GLOSSARY = 0x0080;
+ const RENDER_COPYRIGHT = 0x0100;
+ const RENDER_CHAPTER = 0x0200;
+ const RENDER_SECTION = 0x0400;
+ const RENDER_SUBSECTION = 0x0800;
+ const RENDER_APPENDIX = 0x1000;
+ const RENDER_HELP = 0x2000;
+ const RENDER_BOOKMARK = 0x4000;
+ const RENDER_CUSTOM = 0x8000;
+ const RENDER_ALL = 0xffff;
+
+ /**
+ * Maps render constants to W3C link types
+ *
+ * @var array
+ */
+ protected static $RELATIONS = [
+ self::RENDER_ALTERNATE => 'alternate',
+ self::RENDER_STYLESHEET => 'stylesheet',
+ self::RENDER_START => 'start',
+ self::RENDER_NEXT => 'next',
+ self::RENDER_PREV => 'prev',
+ self::RENDER_CONTENTS => 'contents',
+ self::RENDER_INDEX => 'index',
+ self::RENDER_GLOSSARY => 'glossary',
+ self::RENDER_COPYRIGHT => 'copyright',
+ self::RENDER_CHAPTER => 'chapter',
+ self::RENDER_SECTION => 'section',
+ self::RENDER_SUBSECTION => 'subsection',
+ self::RENDER_APPENDIX => 'appendix',
+ self::RENDER_HELP => 'help',
+ self::RENDER_BOOKMARK => 'bookmark',
+ ];
+
+ /**
+ * The helper's render flag
+ *
+ * @see render()
+ * @see setRenderFlag()
+ * @var int
+ */
+ protected $renderFlag = self::RENDER_ALL;
+
+ /**
+ * Root container
+ *
+ * Used for preventing methods to traverse above the container given to
+ * the {@link render()} method.
+ *
+ * @see _findRoot()
+ * @var AbstractContainer
+ */
+ protected $root;
+
+ /**
+ * Helper entry point
+ *
+ * @param string|AbstractContainer $container container to operate on
+ * @return Links
+ */
+ public function __invoke($container = null)
+ {
+ if (null !== $container) {
+ $this->setContainer($container);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Magic overload: Proxy calls to {@link findRelation()} or container
+ *
+ * Examples of finder calls:
+ *
+ * // METHOD // SAME AS
+ * $h->findRelNext($page); // $h->findRelation($page, 'rel', 'next')
+ * $h->findRevSection($page); // $h->findRelation($page, 'rev', 'section');
+ * $h->findRelFoo($page); // $h->findRelation($page, 'rel', 'foo');
+ *
+ *
+ * @param string $method
+ * @param array $arguments
+ * @return mixed
+ * @throws Exception\ExceptionInterface
+ */
+ public function __call($method, array $arguments = [])
+ {
+ ErrorHandler::start(E_WARNING);
+ $result = preg_match('/find(Rel|Rev)(.+)/', $method, $match);
+ ErrorHandler::stop();
+ if ($result) {
+ return $this->findRelation($arguments[0], strtolower($match[1]), strtolower($match[2]));
+ }
+
+ return parent::__call($method, $arguments);
+ }
+
+ /**
+ * Renders helper
+ *
+ * Implements {@link HelperInterface::render()}.
+ *
+ * @param AbstractContainer|string|null $container [optional] container to render.
+ * Default is to render the
+ * container registered in the
+ * helper.
+ * @return string
+ */
+ public function render($container = null)
+ {
+ $this->parseContainer($container);
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+ $active = $this->findActive($container);
+ if ($active) {
+ $active = $active['page'];
+ } else {
+ // no active page
+ return '';
+ }
+
+ $output = '';
+ $indent = $this->getIndent();
+ $this->root = $container;
+
+ $result = $this->findAllRelations($active, $this->getRenderFlag());
+ foreach ($result as $attrib => $types) {
+ foreach ($types as $relation => $pages) {
+ foreach ($pages as $page) {
+ $r = $this->renderLink($page, $attrib, $relation);
+ if ($r) {
+ $output .= $indent . $r . PHP_EOL;
+ }
+ }
+ }
+ }
+
+ $this->root = null;
+
+ // return output (trim last newline by spec)
+ return strlen($output) ? rtrim($output, PHP_EOL) : '';
+ }
+
+ /**
+ * Renders the given $page as a link element, with $attrib = $relation
+ *
+ * @param AbstractPage $page the page to render the link for
+ * @param string $attrib the attribute to use for $type,
+ * either 'rel' or 'rev'
+ * @param string $relation relation type, muse be one of;
+ * alternate, appendix, bookmark,
+ * chapter, contents, copyright,
+ * glossary, help, home, index, next,
+ * prev, section, start, stylesheet,
+ * subsection
+ * @return string
+ * @throws Exception\DomainException
+ */
+ public function renderLink(AbstractPage $page, $attrib, $relation)
+ {
+ if (! in_array($attrib, ['rel', 'rev'])) {
+ throw new Exception\DomainException(sprintf(
+ 'Invalid relation attribute "%s", must be "rel" or "rev"',
+ $attrib
+ ));
+ }
+
+ if (! $href = $page->getHref()) {
+ return '';
+ }
+
+ // TODO: add more attribs
+ // http://www.w3.org/TR/html401/struct/links.html#h-12.2
+ $attribs = [
+ $attrib => $relation,
+ 'href' => $href,
+ 'title' => $page->getLabel()
+ ];
+
+ return ' htmlAttribs($attribs) .
+ $this->getClosingBracket();
+ }
+
+ // Finder methods:
+
+ /**
+ * Finds all relations (forward and reverse) for the given $page
+ *
+ * The form of the returned array:
+ *
+ * // $page denotes an instance of Zend\Navigation\Page\AbstractPage
+ * $returned = array(
+ * 'rel' => array(
+ * 'alternate' => array($page, $page, $page),
+ * 'start' => array($page),
+ * 'next' => array($page),
+ * 'prev' => array($page),
+ * 'canonical' => array($page)
+ * ),
+ * 'rev' => array(
+ * 'section' => array($page)
+ * )
+ * );
+ *
+ *
+ * @param AbstractPage $page page to find links for
+ * @param null|int
+ * @return array
+ */
+ public function findAllRelations(AbstractPage $page, $flag = null)
+ {
+ if (! is_int($flag)) {
+ $flag = self::RENDER_ALL;
+ }
+
+ $result = ['rel' => [], 'rev' => []];
+ $native = array_values(static::$RELATIONS);
+
+ foreach (array_keys($result) as $rel) {
+ $meth = 'getDefined' . ucfirst($rel);
+ $types = array_merge($native, array_diff($page->$meth(), $native));
+
+ foreach ($types as $type) {
+ if (! $relFlag = array_search($type, static::$RELATIONS)) {
+ $relFlag = self::RENDER_CUSTOM;
+ }
+ if (! ($flag & $relFlag)) {
+ continue;
+ }
+
+ $found = $this->findRelation($page, $rel, $type);
+ if ($found) {
+ if (! is_array($found)) {
+ $found = [$found];
+ }
+ $result[$rel][$type] = $found;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Finds relations of the given $rel=$type from $page
+ *
+ * This method will first look for relations in the page instance, then
+ * by searching the root container if nothing was found in the page.
+ *
+ * @param AbstractPage $page page to find relations for
+ * @param string $rel relation, "rel" or "rev"
+ * @param string $type link type, e.g. 'start', 'next'
+ * @return AbstractPage|array|null
+ * @throws Exception\DomainException if $rel is not "rel" or "rev"
+ */
+ public function findRelation(AbstractPage $page, $rel, $type)
+ {
+ if (! in_array($rel, ['rel', 'rev'])) {
+ throw new Exception\DomainException(sprintf(
+ 'Invalid argument: $rel must be "rel" or "rev"; "%s" given',
+ $rel
+ ));
+ }
+
+ if (! $result = $this->findFromProperty($page, $rel, $type)) {
+ $result = $this->findFromSearch($page, $rel, $type);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Finds relations of given $type for $page by checking if the
+ * relation is specified as a property of $page
+ *
+ * @param AbstractPage $page page to find relations for
+ * @param string $rel relation, 'rel' or 'rev'
+ * @param string $type link type, e.g. 'start', 'next'
+ * @return AbstractPage|array|null
+ */
+ protected function findFromProperty(AbstractPage $page, $rel, $type)
+ {
+ $method = 'get' . ucfirst($rel);
+ $result = $page->$method($type);
+ if ($result) {
+ $result = $this->convertToPages($result);
+ if ($result) {
+ if (! is_array($result)) {
+ $result = [$result];
+ }
+
+ foreach ($result as $key => $page) {
+ if (! $this->accept($page)) {
+ unset($result[$key]);
+ }
+ }
+
+ return count($result) == 1 ? $result[0] : $result;
+ }
+ }
+
+ return;
+ }
+
+ /**
+ * Finds relations of given $rel=$type for $page by using the helper to
+ * search for the relation in the root container
+ *
+ * @param AbstractPage $page page to find relations for
+ * @param string $rel relation, 'rel' or 'rev'
+ * @param string $type link type, e.g. 'start', 'next', etc
+ * @return array|null
+ */
+ protected function findFromSearch(AbstractPage $page, $rel, $type)
+ {
+ $found = null;
+
+ $method = 'search' . ucfirst($rel) . ucfirst($type);
+ if (method_exists($this, $method)) {
+ $found = $this->$method($page);
+ }
+
+ return $found;
+ }
+
+ // Search methods:
+
+ /**
+ * Searches the root container for the forward 'start' relation of the given
+ * $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to the first document in a collection of documents. This link type
+ * tells search engines which document is considered by the author to be the
+ * starting point of the collection.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|null
+ */
+ public function searchRelStart(AbstractPage $page)
+ {
+ $found = $this->findRoot($page);
+ if (! $found instanceof AbstractPage) {
+ $found->rewind();
+ $found = $found->current();
+ }
+
+ if ($found === $page || ! $this->accept($found)) {
+ $found = null;
+ }
+
+ return $found;
+ }
+
+ /**
+ * Searches the root container for the forward 'next' relation of the given
+ * $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to the next document in a linear sequence of documents. User
+ * agents may choose to preload the "next" document, to reduce the perceived
+ * load time.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|null
+ */
+ public function searchRelNext(AbstractPage $page)
+ {
+ $found = null;
+ $break = false;
+ $iterator = new RecursiveIteratorIterator($this->findRoot($page), RecursiveIteratorIterator::SELF_FIRST);
+ foreach ($iterator as $intermediate) {
+ if ($intermediate === $page) {
+ // current page; break at next accepted page
+ $break = true;
+ continue;
+ }
+
+ if ($break && $this->accept($intermediate)) {
+ $found = $intermediate;
+ break;
+ }
+ }
+
+ return $found;
+ }
+
+ /**
+ * Searches the root container for the forward 'prev' relation of the given
+ * $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to the previous document in an ordered series of documents. Some
+ * user agents also support the synonym "Previous".
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|null
+ */
+ public function searchRelPrev(AbstractPage $page)
+ {
+ $found = null;
+ $prev = null;
+ $iterator = new RecursiveIteratorIterator(
+ $this->findRoot($page),
+ RecursiveIteratorIterator::SELF_FIRST
+ );
+ foreach ($iterator as $intermediate) {
+ if (! $this->accept($intermediate)) {
+ continue;
+ }
+ if ($intermediate === $page) {
+ $found = $prev;
+ break;
+ }
+
+ $prev = $intermediate;
+ }
+
+ return $found;
+ }
+
+ /**
+ * Searches the root container for forward 'chapter' relations of the given
+ * $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to a document serving as a chapter in a collection of documents.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|array|null
+ */
+ public function searchRelChapter(AbstractPage $page)
+ {
+ $found = [];
+
+ // find first level of pages
+ $root = $this->findRoot($page);
+
+ // find start page(s)
+ $start = $this->findRelation($page, 'rel', 'start');
+ if (! is_array($start)) {
+ $start = [$start];
+ }
+
+ foreach ($root as $chapter) {
+ // exclude self and start page from chapters
+ if ($chapter !== $page &&
+ ! in_array($chapter, $start) &&
+ $this->accept($chapter)) {
+ $found[] = $chapter;
+ }
+ }
+
+ switch (count($found)) {
+ case 0:
+ return;
+ case 1:
+ return $found[0];
+ default:
+ return $found;
+ }
+ }
+
+ /**
+ * Searches the root container for forward 'section' relations of the given
+ * $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to a document serving as a section in a collection of documents.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|array|null
+ */
+ public function searchRelSection(AbstractPage $page)
+ {
+ $found = [];
+
+ // check if given page has pages and is a chapter page
+ if ($page->hasPages() && $this->findRoot($page)->hasPage($page)) {
+ foreach ($page as $section) {
+ if ($this->accept($section)) {
+ $found[] = $section;
+ }
+ }
+ }
+
+ switch (count($found)) {
+ case 0:
+ return;
+ case 1:
+ return $found[0];
+ default:
+ return $found;
+ }
+ }
+
+ /**
+ * Searches the root container for forward 'subsection' relations of the
+ * given $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to a document serving as a subsection in a collection of
+ * documents.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|array|null
+ */
+ public function searchRelSubsection(AbstractPage $page)
+ {
+ $found = [];
+
+ if ($page->hasPages()) {
+ // given page has child pages, loop chapters
+ foreach ($this->findRoot($page) as $chapter) {
+ // is page a section?
+ if ($chapter->hasPage($page)) {
+ foreach ($page as $subsection) {
+ if ($this->accept($subsection)) {
+ $found[] = $subsection;
+ }
+ }
+ }
+ }
+ }
+
+ switch (count($found)) {
+ case 0:
+ return;
+ case 1:
+ return $found[0];
+ default:
+ return $found;
+ }
+ }
+
+ /**
+ * Searches the root container for the reverse 'section' relation of the
+ * given $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to a document serving as a section in a collection of documents.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|null
+ */
+ public function searchRevSection(AbstractPage $page)
+ {
+ $found = null;
+ $parent = $page->getParent();
+ if ($parent) {
+ if ($parent instanceof AbstractPage &&
+ $this->findRoot($page)->hasPage($parent)) {
+ $found = $parent;
+ }
+ }
+
+ return $found;
+ }
+
+ /**
+ * Searches the root container for the reverse 'section' relation of the
+ * given $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to a document serving as a subsection in a collection of
+ * documents.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|null
+ */
+ public function searchRevSubsection(AbstractPage $page)
+ {
+ $found = null;
+ $parent = $page->getParent();
+ if ($parent) {
+ if ($parent instanceof AbstractPage) {
+ $root = $this->findRoot($page);
+ foreach ($root as $chapter) {
+ if ($chapter->hasPage($parent)) {
+ $found = $parent;
+ break;
+ }
+ }
+ }
+ }
+
+ return $found;
+ }
+
+ // Util methods:
+
+ /**
+ * Returns the root container of the given page
+ *
+ * When rendering a container, the render method still store the given
+ * container as the root container, and unset it when done rendering. This
+ * makes sure finder methods will not traverse above the container given
+ * to the render method.
+ *
+ * @param AbstractPage $page
+ * @return AbstractContainer
+ */
+ protected function findRoot(AbstractPage $page)
+ {
+ if ($this->root) {
+ return $this->root;
+ }
+
+ $root = $page;
+
+ while ($parent = $page->getParent()) {
+ $root = $parent;
+ if ($parent instanceof AbstractPage) {
+ $page = $parent;
+ } else {
+ break;
+ }
+ }
+
+ return $root;
+ }
+
+ /**
+ * Converts a $mixed value to an array of pages
+ *
+ * @param mixed $mixed mixed value to get page(s) from
+ * @param bool $recursive whether $value should be looped
+ * if it is an array or a config
+ * @return AbstractPage|array|null
+ */
+ protected function convertToPages($mixed, $recursive = true)
+ {
+ if ($mixed instanceof AbstractPage) {
+ // value is a page instance; return directly
+ return $mixed;
+ } elseif ($mixed instanceof AbstractContainer) {
+ // value is a container; return pages in it
+ $pages = [];
+ foreach ($mixed as $page) {
+ $pages[] = $page;
+ }
+ return $pages;
+ } elseif ($mixed instanceof Traversable) {
+ $mixed = ArrayUtils::iteratorToArray($mixed);
+ } elseif (is_string($mixed)) {
+ // value is a string; make a URI page
+ return AbstractPage::factory([
+ 'type' => 'uri',
+ 'uri' => $mixed
+ ]);
+ }
+
+ if (is_array($mixed) && ! empty($mixed)) {
+ if ($recursive && is_numeric(key($mixed))) {
+ // first key is numeric; assume several pages
+ $pages = [];
+ foreach ($mixed as $value) {
+ $value = $this->convertToPages($value, false);
+ if ($value) {
+ $pages[] = $value;
+ }
+ }
+ return $pages;
+ } else {
+ // pass array to factory directly
+ try {
+ $page = AbstractPage::factory($mixed);
+ return $page;
+ } catch (\Exception $e) {
+ }
+ }
+ }
+
+ // nothing found
+ return;
+ }
+
+ /**
+ * Sets the helper's render flag
+ *
+ * The helper uses the bitwise '&' operator against the hex values of the
+ * render constants. This means that the flag can is "bitwised" value of
+ * the render constants. Examples:
+ *
+ * // render all links except glossary
+ * $flag = Links:RENDER_ALL ^ Links:RENDER_GLOSSARY;
+ * $helper->setRenderFlag($flag);
+ *
+ * // render only chapters and sections
+ * $flag = Links:RENDER_CHAPTER | Links:RENDER_SECTION;
+ * $helper->setRenderFlag($flag);
+ *
+ * // render only relations that are not native W3C relations
+ * $helper->setRenderFlag(Links:RENDER_CUSTOM);
+ *
+ * // render all relations (default)
+ * $helper->setRenderFlag(Links:RENDER_ALL);
+ *
+ *
+ * Note that custom relations can also be rendered directly using the
+ * {@link renderLink()} method.
+ *
+ * @param int $renderFlag
+ * @return Links
+ */
+ public function setRenderFlag($renderFlag)
+ {
+ $this->renderFlag = (int) $renderFlag;
+
+ return $this;
+ }
+
+ /**
+ * Returns the helper's render flag
+ *
+ * @return int
+ */
+ public function getRenderFlag()
+ {
+ return $this->renderFlag;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Navigation/Listener/AclListener.php b/module/Zend/View/src/Helper/Navigation/Listener/AclListener.php
new file mode 100644
index 00000000..caf06192
--- /dev/null
+++ b/module/Zend/View/src/Helper/Navigation/Listener/AclListener.php
@@ -0,0 +1,55 @@
+getParams();
+ $acl = $params['acl'];
+ $page = $params['page'];
+ $role = $params['role'];
+
+ if (! $acl) {
+ return $accepted;
+ }
+
+ $resource = $page->getResource();
+ $privilege = $page->getPrivilege();
+
+ if ($resource || $privilege) {
+ $accepted = $acl->hasResource($resource)
+ && $acl->isAllowed($role, $resource, $privilege);
+ }
+
+ return $accepted;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Navigation/Menu.php b/module/Zend/View/src/Helper/Navigation/Menu.php
new file mode 100644
index 00000000..302dc472
--- /dev/null
+++ b/module/Zend/View/src/Helper/Navigation/Menu.php
@@ -0,0 +1,782 @@
+ element.
+ *
+ * @var bool
+ */
+ protected $addClassToListItem = false;
+
+ /**
+ * Whether labels should be escaped.
+ *
+ * @var bool
+ */
+ protected $escapeLabels = true;
+
+ /**
+ * Whether only active branch should be rendered.
+ *
+ * @var bool
+ */
+ protected $onlyActiveBranch = false;
+
+ /**
+ * Partial view script to use for rendering menu.
+ *
+ * @var string|array
+ */
+ protected $partial = null;
+
+ /**
+ * Whether parents should be rendered when only rendering active branch.
+ *
+ * @var bool
+ */
+ protected $renderParents = true;
+
+ /**
+ * CSS class to use for the ul element.
+ *
+ * @var string
+ */
+ protected $ulClass = 'navigation';
+
+ /**
+ * CSS class to use for the active li element.
+ *
+ * @var string
+ */
+ protected $liActiveClass = 'active';
+
+ /**
+ * View helper entry point.
+ *
+ * Retrieves helper and optionally sets container to operate on.
+ *
+ * @param AbstractContainer $container [optional] container to operate on
+ * @return self
+ */
+ public function __invoke($container = null)
+ {
+ if (null !== $container) {
+ $this->setContainer($container);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Renders menu.
+ *
+ * Implements {@link HelperInterface::render()}.
+ *
+ * If a partial view is registered in the helper, the menu will be rendered
+ * using the given partial script. If no partial is registered, the menu
+ * will be rendered as an 'ul' element by the helper's internal method.
+ *
+ * @see renderPartial()
+ * @see renderMenu()
+ * @param AbstractContainer $container [optional] container to render. Default is
+ * to render the container registered in the helper.
+ * @return string
+ */
+ public function render($container = null)
+ {
+ $partial = $this->getPartial();
+ if ($partial) {
+ return $this->renderPartial($container, $partial);
+ }
+
+ return $this->renderMenu($container);
+ }
+
+ /**
+ * Renders the deepest active menu within [$minDepth, $maxDepth], (called from {@link renderMenu()}).
+ *
+ * @param AbstractContainer $container container to render
+ * @param string $ulClass CSS class for first UL
+ * @param string $indent initial indentation
+ * @param int|null $minDepth minimum depth
+ * @param int|null $maxDepth maximum depth
+ * @param bool $escapeLabels Whether or not to escape the labels
+ * @param bool $addClassToListItem Whether or not page class applied to element
+ * @param string $liActiveClass CSS class for active LI
+ * @return string
+ */
+ protected function renderDeepestMenu(
+ AbstractContainer $container,
+ $ulClass,
+ $indent,
+ $minDepth,
+ $maxDepth,
+ $escapeLabels,
+ $addClassToListItem,
+ $liActiveClass
+ ) {
+ if (! $active = $this->findActive($container, $minDepth - 1, $maxDepth)) {
+ return '';
+ }
+
+ // special case if active page is one below minDepth
+ if ($active['depth'] < $minDepth) {
+ if (! $active['page']->hasPages(! $this->renderInvisible)) {
+ return '';
+ }
+ } elseif (! $active['page']->hasPages(! $this->renderInvisible)) {
+ // found pages has no children; render siblings
+ $active['page'] = $active['page']->getParent();
+ } elseif (is_int($maxDepth) && $active['depth'] + 1 > $maxDepth) {
+ // children are below max depth; render siblings
+ $active['page'] = $active['page']->getParent();
+ }
+
+ /* @var $escaper \Zend\View\Helper\EscapeHtmlAttr */
+ $escaper = $this->view->plugin('escapeHtmlAttr');
+ $ulClass = $ulClass ? ' class="' . $escaper($ulClass) . '"' : '';
+ $html = $indent . '' . PHP_EOL;
+
+ foreach ($active['page'] as $subPage) {
+ if (! $this->accept($subPage)) {
+ continue;
+ }
+
+ // render li tag and page
+ $liClasses = [];
+
+ // Is page active?
+ if ($subPage->isActive(true)) {
+ $liClasses[] = $liActiveClass;
+ }
+
+ // Add CSS class from page to
+ if ($addClassToListItem && $subPage->getClass()) {
+ $liClasses[] = $subPage->getClass();
+ }
+
+ $liClass = empty($liClasses) ? '' : ' class="' . $escaper(implode(' ', $liClasses)) . '"';
+ $html .= $indent . ' ' . PHP_EOL;
+ $html .= $indent . ' ' . $this->htmlify($subPage, $escapeLabels, $addClassToListItem) . PHP_EOL;
+ $html .= $indent . ' ' . PHP_EOL;
+ }
+
+ $html .= $indent . ' ';
+
+ return $html;
+ }
+
+ /**
+ * Renders helper.
+ *
+ * Renders a HTML 'ul' for the given $container. If $container is not given,
+ * the container registered in the helper will be used.
+ *
+ * Available $options:
+ *
+ * @param AbstractContainer $container [optional] container to create menu from.
+ * Default is to use the container retrieved from {@link getContainer()}.
+ * @param array $options [optional] options for controlling rendering
+ * @return string
+ */
+ public function renderMenu($container = null, array $options = [])
+ {
+ $this->parseContainer($container);
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+ $options = $this->normalizeOptions($options);
+ if ($options['onlyActiveBranch'] && ! $options['renderParents']) {
+ return $this->renderDeepestMenu(
+ $container,
+ $options['ulClass'],
+ $options['indent'],
+ $options['minDepth'],
+ $options['maxDepth'],
+ $options['escapeLabels'],
+ $options['addClassToListItem'],
+ $options['liActiveClass']
+ );
+ }
+
+ return $this->renderNormalMenu(
+ $container,
+ $options['ulClass'],
+ $options['indent'],
+ $options['minDepth'],
+ $options['maxDepth'],
+ $options['onlyActiveBranch'],
+ $options['escapeLabels'],
+ $options['addClassToListItem'],
+ $options['liActiveClass']
+ );
+ }
+
+ /**
+ * Renders a normal menu (called from {@link renderMenu()}).
+ *
+ * @param AbstractContainer $container container to render
+ * @param string $ulClass CSS class for first UL
+ * @param string $indent initial indentation
+ * @param int|null $minDepth minimum depth
+ * @param int|null $maxDepth maximum depth
+ * @param bool $onlyActive render only active branch?
+ * @param bool $escapeLabels Whether or not to escape the labels
+ * @param bool $addClassToListItem Whether or not page class applied to element
+ * @param string $liActiveClass CSS class for active LI
+ * @return string
+ */
+ protected function renderNormalMenu(
+ AbstractContainer $container,
+ $ulClass,
+ $indent,
+ $minDepth,
+ $maxDepth,
+ $onlyActive,
+ $escapeLabels,
+ $addClassToListItem,
+ $liActiveClass
+ ) {
+ $html = '';
+
+ // find deepest active
+ $found = $this->findActive($container, $minDepth, $maxDepth);
+
+ /* @var $escaper \Zend\View\Helper\EscapeHtmlAttr */
+ $escaper = $this->view->plugin('escapeHtmlAttr');
+
+ if ($found) {
+ $foundPage = $found['page'];
+ $foundDepth = $found['depth'];
+ } else {
+ $foundPage = null;
+ }
+
+ // create iterator
+ $iterator = new RecursiveIteratorIterator(
+ $container,
+ RecursiveIteratorIterator::SELF_FIRST
+ );
+
+ if (is_int($maxDepth)) {
+ $iterator->setMaxDepth($maxDepth);
+ }
+
+ // iterate container
+ $prevDepth = -1;
+ foreach ($iterator as $page) {
+ $depth = $iterator->getDepth();
+ $isActive = $page->isActive(true);
+ if ($depth < $minDepth || ! $this->accept($page)) {
+ // page is below minDepth or not accepted by acl/visibility
+ continue;
+ } elseif ($onlyActive && ! $isActive) {
+ // page is not active itself, but might be in the active branch
+ $accept = false;
+ if ($foundPage) {
+ if ($foundPage->hasPage($page)) {
+ // accept if page is a direct child of the active page
+ $accept = true;
+ } elseif ($foundPage->getParent()->hasPage($page)) {
+ // page is a sibling of the active page...
+ if (! $foundPage->hasPages(! $this->renderInvisible)
+ || is_int($maxDepth) && $foundDepth + 1 > $maxDepth
+ ) {
+ // accept if active page has no children, or the
+ // children are too deep to be rendered
+ $accept = true;
+ }
+ }
+ }
+ if (! $accept) {
+ continue;
+ }
+ }
+
+ // make sure indentation is correct
+ $depth -= $minDepth;
+ $myIndent = $indent.str_repeat(' ', $depth);
+ if ($depth > $prevDepth) {
+ // start new ul tag
+ if ($ulClass && $depth == 0) {
+ $ulClass = ' class="' . $escaper($ulClass) . '"';
+ } else {
+ $ulClass = '';
+ }
+ $html .= $myIndent . '' . PHP_EOL;
+ } elseif ($prevDepth > $depth) {
+ // close li/ul tags until we're at current depth
+ for ($i = $prevDepth; $i > $depth; $i--) {
+ $ind = $indent.str_repeat(' ', $i);
+ $html .= $ind . ' ' . PHP_EOL;
+ $html .= $ind . ' ' . PHP_EOL;
+ }
+ // close previous li tag
+ $html .= $myIndent . ' ' . PHP_EOL;
+ } else {
+ // close previous li tag
+ $html .= $myIndent . ' ' . PHP_EOL;
+ }
+
+ // render li tag and page
+ $liClasses = [];
+
+ // Is page active?
+ if ($isActive) {
+ $liClasses[] = $liActiveClass;
+ }
+
+ // Add CSS class from page to
+ if ($addClassToListItem && $page->getClass()) {
+ $liClasses[] = $page->getClass();
+ }
+ $liClass = empty($liClasses) ? '' : ' class="' . $escaper(implode(' ', $liClasses)) . '"';
+ $html .= $myIndent . ' ' . PHP_EOL
+ . $myIndent . ' ' . $this->htmlify($page, $escapeLabels, $addClassToListItem) . PHP_EOL;
+
+ // store as previous depth for next iteration
+ $prevDepth = $depth;
+ }
+
+ if ($html) {
+ // done iterating container; close open ul/li tags
+ for ($i = $prevDepth + 1; $i > 0; $i--) {
+ $myIndent = $indent . str_repeat(' ', $i - 1);
+ $html .= $myIndent . ' ' . PHP_EOL
+ . $myIndent . '' . PHP_EOL;
+ }
+ $html = rtrim($html, PHP_EOL);
+ }
+
+ return $html;
+ }
+
+ /**
+ * Renders the given $container by invoking the partial view helper.
+ *
+ * The container will simply be passed on as a model to the view script
+ * as-is, and will be available in the partial script as 'container', e.g.
+ * echo 'Number of pages: ', count($this->container);
.
+ *
+ * @param null|AbstractContainer $container [optional] container to pass to view
+ * script. Default is to use the container registered in the helper.
+ * @param null|string|array $partial [optional] partial view script to use.
+ * Default is to use the partial registered in the helper. If an array
+ * is given, the first value is used for the partial view script.
+ * @return string
+ * @throws Exception\RuntimeException if no partial provided
+ * @throws Exception\InvalidArgumentException if partial is invalid array
+ */
+ public function renderPartial($container = null, $partial = null)
+ {
+ return $this->renderPartialModel([], $container, $partial);
+ }
+
+ /**
+ * Renders the given $container by invoking the partial view helper with the given parameters as the model.
+ *
+ * The container will simply be passed on as a model to the view script
+ * as-is, and will be available in the partial script as 'container', e.g.
+ * echo 'Number of pages: ', count($this->container);
.
+ *
+ * Any parameters provided will be passed to the partial via the view model.
+ *
+ * @param null|AbstractContainer $container [optional] container to pass to view
+ * script. Default is to use the container registered in the helper.
+ * @param null|string|array $partial [optional] partial view script to use.
+ * Default is to use the partial registered in the helper. If an array
+ * is given, the first value is used for the partial view script.
+ * @return string
+ * @throws Exception\RuntimeException if no partial provided
+ * @throws Exception\InvalidArgumentException if partial is invalid array
+ */
+ public function renderPartialWithParams(array $params = [], $container = null, $partial = null)
+ {
+ return $this->renderPartialModel($params, $container, $partial);
+ }
+
+ /**
+ * Renders the inner-most sub menu for the active page in the $container.
+ *
+ * This is a convenience method which is equivalent to the following call:
+ *
+ * renderMenu($container, array(
+ * 'indent' => $indent,
+ * 'ulClass' => $ulClass,
+ * 'minDepth' => null,
+ * 'maxDepth' => null,
+ * 'onlyActiveBranch' => true,
+ * 'renderParents' => false,
+ * 'liActiveClass' => $liActiveClass
+ * ));
+ *
+ *
+ * @param AbstractContainer $container [optional] container to render.
+ * Default is to render the container registered in the helper.
+ * @param string $ulClass [optional] CSS class to use for UL element.
+ * Default is to use the value from {@link getUlClass()}.
+ * @param string|int $indent [optional] indentation as a string or number
+ * of spaces. Default is to use the value retrieved from
+ * {@link getIndent()}.
+ * @param string $liActiveClass [optional] CSS class to use for UL
+ * element. Default is to use the value from {@link getUlClass()}.
+ * @return string
+ */
+ public function renderSubMenu(
+ AbstractContainer $container = null,
+ $ulClass = null,
+ $indent = null,
+ $liActiveClass = null
+ ) {
+ return $this->renderMenu($container, [
+ 'indent' => $indent,
+ 'ulClass' => $ulClass,
+ 'minDepth' => null,
+ 'maxDepth' => null,
+ 'onlyActiveBranch' => true,
+ 'renderParents' => false,
+ 'escapeLabels' => true,
+ 'addClassToListItem' => false,
+ 'liActiveClass' => $liActiveClass,
+ ]);
+ }
+
+ /**
+ * Returns an HTML string containing an 'a' element for the given page if
+ * the page's href is not empty, and a 'span' element if it is empty.
+ *
+ * Overrides {@link AbstractHelper::htmlify()}.
+ *
+ * @param AbstractPage $page page to generate HTML for
+ * @param bool $escapeLabel Whether or not to escape the label
+ * @param bool $addClassToListItem Whether or not to add the page class to the list item
+ * @return string
+ */
+ public function htmlify(AbstractPage $page, $escapeLabel = true, $addClassToListItem = false)
+ {
+ // get attribs for element
+ $attribs = [
+ 'id' => $page->getId(),
+ 'title' => $this->translate($page->getTitle(), $page->getTextDomain()),
+ ];
+
+ if ($addClassToListItem === false) {
+ $attribs['class'] = $page->getClass();
+ }
+
+ // does page have a href?
+ $href = $page->getHref();
+ if ($href) {
+ $element = 'a';
+ $attribs['href'] = $href;
+ $attribs['target'] = $page->getTarget();
+ } else {
+ $element = 'span';
+ }
+
+ $html = '<' . $element . $this->htmlAttribs($attribs) . '>';
+ $label = $this->translate($page->getLabel(), $page->getTextDomain());
+
+ if ($escapeLabel === true) {
+ /** @var \Zend\View\Helper\EscapeHtml $escaper */
+ $escaper = $this->view->plugin('escapeHtml');
+ $html .= $escaper($label);
+ } else {
+ $html .= $label;
+ }
+
+ $html .= '' . $element . '>';
+ return $html;
+ }
+
+ /**
+ * Normalizes given render options.
+ *
+ * @param array $options [optional] options to normalize
+ * @return array
+ */
+ protected function normalizeOptions(array $options = [])
+ {
+ if (isset($options['indent'])) {
+ $options['indent'] = $this->getWhitespace($options['indent']);
+ } else {
+ $options['indent'] = $this->getIndent();
+ }
+
+ if (isset($options['ulClass']) && $options['ulClass'] !== null) {
+ $options['ulClass'] = (string) $options['ulClass'];
+ } else {
+ $options['ulClass'] = $this->getUlClass();
+ }
+
+ if (array_key_exists('minDepth', $options)) {
+ if (null !== $options['minDepth']) {
+ $options['minDepth'] = (int) $options['minDepth'];
+ }
+ } else {
+ $options['minDepth'] = $this->getMinDepth();
+ }
+
+ if ($options['minDepth'] < 0 || $options['minDepth'] === null) {
+ $options['minDepth'] = 0;
+ }
+
+ if (array_key_exists('maxDepth', $options)) {
+ if (null !== $options['maxDepth']) {
+ $options['maxDepth'] = (int) $options['maxDepth'];
+ }
+ } else {
+ $options['maxDepth'] = $this->getMaxDepth();
+ }
+
+ if (! isset($options['onlyActiveBranch'])) {
+ $options['onlyActiveBranch'] = $this->getOnlyActiveBranch();
+ }
+
+ if (! isset($options['escapeLabels'])) {
+ $options['escapeLabels'] = $this->escapeLabels;
+ }
+
+ if (! isset($options['renderParents'])) {
+ $options['renderParents'] = $this->getRenderParents();
+ }
+
+ if (! isset($options['addClassToListItem'])) {
+ $options['addClassToListItem'] = $this->getAddClassToListItem();
+ }
+
+ if (isset($options['liActiveClass']) && $options['liActiveClass'] !== null) {
+ $options['liActiveClass'] = (string) $options['liActiveClass'];
+ } else {
+ $options['liActiveClass'] = $this->getLiActiveClass();
+ }
+
+ return $options;
+ }
+
+ /**
+ * Sets a flag indicating whether labels should be escaped.
+ *
+ * @param bool $flag [optional] escape labels
+ * @return self
+ */
+ public function escapeLabels($flag = true)
+ {
+ $this->escapeLabels = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Enables/disables page class applied to element.
+ *
+ * @param bool $flag [optional] page class applied to element Default
+ * is true.
+ * @return self fluent interface, returns self
+ */
+ public function setAddClassToListItem($flag = true)
+ {
+ $this->addClassToListItem = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Returns flag indicating whether page class should be applied to element.
+ *
+ * By default, this value is false.
+ *
+ * @return bool whether parents should be rendered
+ */
+ public function getAddClassToListItem()
+ {
+ return $this->addClassToListItem;
+ }
+
+ /**
+ * Sets a flag indicating whether only active branch should be rendered.
+ *
+ * @param bool $flag [optional] render only active branch.
+ * @return self
+ */
+ public function setOnlyActiveBranch($flag = true)
+ {
+ $this->onlyActiveBranch = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Returns a flag indicating whether only active branch should be rendered.
+ *
+ * By default, this value is false, meaning the entire menu will be
+ * be rendered.
+ *
+ * @return bool
+ */
+ public function getOnlyActiveBranch()
+ {
+ return $this->onlyActiveBranch;
+ }
+
+ /**
+ * Sets which partial view script to use for rendering menu.
+ *
+ * @param string|array $partial partial view script or null. If an array
+ * is given, the first value is used for the partial view script.
+ * @return self
+ */
+ public function setPartial($partial)
+ {
+ if (null === $partial || is_string($partial) || is_array($partial)) {
+ $this->partial = $partial;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns partial view script to use for rendering menu.
+ *
+ * @return string|array|null
+ */
+ public function getPartial()
+ {
+ return $this->partial;
+ }
+
+ /**
+ * Enables/disables rendering of parents when only rendering active branch.
+ *
+ * See {@link setOnlyActiveBranch()} for more information.
+ *
+ * @param bool $flag [optional] render parents when rendering active branch.
+ * @return self
+ */
+ public function setRenderParents($flag = true)
+ {
+ $this->renderParents = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Returns flag indicating whether parents should be rendered when rendering only the active branch.
+ *
+ * By default, this value is true.
+ *
+ * @return bool
+ */
+ public function getRenderParents()
+ {
+ return $this->renderParents;
+ }
+
+ /**
+ * Sets CSS class to use for the first 'ul' element when rendering.
+ *
+ * @param string $ulClass CSS class to set
+ * @return self
+ */
+ public function setUlClass($ulClass)
+ {
+ if (is_string($ulClass)) {
+ $this->ulClass = $ulClass;
+ }
+ return $this;
+ }
+
+ /**
+ * Returns CSS class to use for the first 'ul' element when rendering.
+ *
+ * @return string
+ */
+ public function getUlClass()
+ {
+ return $this->ulClass;
+ }
+
+ /**
+ * Sets CSS class to use for the active 'li' element when rendering.
+ *
+ * @param string $liActiveClass CSS class to set
+ * @return self
+ */
+ public function setLiActiveClass($liActiveClass)
+ {
+ if (is_string($liActiveClass)) {
+ $this->liActiveClass = $liActiveClass;
+ }
+ return $this;
+ }
+
+ /**
+ * Returns CSS class to use for the active 'li' element when rendering.
+ *
+ * @return string
+ */
+ public function getLiActiveClass()
+ {
+ return $this->liActiveClass;
+ }
+
+ /**
+ * Render a partial with the given "model".
+ *
+ * @param array $params
+ * @param null|AbstractContainer $container
+ * @param null|string|array $partial
+ * @return string
+ * @throws Exception\RuntimeException if no partial provided
+ * @throws Exception\InvalidArgumentException if partial is invalid array
+ */
+ protected function renderPartialModel(array $params, $container, $partial)
+ {
+ $this->parseContainer($container);
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+ if (null === $partial) {
+ $partial = $this->getPartial();
+ }
+
+ if (empty($partial)) {
+ throw new Exception\RuntimeException(
+ 'Unable to render menu: No partial view script provided'
+ );
+ }
+
+ $model = array_merge($params, ['container' => $container]);
+
+ /** @var \Zend\View\Helper\Partial $partialHelper */
+ $partialHelper = $this->view->plugin('partial');
+ if (is_array($partial)) {
+ if (count($partial) != 2) {
+ throw new Exception\InvalidArgumentException(
+ 'Unable to render menu: A view partial supplied as '
+ . 'an array must contain one value: the partial view script'
+ );
+ }
+
+ return $partialHelper($partial[0], $model);
+ }
+
+ return $partialHelper($partial, $model);
+ }
+}
diff --git a/module/Zend/View/src/Helper/Navigation/PluginManager.php b/module/Zend/View/src/Helper/Navigation/PluginManager.php
new file mode 100644
index 00000000..a6ab8910
--- /dev/null
+++ b/module/Zend/View/src/Helper/Navigation/PluginManager.php
@@ -0,0 +1,98 @@
+ Breadcrumbs::class,
+ 'links' => Links::class,
+ 'menu' => Menu::class,
+ 'sitemap' => Sitemap::class,
+ ];
+
+ /**
+ * Default factories
+ *
+ * @var string[]
+ */
+ protected $factories = [
+ Breadcrumbs::class => InvokableFactory::class,
+ Links::class => InvokableFactory::class,
+ Menu::class => InvokableFactory::class,
+ Sitemap::class => InvokableFactory::class,
+
+ // v2 canonical FQCNs
+
+ 'zendviewhelpernavigationbreadcrumbs' => InvokableFactory::class,
+ 'zendviewhelpernavigationlinks' => InvokableFactory::class,
+ 'zendviewhelpernavigationmenu' => InvokableFactory::class,
+ 'zendviewhelpernavigationsitemap' => InvokableFactory::class,
+ ];
+
+ /**
+ * @param null|ConfigInterface|ContainerInterface $configOrContainerInstance
+ * @param array $v3config If $configOrContainerInstance is a container, this
+ * value will be passed to the parent constructor.
+ */
+ public function __construct($configOrContainerInstance = null, array $v3config = [])
+ {
+ $this->initializers[] = function ($first, $second) {
+ // v2 vs v3 argument order
+ if ($first instanceof ContainerInterface) {
+ // v3
+ $container = $first;
+ $instance = $second;
+ } else {
+ // v2
+ $container = $second;
+ $instance = $first;
+ }
+
+ if (! $instance instanceof AbstractHelper) {
+ return;
+ }
+
+ // This initializer was written with v2 functionality in mind; as such,
+ // we need to test and see if we're called in a v2 context, and, if so,
+ // set the service locator to the parent locator.
+ //
+ // Under v3, the parent locator is what is passed to the method already.
+ if (! method_exists($container, 'configure') && $container->getServiceLocator()) {
+ $container = $container->getServiceLocator();
+ }
+
+ $instance->setServiceLocator($container);
+ };
+
+ parent::__construct($configOrContainerInstance, $v3config);
+ }
+}
diff --git a/module/Zend/View/src/Helper/Navigation/Sitemap.php b/module/Zend/View/src/Helper/Navigation/Sitemap.php
new file mode 100644
index 00000000..76acb227
--- /dev/null
+++ b/module/Zend/View/src/Helper/Navigation/Sitemap.php
@@ -0,0 +1,439 @@
+ tag
+ *
+ * @var string
+ */
+ const SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
+
+ /**
+ * Schema URL
+ *
+ * @var string
+ */
+ const SITEMAP_XSD = 'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd';
+
+ /**
+ * Whether XML output should be formatted
+ *
+ * @var bool
+ */
+ protected $formatOutput = false;
+
+ /**
+ * Server url
+ *
+ * @var string
+ */
+ protected $serverUrl;
+
+ /**
+ * List of urls in the sitemap
+ *
+ * @var array
+ */
+ protected $urls = [];
+
+ /**
+ * Whether sitemap should be validated using Zend\Validate\Sitemap\*
+ *
+ * @var bool
+ */
+ protected $useSitemapValidators = true;
+
+ /**
+ * Whether sitemap should be schema validated when generated
+ *
+ * @var bool
+ */
+ protected $useSchemaValidation = false;
+
+ /**
+ * Whether the XML declaration should be included in XML output
+ *
+ * @var bool
+ */
+ protected $useXmlDeclaration = true;
+
+ /**
+ * Helper entry point
+ *
+ * @param string|AbstractContainer $container container to operate on
+ * @return Sitemap
+ */
+ public function __invoke($container = null)
+ {
+ if (null !== $container) {
+ $this->setContainer($container);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Renders helper
+ *
+ * Implements {@link HelperInterface::render()}.
+ *
+ * @param AbstractContainer $container [optional] container to render. Default is
+ * to render the container registered in the helper.
+ * @return string
+ */
+ public function render($container = null)
+ {
+ $dom = $this->getDomSitemap($container);
+ $xml = $this->getUseXmlDeclaration() ?
+ $dom->saveXML() :
+ $dom->saveXML($dom->documentElement);
+
+ return rtrim($xml, PHP_EOL);
+ }
+
+ /**
+ * Returns a DOMDocument containing the Sitemap XML for the given container
+ *
+ * @param AbstractContainer $container [optional] container to get
+ * breadcrumbs from, defaults
+ * to what is registered in the
+ * helper
+ * @return DOMDocument DOM representation of the
+ * container
+ * @throws Exception\RuntimeException if schema validation is on
+ * and the sitemap is invalid
+ * according to the sitemap
+ * schema, or if sitemap
+ * validators are used and the
+ * loc element fails validation
+ */
+ public function getDomSitemap(AbstractContainer $container = null)
+ {
+ // Reset the urls
+ $this->urls = [];
+
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+ // check if we should validate using our own validators
+ if ($this->getUseSitemapValidators()) {
+ // create validators
+ $locValidator = new \Zend\Validator\Sitemap\Loc();
+ $lastmodValidator = new \Zend\Validator\Sitemap\Lastmod();
+ $changefreqValidator = new \Zend\Validator\Sitemap\Changefreq();
+ $priorityValidator = new \Zend\Validator\Sitemap\Priority();
+ }
+
+ // create document
+ $dom = new DOMDocument('1.0', 'UTF-8');
+ $dom->formatOutput = $this->getFormatOutput();
+
+ // ...and urlset (root) element
+ $urlSet = $dom->createElementNS(self::SITEMAP_NS, 'urlset');
+ $dom->appendChild($urlSet);
+
+ // create iterator
+ $iterator = new RecursiveIteratorIterator($container, RecursiveIteratorIterator::SELF_FIRST);
+
+ $maxDepth = $this->getMaxDepth();
+ if (is_int($maxDepth)) {
+ $iterator->setMaxDepth($maxDepth);
+ }
+ $minDepth = $this->getMinDepth();
+ if (! is_int($minDepth) || $minDepth < 0) {
+ $minDepth = 0;
+ }
+
+ // iterate container
+ foreach ($iterator as $page) {
+ if ($iterator->getDepth() < $minDepth || ! $this->accept($page)) {
+ // page should not be included
+ continue;
+ }
+
+ // get absolute url from page
+ if (! $url = $this->url($page)) {
+ // skip page if it has no url (rare case)
+ // or already is in the sitemap
+ continue;
+ }
+
+ // create url node for this page
+ $urlNode = $dom->createElementNS(self::SITEMAP_NS, 'url');
+ $urlSet->appendChild($urlNode);
+
+ if ($this->getUseSitemapValidators()
+ && ! $locValidator->isValid($url)
+ ) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Encountered an invalid URL for Sitemap XML: "%s"',
+ $url
+ ));
+ }
+
+ // put url in 'loc' element
+ $urlNode->appendChild($dom->createElementNS(self::SITEMAP_NS, 'loc', $url));
+
+ // add 'lastmod' element if a valid lastmod is set in page
+ if (isset($page->lastmod)) {
+ $lastmod = strtotime((string) $page->lastmod);
+
+ // prevent 1970-01-01...
+ if ($lastmod !== false) {
+ $lastmod = date('c', $lastmod);
+ }
+
+ if (! $this->getUseSitemapValidators()
+ || $lastmodValidator->isValid($lastmod)
+ ) {
+ // Cast $lastmod to string in case no validation was used
+ $urlNode->appendChild(
+ $dom->createElementNS(self::SITEMAP_NS, 'lastmod', (string) $lastmod)
+ );
+ }
+ }
+
+ // add 'changefreq' element if a valid changefreq is set in page
+ if (isset($page->changefreq)) {
+ $changefreq = $page->changefreq;
+ if (! $this->getUseSitemapValidators() ||
+ $changefreqValidator->isValid($changefreq)) {
+ $urlNode->appendChild(
+ $dom->createElementNS(self::SITEMAP_NS, 'changefreq', $changefreq)
+ );
+ }
+ }
+
+ // add 'priority' element if a valid priority is set in page
+ if (isset($page->priority)) {
+ $priority = $page->priority;
+ if (! $this->getUseSitemapValidators() ||
+ $priorityValidator->isValid($priority)) {
+ $urlNode->appendChild(
+ $dom->createElementNS(self::SITEMAP_NS, 'priority', $priority)
+ );
+ }
+ }
+ }
+
+ // validate using schema if specified
+ if ($this->getUseSchemaValidation()) {
+ ErrorHandler::start();
+ $test = $dom->schemaValidate(self::SITEMAP_XSD);
+ $error = ErrorHandler::stop();
+ if (! $test) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Sitemap is invalid according to XML Schema at "%s"',
+ self::SITEMAP_XSD
+ ), 0, $error);
+ }
+ }
+
+ return $dom;
+ }
+
+ /**
+ * Returns an escaped absolute URL for the given page
+ *
+ * @param AbstractPage $page
+ * @return string
+ */
+ public function url(AbstractPage $page)
+ {
+ $href = $page->getHref();
+
+ if (! isset($href{0})) {
+ // no href
+ return '';
+ } elseif ($href{0} == '/') {
+ // href is relative to root; use serverUrl helper
+ $url = $this->getServerUrl() . $href;
+ } elseif (preg_match('/^[a-z]+:/im', (string) $href)) {
+ // scheme is given in href; assume absolute URL already
+ $url = (string) $href;
+ } else {
+ // href is relative to current document; use url helpers
+ $basePathHelper = $this->getView()->plugin('basepath');
+ $curDoc = $basePathHelper();
+ $curDoc = ('/' == $curDoc) ? '' : trim($curDoc, '/');
+ $url = rtrim($this->getServerUrl(), '/') . '/'
+ . $curDoc
+ . (empty($curDoc) ? '' : '/') . $href;
+ }
+
+ if (! in_array($url, $this->urls)) {
+ $this->urls[] = $url;
+ return $this->xmlEscape($url);
+ }
+
+ return;
+ }
+
+ /**
+ * Escapes string for XML usage
+ *
+ * @param string $string
+ * @return string
+ */
+ protected function xmlEscape($string)
+ {
+ $escaper = $this->view->plugin('escapeHtml');
+ return $escaper($string);
+ }
+
+ /**
+ * Sets whether XML output should be formatted
+ *
+ * @param bool $formatOutput
+ * @return Sitemap
+ */
+ public function setFormatOutput($formatOutput = true)
+ {
+ $this->formatOutput = (bool) $formatOutput;
+ return $this;
+ }
+
+ /**
+ * Returns whether XML output should be formatted
+ *
+ * @return bool
+ */
+ public function getFormatOutput()
+ {
+ return $this->formatOutput;
+ }
+
+ /**
+ * Sets server url (scheme and host-related stuff without request URI)
+ *
+ * E.g. http://www.example.com
+ *
+ * @param string $serverUrl
+ * @return Sitemap
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setServerUrl($serverUrl)
+ {
+ $uri = Uri\UriFactory::factory($serverUrl);
+ $uri->setFragment('');
+ $uri->setPath('');
+ $uri->setQuery('');
+
+ if ($uri->isValid()) {
+ $this->serverUrl = $uri->toString();
+ } else {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid server URL: "%s"',
+ $serverUrl
+ ));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns server URL
+ *
+ * @return string
+ */
+ public function getServerUrl()
+ {
+ if (! isset($this->serverUrl)) {
+ $serverUrlHelper = $this->getView()->plugin('serverUrl');
+ $this->serverUrl = $serverUrlHelper();
+ }
+
+ return $this->serverUrl;
+ }
+
+ /**
+ * Sets whether sitemap should be validated using Zend\Validate\Sitemap_*
+ *
+ * @param bool $useSitemapValidators
+ * @return Sitemap
+ */
+ public function setUseSitemapValidators($useSitemapValidators)
+ {
+ $this->useSitemapValidators = (bool) $useSitemapValidators;
+ return $this;
+ }
+
+ /**
+ * Returns whether sitemap should be validated using Zend\Validate\Sitemap_*
+ *
+ * @return bool
+ */
+ public function getUseSitemapValidators()
+ {
+ return $this->useSitemapValidators;
+ }
+
+ /**
+ * Sets whether sitemap should be schema validated when generated
+ *
+ * @param bool $schemaValidation
+ * @return Sitemap
+ */
+ public function setUseSchemaValidation($schemaValidation)
+ {
+ $this->useSchemaValidation = (bool) $schemaValidation;
+ return $this;
+ }
+
+ /**
+ * Returns true if sitemap should be schema validated when generated
+ *
+ * @return bool
+ */
+ public function getUseSchemaValidation()
+ {
+ return $this->useSchemaValidation;
+ }
+
+ /**
+ * Sets whether the XML declaration should be used in output
+ *
+ * @param bool $useXmlDecl
+ * @return Sitemap
+ */
+ public function setUseXmlDeclaration($useXmlDecl)
+ {
+ $this->useXmlDeclaration = (bool) $useXmlDecl;
+ return $this;
+ }
+
+ /**
+ * Returns whether the XML declaration should be used in output
+ *
+ * @return bool
+ */
+ public function getUseXmlDeclaration()
+ {
+ return $this->useXmlDeclaration;
+ }
+}
diff --git a/module/Zend/View/src/Helper/PaginationControl.php b/module/Zend/View/src/Helper/PaginationControl.php
new file mode 100644
index 00000000..65403983
--- /dev/null
+++ b/module/Zend/View/src/Helper/PaginationControl.php
@@ -0,0 +1,138 @@
+paginator is set and,
+ * if so, uses that. Also, if no scrolling style or partial are specified,
+ * the defaults will be used (if set).
+ *
+ * @param Paginator\Paginator $paginator (Optional)
+ * @param string $scrollingStyle (Optional) Scrolling style
+ * @param string $partial (Optional) View partial
+ * @param array|string $params (Optional) params to pass to the partial
+ * @throws Exception\RuntimeException if no paginator or no view partial provided
+ * @throws Exception\InvalidArgumentException if partial is invalid array
+ * @return string
+ */
+ public function __invoke(
+ Paginator\Paginator $paginator = null,
+ $scrollingStyle = null,
+ $partial = null,
+ $params = null
+ ) {
+ if ($paginator === null) {
+ if (isset($this->view->paginator)
+ && $this->view->paginator !== null
+ && $this->view->paginator instanceof Paginator\Paginator
+ ) {
+ $paginator = $this->view->paginator;
+ } else {
+ throw new Exception\RuntimeException('No paginator instance provided or incorrect type');
+ }
+ }
+
+ if ($partial === null) {
+ if (static::$defaultViewPartial === null) {
+ throw new Exception\RuntimeException('No view partial provided and no default set');
+ }
+
+ $partial = static::$defaultViewPartial;
+ }
+
+ if ($scrollingStyle === null) {
+ $scrollingStyle = static::$defaultScrollingStyle;
+ }
+
+ $pages = get_object_vars($paginator->getPages($scrollingStyle));
+
+ if ($params !== null) {
+ $pages = array_merge($pages, (array) $params);
+ }
+
+ if (is_array($partial)) {
+ if (count($partial) != 2) {
+ throw new Exception\InvalidArgumentException(
+ 'A view partial supplied as an array must contain two values: the filename and its module'
+ );
+ }
+
+ if ($partial[1] !== null) {
+ $partialHelper = $this->view->plugin('partial');
+ return $partialHelper($partial[0], $pages);
+ }
+
+ $partial = $partial[0];
+ }
+
+ $partialHelper = $this->view->plugin('partial');
+ return $partialHelper($partial, $pages);
+ }
+
+ /**
+ * Sets the default Scrolling Style
+ *
+ * @param string $style string 'all' | 'elastic' | 'sliding' | 'jumping'
+ */
+ public static function setDefaultScrollingStyle($style)
+ {
+ static::$defaultScrollingStyle = $style;
+ }
+
+ /**
+ * Gets the default scrolling style
+ *
+ * @return string
+ */
+ public static function getDefaultScrollingStyle()
+ {
+ return static::$defaultScrollingStyle;
+ }
+
+ /**
+ * Sets the default view partial.
+ *
+ * @param string|array $partial View partial
+ */
+ public static function setDefaultViewPartial($partial)
+ {
+ static::$defaultViewPartial = $partial;
+ }
+
+ /**
+ * Gets the default view partial
+ *
+ * @return string|array
+ */
+ public static function getDefaultViewPartial()
+ {
+ return static::$defaultViewPartial;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Partial.php b/module/Zend/View/src/Helper/Partial.php
new file mode 100644
index 00000000..ae1ae02b
--- /dev/null
+++ b/module/Zend/View/src/Helper/Partial.php
@@ -0,0 +1,93 @@
+getView()->render($name);
+ }
+
+ if (is_scalar($values)) {
+ $values = [];
+ } elseif ($values instanceof ModelInterface) {
+ $values = $values->getVariables();
+ } elseif (is_object($values)) {
+ if (null !== ($objectKey = $this->getObjectKey())) {
+ $values = [$objectKey => $values];
+ } elseif (method_exists($values, 'toArray')) {
+ $values = $values->toArray();
+ }
+ }
+
+ return $this->getView()->render($name, $values);
+ }
+
+ /**
+ * Set object key
+ *
+ * @param string|null $key
+ *
+ * @return self
+ */
+ public function setObjectKey($key)
+ {
+ if (null === $key) {
+ $this->objectKey = null;
+ return $this;
+ }
+
+ $this->objectKey = (string) $key;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve object key
+ *
+ * The objectKey is the variable to which an object in the iterator will be
+ * assigned.
+ *
+ * @return null|string
+ */
+ public function getObjectKey()
+ {
+ return $this->objectKey;
+ }
+}
diff --git a/module/Zend/View/src/Helper/PartialLoop.php b/module/Zend/View/src/Helper/PartialLoop.php
new file mode 100644
index 00000000..edec3270
--- /dev/null
+++ b/module/Zend/View/src/Helper/PartialLoop.php
@@ -0,0 +1,176 @@
+ null,
+ ];
+
+ /**
+ * Renders a template fragment within a variable scope distinct from the
+ * calling View object.
+ *
+ * If no arguments are provided, returns object instance.
+ *
+ * @param string $name Name of view script
+ * @param array $values Variables to populate in the view
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public function __invoke($name = null, $values = null)
+ {
+ if (0 == func_num_args()) {
+ return $this;
+ }
+ return $this->loop($name, $values);
+ }
+
+ /**
+ * Renders a template fragment within a variable scope distinct from the
+ * calling View object.
+ *
+ * @param string $name Name of view script
+ * @param array $values Variables to populate in the view
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public function loop($name = null, $values = null)
+ {
+ // reset the counter if it's called again
+ $this->partialCounter = 0;
+ $content = '';
+
+ foreach ($this->extractViewVariables($values) as $item) {
+ $this->nestObjectKey();
+
+ $this->partialCounter++;
+ $content .= parent::__invoke($name, $item);
+
+ $this->unNestObjectKey();
+ }
+
+ return $content;
+ }
+
+ /**
+ * Get the partial counter
+ *
+ * @return int
+ */
+ public function getPartialCounter()
+ {
+ return $this->partialCounter;
+ }
+
+ /**
+ * Set object key in this loop and any child loop
+ *
+ * {@inheritDoc}
+ *
+ * @param string|null $key
+ *
+ * @return self
+ */
+ public function setObjectKey($key)
+ {
+ if (null === $key) {
+ unset($this->objectKeyStack[$this->nestingLevel]);
+ } else {
+ $this->objectKeyStack[$this->nestingLevel] = (string) $key;
+ }
+
+ return parent::setObjectKey($key);
+ }
+
+ /**
+ * Increment nestedLevel and default objectKey to parent's value
+ *
+ * @return self
+ */
+ private function nestObjectKey()
+ {
+ $this->nestingLevel += 1;
+
+ $this->setObjectKey($this->getObjectKey());
+
+ return $this;
+ }
+
+ /**
+ * Decrement nestedLevel and restore objectKey to parent's value
+ *
+ * @return self
+ */
+ private function unNestObjectKey()
+ {
+ $this->setObjectKey(null);
+
+ $this->nestingLevel -= 1;
+ if (isset($this->objectKeyStack[$this->nestingLevel])) {
+ $this->objectKey = $this->objectKeyStack[$this->nestingLevel];
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param mixed $values
+ *
+ * @return array Variables to populate in the view
+ */
+ private function extractViewVariables($values)
+ {
+ if ($values instanceof Traversable) {
+ return ArrayUtils::iteratorToArray($values, false);
+ }
+
+ if (is_array($values)) {
+ return $values;
+ }
+
+ if (is_object($values) && method_exists($values, 'toArray')) {
+ return $values->toArray();
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'PartialLoop helper requires iterable data, %s given',
+ is_object($values) ? get_class($values) : gettype($values)
+ ));
+ }
+}
diff --git a/module/Zend/View/src/Helper/Placeholder.php b/module/Zend/View/src/Helper/Placeholder.php
new file mode 100644
index 00000000..8d42e07f
--- /dev/null
+++ b/module/Zend/View/src/Helper/Placeholder.php
@@ -0,0 +1,122 @@
+getContainer($name);
+ }
+
+ /**
+ * createContainer
+ *
+ * @param string $key
+ * @param array $value
+ * @return Container\AbstractContainer
+ */
+ public function createContainer($key, array $value = [])
+ {
+ $key = (string) $key;
+
+ $this->items[$key] = new $this->containerClass($value);
+ return $this->items[$key];
+ }
+
+ /**
+ * Retrieve a placeholder container
+ *
+ * @param string $key
+ * @return Container\AbstractContainer
+ */
+ public function getContainer($key)
+ {
+ $key = (string) $key;
+ if (isset($this->items[$key])) {
+ return $this->items[$key];
+ }
+
+ $container = $this->createContainer($key);
+
+ return $container;
+ }
+
+ /**
+ * Does a particular container exist?
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function containerExists($key)
+ {
+ $key = (string) $key;
+ $return = array_key_exists($key, $this->items);
+ return $return;
+ }
+
+ /**
+ * Delete a specific container by name
+ *
+ * @param string $key
+ * @return void
+ */
+ public function deleteContainer($key)
+ {
+ $key = (string) $key;
+ unset($this->items[$key]);
+ }
+
+ /**
+ * Remove all containers
+ *
+ * @return void
+ */
+ public function clearContainers()
+ {
+ $this->items = [];
+ }
+}
diff --git a/module/Zend/View/src/Helper/Placeholder/Container.php b/module/Zend/View/src/Helper/Placeholder/Container.php
new file mode 100644
index 00000000..07619819
--- /dev/null
+++ b/module/Zend/View/src/Helper/Placeholder/Container.php
@@ -0,0 +1,17 @@
+toString();
+ }
+
+ /**
+ * Render the placeholder
+ *
+ * @param null|int|string $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ // If we don't have items - do not show prefix and postfix
+ if (! count($this)) {
+ return '';
+ }
+
+ $indent = ($indent === null)
+ ? $this->getIndent()
+ : $this->getWhitespace($indent);
+
+ $items = $this->getArrayCopy();
+ $return = $indent
+ . $this->getPrefix()
+ . implode($this->getSeparator(), $items)
+ . $this->getPostfix();
+ $return = preg_replace("/(\r\n?|\n)/", '$1' . $indent, $return);
+
+ return $return;
+ }
+
+ /**
+ * Start capturing content to push into placeholder
+ *
+ * @param string $type How to capture content into placeholder; append, prepend, or set
+ * @param mixed $key Key to which to capture content
+ * @throws Exception\RuntimeException if nested captures detected
+ * @return void
+ */
+ public function captureStart($type = AbstractContainer::APPEND, $key = null)
+ {
+ if ($this->captureLock) {
+ throw new Exception\RuntimeException(
+ 'Cannot nest placeholder captures for the same placeholder'
+ );
+ }
+
+ $this->captureLock = true;
+ $this->captureType = $type;
+ if ((null !== $key) && is_scalar($key)) {
+ $this->captureKey = (string) $key;
+ }
+ ob_start();
+ }
+
+ /**
+ * End content capture
+ *
+ * @return void
+ */
+ public function captureEnd()
+ {
+ $data = ob_get_clean();
+ $key = null;
+ $this->captureLock = false;
+ if (null !== $this->captureKey) {
+ $key = $this->captureKey;
+ }
+ switch ($this->captureType) {
+ case self::SET:
+ if (null !== $key) {
+ $this[$key] = $data;
+ } else {
+ $this->exchangeArray([$data]);
+ }
+ break;
+ case self::PREPEND:
+ if (null !== $key) {
+ $array = [$key => $data];
+ $values = $this->getArrayCopy();
+ $final = $array + $values;
+ $this->exchangeArray($final);
+ } else {
+ $this->prepend($data);
+ }
+ break;
+ case self::APPEND:
+ default:
+ if (null !== $key) {
+ if (empty($this[$key])) {
+ $this[$key] = $data;
+ } else {
+ $this[$key] .= $data;
+ }
+ } else {
+ $this[$this->nextIndex()] = $data;
+ }
+ break;
+ }
+ }
+
+ /**
+ * Get keys
+ *
+ * @return array
+ */
+ public function getKeys()
+ {
+ $array = $this->getArrayCopy();
+
+ return array_keys($array);
+ }
+
+ /**
+ * Retrieve container value
+ *
+ * If single element registered, returns that element; otherwise,
+ * serializes to array.
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ if (1 == count($this)) {
+ $keys = $this->getKeys();
+ $key = array_shift($keys);
+ return $this[$key];
+ }
+
+ return $this->getArrayCopy();
+ }
+
+ /**
+ * Retrieve whitespace representation of $indent
+ *
+ * @param int|string $indent
+ * @return string
+ */
+ public function getWhitespace($indent)
+ {
+ if (is_int($indent)) {
+ $indent = str_repeat(' ', $indent);
+ }
+
+ return (string) $indent;
+ }
+
+ /**
+ * Set a single value
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function set($value)
+ {
+ $this->exchangeArray([$value]);
+
+ return $this;
+ }
+
+ /**
+ * Prepend a value to the top of the container
+ *
+ * @param mixed $value
+ * @return self
+ */
+ public function prepend($value)
+ {
+ $values = $this->getArrayCopy();
+ array_unshift($values, $value);
+ $this->exchangeArray($values);
+
+ return $this;
+ }
+
+ /**
+ * Append a value to the end of the container
+ *
+ * @param mixed $value
+ * @return self
+ */
+ public function append($value)
+ {
+ parent::append($value);
+ return $this;
+ }
+
+ /**
+ * Next Index as defined by the PHP manual
+ *
+ * @return int
+ */
+ public function nextIndex()
+ {
+ $keys = $this->getKeys();
+ if (empty($keys)) {
+ return 0;
+ }
+
+ return max($keys) + 1;
+ }
+
+ /**
+ * Set the indentation string for __toString() serialization,
+ * optionally, if a number is passed, it will be the number of spaces
+ *
+ * @param string|int $indent
+ * @return self
+ */
+ public function setIndent($indent)
+ {
+ $this->indent = $this->getWhitespace($indent);
+ return $this;
+ }
+
+ /**
+ * Retrieve indentation
+ *
+ * @return string
+ */
+ public function getIndent()
+ {
+ return $this->indent;
+ }
+
+ /**
+ * Set postfix for __toString() serialization
+ *
+ * @param string $postfix
+ * @return self
+ */
+ public function setPostfix($postfix)
+ {
+ $this->postfix = (string) $postfix;
+ return $this;
+ }
+
+ /**
+ * Retrieve postfix
+ *
+ * @return string
+ */
+ public function getPostfix()
+ {
+ return $this->postfix;
+ }
+
+ /**
+ * Set prefix for __toString() serialization
+ *
+ * @param string $prefix
+ * @return self
+ */
+ public function setPrefix($prefix)
+ {
+ $this->prefix = (string) $prefix;
+ return $this;
+ }
+
+ /**
+ * Retrieve prefix
+ *
+ * @return string
+ */
+ public function getPrefix()
+ {
+ return $this->prefix;
+ }
+
+ /**
+ * Set separator for __toString() serialization
+ *
+ * Used to implode elements in container
+ *
+ * @param string $separator
+ * @return self
+ */
+ public function setSeparator($separator)
+ {
+ $this->separator = (string) $separator;
+ return $this;
+ }
+
+ /**
+ * Retrieve separator
+ *
+ * @return string
+ */
+ public function getSeparator()
+ {
+ return $this->separator;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Placeholder/Container/AbstractStandalone.php b/module/Zend/View/src/Helper/Placeholder/Container/AbstractStandalone.php
new file mode 100644
index 00000000..c40801e6
--- /dev/null
+++ b/module/Zend/View/src/Helper/Placeholder/Container/AbstractStandalone.php
@@ -0,0 +1,380 @@
+setContainer($this->getContainer());
+ }
+
+ /**
+ * Overload
+ *
+ * Proxy to container methods
+ *
+ * @param string $method
+ * @param array $args
+ * @throws Exception\BadMethodCallException
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ $container = $this->getContainer();
+ if (method_exists($container, $method)) {
+ $return = call_user_func_array([$container, $method], $args);
+ if ($return === $container) {
+ // If the container is returned, we really want the current object
+ return $this;
+ }
+ return $return;
+ }
+
+ throw new Exception\BadMethodCallException('Method "' . $method . '" does not exist');
+ }
+
+ /**
+ * Overloading: set property value
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $container = $this->getContainer();
+ $container[$key] = $value;
+ }
+
+ /**
+ * Overloading: retrieve property
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ $container = $this->getContainer();
+ if (isset($container[$key])) {
+ return $container[$key];
+ }
+
+ return;
+ }
+
+ /**
+ * Overloading: check if property is set
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ $container = $this->getContainer();
+ return isset($container[$key]);
+ }
+
+ /**
+ * Overloading: unset property
+ *
+ * @param string $key
+ * @return void
+ */
+ public function __unset($key)
+ {
+ $container = $this->getContainer();
+ if (isset($container[$key])) {
+ unset($container[$key]);
+ }
+ }
+
+ /**
+ * Cast to string representation
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * String representation
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return $this->getContainer()->toString();
+ }
+
+ /**
+ * Escape a string
+ *
+ * @param string $string
+ * @return string
+ */
+ protected function escape($string)
+ {
+ return $this->getEscaper()->escapeHtml((string) $string);
+ }
+
+ /**
+ * Escape an attribute value
+ *
+ * @param string $string
+ * @return string
+ */
+ protected function escapeAttribute($string)
+ {
+ return $this->getEscaper()->escapeHtmlAttr((string) $string);
+ }
+
+ /**
+ * Set whether or not auto escaping should be used
+ *
+ * @param bool $autoEscape whether or not to auto escape output
+ * @return AbstractStandalone
+ */
+ public function setAutoEscape($autoEscape = true)
+ {
+ $this->autoEscape = (bool) $autoEscape;
+ return $this;
+ }
+
+ /**
+ * Return whether autoEscaping is enabled or disabled
+ *
+ * return bool
+ */
+ public function getAutoEscape()
+ {
+ return $this->autoEscape;
+ }
+
+ /**
+ * Set container on which to operate
+ *
+ * @param AbstractContainer $container
+ * @return AbstractStandalone
+ */
+ public function setContainer(AbstractContainer $container)
+ {
+ $this->container = $container;
+ return $this;
+ }
+
+ /**
+ * Retrieve placeholder container
+ *
+ * @return AbstractContainer
+ */
+ public function getContainer()
+ {
+ if (! $this->container instanceof AbstractContainer) {
+ $this->container = new $this->containerClass();
+ }
+ return $this->container;
+ }
+
+ /**
+ * Delete a container
+ *
+ * @return bool
+ */
+ public function deleteContainer()
+ {
+ if (null != $this->container) {
+ $this->container = null;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the container class to use
+ *
+ * @param string $name
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\DomainException
+ * @return \Zend\View\Helper\Placeholder\Container\AbstractStandalone
+ */
+ public function setContainerClass($name)
+ {
+ if (! class_exists($name)) {
+ throw new Exception\DomainException(
+ sprintf(
+ '%s expects a valid container class name; received "%s", which did not resolve',
+ __METHOD__,
+ $name
+ )
+ );
+ }
+
+ if (! in_array('Zend\View\Helper\Placeholder\Container\AbstractContainer', class_parents($name))) {
+ throw new Exception\InvalidArgumentException('Invalid Container class specified');
+ }
+
+ $this->containerClass = $name;
+ return $this;
+ }
+
+ /**
+ * Retrieve the container class
+ *
+ * @return string
+ */
+ public function getContainerClass()
+ {
+ return $this->containerClass;
+ }
+
+ /**
+ * Set Escaper instance
+ *
+ * @param Escaper $escaper
+ * @return AbstractStandalone
+ */
+ public function setEscaper(Escaper $escaper)
+ {
+ $encoding = $escaper->getEncoding();
+ $this->escapers[$encoding] = $escaper;
+
+ return $this;
+ }
+
+ /**
+ * Get Escaper instance
+ *
+ * Lazy-loads one if none available
+ *
+ * @param string|null $enc Encoding to use
+ * @return mixed
+ */
+ public function getEscaper($enc = 'UTF-8')
+ {
+ $enc = strtolower($enc);
+ if (! isset($this->escapers[$enc])) {
+ $this->setEscaper(new Escaper($enc));
+ }
+
+ return $this->escapers[$enc];
+ }
+
+ /**
+ * Countable
+ *
+ * @return int
+ */
+ public function count()
+ {
+ $container = $this->getContainer();
+ return count($container);
+ }
+
+ /**
+ * ArrayAccess: offsetExists
+ *
+ * @param string|int $offset
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return $this->getContainer()->offsetExists($offset);
+ }
+
+ /**
+ * ArrayAccess: offsetGet
+ *
+ * @param string|int $offset
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ return $this->getContainer()->offsetGet($offset);
+ }
+
+ /**
+ * ArrayAccess: offsetSet
+ *
+ * @param string|int $offset
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($offset, $value)
+ {
+ return $this->getContainer()->offsetSet($offset, $value);
+ }
+
+ /**
+ * ArrayAccess: offsetUnset
+ *
+ * @param string|int $offset
+ * @return void
+ */
+ public function offsetUnset($offset)
+ {
+ return $this->getContainer()->offsetUnset($offset);
+ }
+
+ /**
+ * IteratorAggregate: get Iterator
+ *
+ * @return \Iterator
+ */
+ public function getIterator()
+ {
+ return $this->getContainer()->getIterator();
+ }
+}
diff --git a/module/Zend/View/src/Helper/Placeholder/Registry.php b/module/Zend/View/src/Helper/Placeholder/Registry.php
new file mode 100644
index 00000000..d1d3f6ef
--- /dev/null
+++ b/module/Zend/View/src/Helper/Placeholder/Registry.php
@@ -0,0 +1,185 @@
+items[$key] = $container;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve a placeholder container
+ *
+ * @param string $key
+ * @return Container\AbstractContainer
+ */
+ public function getContainer($key)
+ {
+ $key = (string) $key;
+ if (isset($this->items[$key])) {
+ return $this->items[$key];
+ }
+
+ $container = $this->createContainer($key);
+
+ return $container;
+ }
+
+ /**
+ * Does a particular container exist?
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function containerExists($key)
+ {
+ $key = (string) $key;
+
+ return array_key_exists($key, $this->items);
+ }
+
+ /**
+ * createContainer
+ *
+ * @param string $key
+ * @param array $value
+ * @return Container\AbstractContainer
+ */
+ public function createContainer($key, array $value = [])
+ {
+ $key = (string) $key;
+
+ $this->items[$key] = new $this->containerClass($value);
+
+ return $this->items[$key];
+ }
+
+ /**
+ * Delete a container
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function deleteContainer($key)
+ {
+ $key = (string) $key;
+ if (isset($this->items[$key])) {
+ unset($this->items[$key]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the container class to use
+ *
+ * @param string $name
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\DomainException
+ * @return Registry
+ */
+ public function setContainerClass($name)
+ {
+ if (! class_exists($name)) {
+ throw new Exception\DomainException(
+ sprintf(
+ '%s expects a valid registry class name; received "%s", which did not resolve',
+ __METHOD__,
+ $name
+ )
+ );
+ }
+
+ if (! in_array('Zend\View\Helper\Placeholder\Container\AbstractContainer', class_parents($name))) {
+ throw new Exception\InvalidArgumentException('Invalid Container class specified');
+ }
+
+ $this->containerClass = $name;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the container class
+ *
+ * @return string
+ */
+ public function getContainerClass()
+ {
+ return $this->containerClass;
+ }
+}
diff --git a/module/Zend/View/src/Helper/RenderChildModel.php b/module/Zend/View/src/Helper/RenderChildModel.php
new file mode 100644
index 00000000..bfcde6fc
--- /dev/null
+++ b/module/Zend/View/src/Helper/RenderChildModel.php
@@ -0,0 +1,133 @@
+render($child);
+ }
+
+ /**
+ * Render a model
+ *
+ * If a matching child model is found, it is rendered. If not, an empty
+ * string is returned.
+ *
+ * @param string $child
+ * @return string
+ */
+ public function render($child)
+ {
+ $model = $this->findChild($child);
+ if (! $model) {
+ return '';
+ }
+
+ $current = $this->current;
+ $view = $this->getView();
+ $return = $view->render($model);
+ $helper = $this->getViewModelHelper();
+ $helper->setCurrent($current);
+
+ return $return;
+ }
+
+ /**
+ * Find the named child model
+ *
+ * Iterates through the current view model, looking for a child model that
+ * has a captureTo value matching the requested $child. If found, that child
+ * model is returned; otherwise, a boolean false is returned.
+ *
+ * @param string $child
+ * @return false|Model
+ */
+ protected function findChild($child)
+ {
+ $this->current = $model = $this->getCurrent();
+ foreach ($model->getChildren() as $childModel) {
+ if ($childModel->captureTo() == $child) {
+ return $childModel;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the current view model
+ *
+ * @throws Exception\RuntimeException
+ * @return null|Model
+ */
+ protected function getCurrent()
+ {
+ $helper = $this->getViewModelHelper();
+ if (! $helper->hasCurrent()) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: no view model currently registered in renderer; cannot query for children',
+ __METHOD__
+ ));
+ }
+
+ return $helper->getCurrent();
+ }
+
+ /**
+ * Retrieve the view model helper
+ *
+ * @return ViewModel
+ */
+ protected function getViewModelHelper()
+ {
+ if ($this->viewModelHelper) {
+ return $this->viewModelHelper;
+ }
+
+ if (method_exists($this->getView(), 'plugin')) {
+ $this->viewModelHelper = $this->view->plugin('view_model');
+ }
+
+ return $this->viewModelHelper;
+ }
+}
diff --git a/module/Zend/View/src/Helper/RenderToPlaceholder.php b/module/Zend/View/src/Helper/RenderToPlaceholder.php
new file mode 100644
index 00000000..cab6c33f
--- /dev/null
+++ b/module/Zend/View/src/Helper/RenderToPlaceholder.php
@@ -0,0 +1,35 @@
+view->plugin('placeholder');
+ $placeholderHelper($placeholder)->captureStart();
+ echo $this->view->render($script);
+ $placeholderHelper($placeholder)->captureEnd();
+ }
+}
diff --git a/module/Zend/View/src/Helper/ServerUrl.php b/module/Zend/View/src/Helper/ServerUrl.php
new file mode 100644
index 00000000..56cb0980
--- /dev/null
+++ b/module/Zend/View/src/Helper/ServerUrl.php
@@ -0,0 +1,351 @@
+getScheme() . '://' . $this->getHost() . $path;
+ }
+
+ /**
+ * Detect the host based on headers
+ *
+ * @return void
+ */
+ protected function detectHost()
+ {
+ if ($this->setHostFromProxy()) {
+ return;
+ }
+
+ if (isset($_SERVER['HTTP_HOST']) && ! empty($_SERVER['HTTP_HOST'])) {
+ // Detect if the port is set in SERVER_PORT and included in HTTP_HOST
+ if (isset($_SERVER['SERVER_PORT'])
+ && preg_match('/^(?P.*?):(?P\d+)$/', $_SERVER['HTTP_HOST'], $matches)
+ ) {
+ // If they are the same, set the host to just the hostname
+ // portion of the Host header.
+ if ((int) $matches['port'] === (int) $_SERVER['SERVER_PORT']) {
+ $this->setHost($matches['host']);
+ return;
+ }
+
+ // At this point, we have a SERVER_PORT that differs from the
+ // Host header, indicating we likely have a port-forwarding
+ // situation. As such, we'll set the host and port from the
+ // matched values.
+ $this->setPort((int) $matches['port']);
+ $this->setHost($matches['host']);
+ return;
+ }
+
+ $this->setHost($_SERVER['HTTP_HOST']);
+
+ return;
+ }
+
+ if (! isset($_SERVER['SERVER_NAME']) || ! isset($_SERVER['SERVER_PORT'])) {
+ return;
+ }
+
+ $name = $_SERVER['SERVER_NAME'];
+ $this->setHost($name);
+ }
+
+ /**
+ * Detect the port
+ *
+ * @return null
+ */
+ protected function detectPort()
+ {
+ if ($this->setPortFromProxy()) {
+ return;
+ }
+
+ if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT']) {
+ if ($this->isReversedProxy()) {
+ $this->setPort(443);
+ return;
+ }
+ $this->setPort($_SERVER['SERVER_PORT']);
+ return;
+ }
+ }
+
+ /**
+ * Detect the scheme
+ *
+ * @return null
+ */
+ protected function detectScheme()
+ {
+ if ($this->setSchemeFromProxy()) {
+ return;
+ }
+
+ switch (true) {
+ case (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)):
+ case (isset($_SERVER['HTTP_SCHEME']) && ($_SERVER['HTTP_SCHEME'] == 'https')):
+ case (443 === $this->getPort()):
+ case $this->isReversedProxy():
+ $scheme = 'https';
+ break;
+ default:
+ $scheme = 'http';
+ break;
+ }
+
+ $this->setScheme($scheme);
+ }
+
+ protected function isReversedProxy()
+ {
+ return isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https';
+ }
+
+ /**
+ * Detect if a proxy is in use, and, if so, set the host based on it
+ *
+ * @return bool
+ */
+ protected function setHostFromProxy()
+ {
+ if (! $this->useProxy) {
+ return false;
+ }
+
+ if (! isset($_SERVER['HTTP_X_FORWARDED_HOST']) || empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
+ return false;
+ }
+
+ $host = $_SERVER['HTTP_X_FORWARDED_HOST'];
+ if (strpos($host, ',') !== false) {
+ $hosts = explode(',', $host);
+ $host = trim(array_pop($hosts));
+ }
+ if (empty($host)) {
+ return false;
+ }
+ $this->setHost($host);
+
+ return true;
+ }
+
+ /**
+ * Set port based on detected proxy headers
+ *
+ * @return bool
+ */
+ protected function setPortFromProxy()
+ {
+ if (! $this->useProxy) {
+ return false;
+ }
+
+ if (! isset($_SERVER['HTTP_X_FORWARDED_PORT']) || empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
+ return false;
+ }
+
+ $port = $_SERVER['HTTP_X_FORWARDED_PORT'];
+ $this->setPort($port);
+
+ return true;
+ }
+
+ /**
+ * Set the current scheme based on detected proxy headers
+ *
+ * @return bool
+ */
+ protected function setSchemeFromProxy()
+ {
+ if (! $this->useProxy) {
+ return false;
+ }
+
+ if (isset($_SERVER['SSL_HTTPS'])) {
+ $sslHttps = strtolower($_SERVER['SSL_HTTPS']);
+ if (in_array($sslHttps, ['on', 1])) {
+ $this->setScheme('https');
+ return true;
+ }
+ }
+
+ if (! isset($_SERVER['HTTP_X_FORWARDED_PROTO']) || empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
+ return false;
+ }
+
+ $scheme = trim(strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']));
+ if (empty($scheme)) {
+ return false;
+ }
+
+ $this->setScheme($scheme);
+
+ return true;
+ }
+
+ /**
+ * Sets host
+ *
+ * @param string $host
+ * @return ServerUrl
+ */
+ public function setHost($host)
+ {
+ $port = $this->getPort();
+ $scheme = $this->getScheme();
+
+ if (($scheme == 'http' && (null === $port || $port == 80))
+ || ($scheme == 'https' && (null === $port || $port == 443))
+ ) {
+ $this->host = $host;
+ return $this;
+ }
+
+ $this->host = $host . ':' . $port;
+
+ return $this;
+ }
+
+ /**
+ * Returns host
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ if (null === $this->host) {
+ $this->detectHost();
+ }
+
+ return $this->host;
+ }
+
+ /**
+ * Set server port
+ *
+ * @param int $port
+ * @return ServerUrl
+ */
+ public function setPort($port)
+ {
+ $this->port = (int) $port;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the server port
+ *
+ * @return int|null
+ */
+ public function getPort()
+ {
+ if (null === $this->port) {
+ $this->detectPort();
+ }
+
+ return $this->port;
+ }
+
+ /**
+ * Sets scheme (typically http or https)
+ *
+ * @param string $scheme
+ * @return ServerUrl
+ */
+ public function setScheme($scheme)
+ {
+ $this->scheme = $scheme;
+
+ return $this;
+ }
+
+ /**
+ * Returns scheme (typically http or https)
+ *
+ * @return string
+ */
+ public function getScheme()
+ {
+ if (null === $this->scheme) {
+ $this->detectScheme();
+ }
+
+ return $this->scheme;
+ }
+
+ /**
+ * Set flag indicating whether or not to query proxy servers
+ *
+ * @param bool $useProxy
+ * @return ServerUrl
+ */
+ public function setUseProxy($useProxy = false)
+ {
+ $this->useProxy = (bool) $useProxy;
+
+ return $this;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Service/AssetFactory.php b/module/Zend/View/src/Helper/Service/AssetFactory.php
new file mode 100644
index 00000000..2f06c50c
--- /dev/null
+++ b/module/Zend/View/src/Helper/Service/AssetFactory.php
@@ -0,0 +1,60 @@
+getServiceLocator();
+ }
+ $helper = new Asset();
+
+ $config = $container->get('config');
+ if (isset($config['view_helper_config']['asset'])) {
+ $configHelper = $config['view_helper_config']['asset'];
+ if (isset($configHelper['resource_map']) && is_array($configHelper['resource_map'])) {
+ $helper->setResourceMap($configHelper['resource_map']);
+ } else {
+ throw new Exception\RuntimeException('Invalid resource map configuration.');
+ }
+ }
+
+ return $helper;
+ }
+
+ /**
+ * Create service
+ *
+ * @param ServiceLocatorInterface $serviceLocator
+ * @param string|null $rName
+ * @param string|null $cName
+ * @return Asset
+ */
+ public function createService(ServiceLocatorInterface $serviceLocator, $rName = null, $cName = null)
+ {
+ return $this($serviceLocator, $cName);
+ }
+}
diff --git a/module/Zend/View/src/Helper/Service/FlashMessengerFactory.php b/module/Zend/View/src/Helper/Service/FlashMessengerFactory.php
new file mode 100644
index 00000000..028d1319
--- /dev/null
+++ b/module/Zend/View/src/Helper/Service/FlashMessengerFactory.php
@@ -0,0 +1,68 @@
+getServiceLocator();
+ }
+ $helper = new FlashMessenger();
+ $controllerPluginManager = $container->get('ControllerPluginManager');
+ $flashMessenger = $controllerPluginManager->get('flashmessenger');
+
+ $helper->setPluginFlashMessenger($flashMessenger);
+
+ $config = $container->get('config');
+ if (isset($config['view_helper_config']['flashmessenger'])) {
+ $configHelper = $config['view_helper_config']['flashmessenger'];
+ if (isset($configHelper['message_open_format'])) {
+ $helper->setMessageOpenFormat($configHelper['message_open_format']);
+ }
+ if (isset($configHelper['message_separator_string'])) {
+ $helper->setMessageSeparatorString($configHelper['message_separator_string']);
+ }
+ if (isset($configHelper['message_close_string'])) {
+ $helper->setMessageCloseString($configHelper['message_close_string']);
+ }
+ }
+
+ return $helper;
+ }
+
+ /**
+ * Create service (v2)
+ *
+ * @param ServiceLocatorInterface $container
+ * @param string $normalizedName
+ * @param string $requestedName
+ * @return FlashMessenger
+ */
+ public function createService(ServiceLocatorInterface $container, $normalizedName = null, $requestedName = null)
+ {
+ return $this($container, $requestedName);
+ }
+}
diff --git a/module/Zend/View/src/Helper/Service/IdentityFactory.php b/module/Zend/View/src/Helper/Service/IdentityFactory.php
new file mode 100644
index 00000000..371d7b76
--- /dev/null
+++ b/module/Zend/View/src/Helper/Service/IdentityFactory.php
@@ -0,0 +1,69 @@
+getServiceLocator();
+ }
+
+ $helper = new Identity();
+
+ if (null !== ($authenticationService = $this->discoverAuthenticationService($container))) {
+ $helper->setAuthenticationService($authenticationService);
+ }
+
+ return $helper;
+ }
+
+ /**
+ * Create service
+ *
+ * @param ServiceLocatorInterface $serviceLocator
+ * @return mixed
+ */
+ public function createService(ServiceLocatorInterface $serviceLocator, $rName = null, $cName = null)
+ {
+ return $this($serviceLocator, $cName);
+ }
+
+ /**
+ * @return null|AuthenticationServiceInterface
+ */
+ private function discoverAuthenticationService(ContainerInterface $container)
+ {
+ if ($container->has(AuthenticationService::class)) {
+ return $container->get(AuthenticationService::class);
+ }
+
+ return $container->has(AuthenticationServiceInterface::class)
+ ? $container->get(AuthenticationServiceInterface::class)
+ : null;
+ }
+}
diff --git a/module/Zend/View/src/Helper/TranslatorAwareTrait.php b/module/Zend/View/src/Helper/TranslatorAwareTrait.php
new file mode 100644
index 00000000..cf450d0c
--- /dev/null
+++ b/module/Zend/View/src/Helper/TranslatorAwareTrait.php
@@ -0,0 +1,127 @@
+translator = $translator;
+ if (null !== $textDomain) {
+ $this->setTranslatorTextDomain($textDomain);
+ }
+ return $this;
+ }
+
+ /**
+ * Returns translator used in helper
+ *
+ * @return Translator|null
+ */
+ public function getTranslator()
+ {
+ if (! $this->isTranslatorEnabled()) {
+ return;
+ }
+
+ return $this->translator;
+ }
+
+ /**
+ * Checks if the helper has a translator
+ *
+ * @return bool
+ */
+ public function hasTranslator()
+ {
+ return (bool) $this->getTranslator();
+ }
+
+ /**
+ * Sets whether translator is enabled and should be used
+ *
+ * @param bool $enabled [optional] whether translator should be used.
+ * Default is true.
+ * @return HeadTitle
+ */
+ public function setTranslatorEnabled($enabled = true)
+ {
+ $this->translatorEnabled = (bool) $enabled;
+ return $this;
+ }
+
+ /**
+ * Returns whether translator is enabled and should be used
+ *
+ * @return bool
+ */
+ public function isTranslatorEnabled()
+ {
+ return $this->translatorEnabled;
+ }
+
+ /**
+ * Set translation text domain
+ *
+ * @param string $textDomain
+ * @return HeadTitle
+ */
+ public function setTranslatorTextDomain($textDomain = 'default')
+ {
+ $this->translatorTextDomain = $textDomain;
+ return $this;
+ }
+
+ /**
+ * Return the translation text domain
+ *
+ * @return string
+ */
+ public function getTranslatorTextDomain()
+ {
+ return $this->translatorTextDomain;
+ }
+}
diff --git a/module/Zend/View/src/Helper/Url.php b/module/Zend/View/src/Helper/Url.php
new file mode 100644
index 00000000..73d9cfdd
--- /dev/null
+++ b/module/Zend/View/src/Helper/Url.php
@@ -0,0 +1,157 @@
+router) {
+ throw new Exception\RuntimeException('No RouteStackInterface instance provided');
+ }
+
+ if (3 == func_num_args() && is_bool($options)) {
+ $reuseMatchedParams = $options;
+ $options = [];
+ }
+
+ if ($name === null) {
+ if ($this->routeMatch === null) {
+ throw new Exception\RuntimeException('No RouteMatch instance provided');
+ }
+
+ $name = $this->routeMatch->getMatchedRouteName();
+
+ if ($name === null) {
+ throw new Exception\RuntimeException('RouteMatch does not contain a matched route name');
+ }
+ }
+
+ if (! is_array($params)) {
+ if (! $params instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(
+ 'Params is expected to be an array or a Traversable object'
+ );
+ }
+ $params = iterator_to_array($params);
+ }
+
+ if ($reuseMatchedParams && $this->routeMatch !== null) {
+ $routeMatchParams = $this->routeMatch->getParams();
+
+ if (isset($routeMatchParams[ModuleRouteListener::ORIGINAL_CONTROLLER])) {
+ $routeMatchParams['controller'] = $routeMatchParams[ModuleRouteListener::ORIGINAL_CONTROLLER];
+ unset($routeMatchParams[ModuleRouteListener::ORIGINAL_CONTROLLER]);
+ }
+
+ if (isset($routeMatchParams[ModuleRouteListener::MODULE_NAMESPACE])) {
+ unset($routeMatchParams[ModuleRouteListener::MODULE_NAMESPACE]);
+ }
+
+ $params = array_merge($routeMatchParams, $params);
+ }
+
+ $options['name'] = $name;
+
+ return $this->router->assemble($params, $options);
+ }
+
+ /**
+ * Set the router to use for assembling.
+ *
+ * @param LegacyRouteStackInterface|RouteStackInterface $router
+ * @return Url
+ * @throws Exception\InvalidArgumentException for invalid router types.
+ */
+ public function setRouter($router)
+ {
+ if (! $router instanceof RouteStackInterface
+ && ! $router instanceof LegacyRouteStackInterface
+ ) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects a %s or %s instance; received %s',
+ __METHOD__,
+ RouteStackInterface::class,
+ LegacyRouteStackInterface::class,
+ (is_object($router) ? get_class($router) : gettype($router))
+ ));
+ }
+
+ $this->router = $router;
+ return $this;
+ }
+
+ /**
+ * Set route match returned by the router.
+ *
+ * @param LegacyRouteMatch|RouteMatch $routeMatch
+ * @return Url
+ */
+ public function setRouteMatch($routeMatch)
+ {
+ if (! $routeMatch instanceof RouteMatch
+ && ! $routeMatch instanceof LegacyRouteMatch
+ ) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects a %s or %s instance; received %s',
+ __METHOD__,
+ RouteMatch::class,
+ LegacyRouteMatch::class,
+ (is_object($routeMatch) ? get_class($routeMatch) : gettype($routeMatch))
+ ));
+ }
+
+ $this->routeMatch = $routeMatch;
+ return $this;
+ }
+}
diff --git a/module/Zend/View/src/Helper/ViewModel.php b/module/Zend/View/src/Helper/ViewModel.php
new file mode 100644
index 00000000..4cf26607
--- /dev/null
+++ b/module/Zend/View/src/Helper/ViewModel.php
@@ -0,0 +1,92 @@
+current = $model;
+ return $this;
+ }
+
+ /**
+ * Get the current view model
+ *
+ * @return null|Model
+ */
+ public function getCurrent()
+ {
+ return $this->current;
+ }
+
+ /**
+ * Is a current view model composed?
+ *
+ * @return bool
+ */
+ public function hasCurrent()
+ {
+ return ($this->current instanceof Model);
+ }
+
+ /**
+ * Set the root view model
+ *
+ * @param Model $model
+ * @return ViewModel
+ */
+ public function setRoot(Model $model)
+ {
+ $this->root = $model;
+ return $this;
+ }
+
+ /**
+ * Get the root view model
+ *
+ * @return null|Model
+ */
+ public function getRoot()
+ {
+ return $this->root;
+ }
+
+ /**
+ * Is a root view model composed?
+ *
+ * @return bool
+ */
+ public function hasRoot()
+ {
+ return ($this->root instanceof Model);
+ }
+}
diff --git a/module/Zend/View/src/HelperPluginManager.php b/module/Zend/View/src/HelperPluginManager.php
new file mode 100644
index 00000000..ab45dc17
--- /dev/null
+++ b/module/Zend/View/src/HelperPluginManager.php
@@ -0,0 +1,441 @@
+ Helper\Asset::class,
+ 'Asset' => Helper\Asset::class,
+ 'basePath' => Helper\BasePath::class,
+ 'BasePath' => Helper\BasePath::class,
+ 'basepath' => Helper\BasePath::class,
+ 'Cycle' => Helper\Cycle::class,
+ 'cycle' => Helper\Cycle::class,
+ 'declareVars' => Helper\DeclareVars::class,
+ 'DeclareVars' => Helper\DeclareVars::class,
+ 'declarevars' => Helper\DeclareVars::class,
+ 'Doctype' => Helper\Doctype::class,
+ 'doctype' => Helper\Doctype::class, // overridden by a factory in ViewHelperManagerFactory
+ 'escapeCss' => Helper\EscapeCss::class,
+ 'EscapeCss' => Helper\EscapeCss::class,
+ 'escapecss' => Helper\EscapeCss::class,
+ 'escapeHtmlAttr' => Helper\EscapeHtmlAttr::class,
+ 'EscapeHtmlAttr' => Helper\EscapeHtmlAttr::class,
+ 'escapehtmlattr' => Helper\EscapeHtmlAttr::class,
+ 'escapeHtml' => Helper\EscapeHtml::class,
+ 'EscapeHtml' => Helper\EscapeHtml::class,
+ 'escapehtml' => Helper\EscapeHtml::class,
+ 'escapeJs' => Helper\EscapeJs::class,
+ 'EscapeJs' => Helper\EscapeJs::class,
+ 'escapejs' => Helper\EscapeJs::class,
+ 'escapeUrl' => Helper\EscapeUrl::class,
+ 'EscapeUrl' => Helper\EscapeUrl::class,
+ 'escapeurl' => Helper\EscapeUrl::class,
+ 'flashmessenger' => Helper\FlashMessenger::class,
+ 'flashMessenger' => Helper\FlashMessenger::class,
+ 'FlashMessenger' => Helper\FlashMessenger::class,
+ 'Gravatar' => Helper\Gravatar::class,
+ 'gravatar' => Helper\Gravatar::class,
+ 'headLink' => Helper\HeadLink::class,
+ 'HeadLink' => Helper\HeadLink::class,
+ 'headlink' => Helper\HeadLink::class,
+ 'headMeta' => Helper\HeadMeta::class,
+ 'HeadMeta' => Helper\HeadMeta::class,
+ 'headmeta' => Helper\HeadMeta::class,
+ 'headScript' => Helper\HeadScript::class,
+ 'HeadScript' => Helper\HeadScript::class,
+ 'headscript' => Helper\HeadScript::class,
+ 'headStyle' => Helper\HeadStyle::class,
+ 'HeadStyle' => Helper\HeadStyle::class,
+ 'headstyle' => Helper\HeadStyle::class,
+ 'headTitle' => Helper\HeadTitle::class,
+ 'HeadTitle' => Helper\HeadTitle::class,
+ 'headtitle' => Helper\HeadTitle::class,
+ 'htmlflash' => Helper\HtmlFlash::class,
+ 'htmlFlash' => Helper\HtmlFlash::class,
+ 'HtmlFlash' => Helper\HtmlFlash::class,
+ 'htmllist' => Helper\HtmlList::class,
+ 'htmlList' => Helper\HtmlList::class,
+ 'HtmlList' => Helper\HtmlList::class,
+ 'htmlobject' => Helper\HtmlObject::class,
+ 'htmlObject' => Helper\HtmlObject::class,
+ 'HtmlObject' => Helper\HtmlObject::class,
+ 'htmlpage' => Helper\HtmlPage::class,
+ 'htmlPage' => Helper\HtmlPage::class,
+ 'HtmlPage' => Helper\HtmlPage::class,
+ 'htmlquicktime' => Helper\HtmlQuicktime::class,
+ 'htmlQuicktime' => Helper\HtmlQuicktime::class,
+ 'HtmlQuicktime' => Helper\HtmlQuicktime::class,
+ 'htmltag' => Helper\HtmlTag::class,
+ 'htmlTag' => Helper\HtmlTag::class,
+ 'HtmlTag' => Helper\HtmlTag::class,
+ 'identity' => Helper\Identity::class,
+ 'Identity' => Helper\Identity::class,
+ 'inlinescript' => Helper\InlineScript::class,
+ 'inlineScript' => Helper\InlineScript::class,
+ 'InlineScript' => Helper\InlineScript::class,
+ 'json' => Helper\Json::class,
+ 'Json' => Helper\Json::class,
+ 'layout' => Helper\Layout::class,
+ 'Layout' => Helper\Layout::class,
+ 'paginationcontrol' => Helper\PaginationControl::class,
+ 'paginationControl' => Helper\PaginationControl::class,
+ 'PaginationControl' => Helper\PaginationControl::class,
+ 'partial' => Helper\Partial::class,
+ 'partialloop' => Helper\PartialLoop::class,
+ 'partialLoop' => Helper\PartialLoop::class,
+ 'PartialLoop' => Helper\PartialLoop::class,
+ 'Partial' => Helper\Partial::class,
+ 'placeholder' => Helper\Placeholder::class,
+ 'Placeholder' => Helper\Placeholder::class,
+ 'renderchildmodel' => Helper\RenderChildModel::class,
+ 'renderChildModel' => Helper\RenderChildModel::class,
+ 'RenderChildModel' => Helper\RenderChildModel::class,
+ 'render_child_model' => Helper\RenderChildModel::class,
+ 'rendertoplaceholder' => Helper\RenderToPlaceholder::class,
+ 'renderToPlaceholder' => Helper\RenderToPlaceholder::class,
+ 'RenderToPlaceholder' => Helper\RenderToPlaceholder::class,
+ 'serverurl' => Helper\ServerUrl::class,
+ 'serverUrl' => Helper\ServerUrl::class,
+ 'ServerUrl' => Helper\ServerUrl::class,
+ 'url' => Helper\Url::class,
+ 'Url' => Helper\Url::class,
+ 'view_model' => Helper\ViewModel::class,
+ 'viewmodel' => Helper\ViewModel::class,
+ 'viewModel' => Helper\ViewModel::class,
+ 'ViewModel' => Helper\ViewModel::class,
+ ];
+
+ /**
+ * Default factories
+ *
+ * basepath, doctype, and url are set up as factories in the ViewHelperManagerFactory.
+ * basepath and url are not very useful without their factories, however the doctype
+ * helper works fine as an invokable. The factory for doctype simply checks for the
+ * config value from the merged config.
+ *
+ * @var array
+ */
+ protected $factories = [
+ Helper\Asset::class => Helper\Service\AssetFactory::class,
+ Helper\FlashMessenger::class => Helper\Service\FlashMessengerFactory::class,
+ Helper\Identity::class => Helper\Service\IdentityFactory::class,
+ Helper\BasePath::class => InvokableFactory::class,
+ Helper\Cycle::class => InvokableFactory::class,
+ Helper\DeclareVars::class => InvokableFactory::class,
+ Helper\Doctype::class => InvokableFactory::class, // overridden in ViewHelperManagerFactory
+ Helper\EscapeHtml::class => InvokableFactory::class,
+ Helper\EscapeHtmlAttr::class => InvokableFactory::class,
+ Helper\EscapeJs::class => InvokableFactory::class,
+ Helper\EscapeCss::class => InvokableFactory::class,
+ Helper\EscapeUrl::class => InvokableFactory::class,
+ Helper\Gravatar::class => InvokableFactory::class,
+ Helper\HtmlTag::class => InvokableFactory::class,
+ Helper\HeadLink::class => InvokableFactory::class,
+ Helper\HeadMeta::class => InvokableFactory::class,
+ Helper\HeadScript::class => InvokableFactory::class,
+ Helper\HeadStyle::class => InvokableFactory::class,
+ Helper\HeadTitle::class => InvokableFactory::class,
+ Helper\HtmlFlash::class => InvokableFactory::class,
+ Helper\HtmlList::class => InvokableFactory::class,
+ Helper\HtmlObject::class => InvokableFactory::class,
+ Helper\HtmlPage::class => InvokableFactory::class,
+ Helper\HtmlQuicktime::class => InvokableFactory::class,
+ Helper\InlineScript::class => InvokableFactory::class,
+ Helper\Json::class => InvokableFactory::class,
+ Helper\Layout::class => InvokableFactory::class,
+ Helper\PaginationControl::class => InvokableFactory::class,
+ Helper\PartialLoop::class => InvokableFactory::class,
+ Helper\Partial::class => InvokableFactory::class,
+ Helper\Placeholder::class => InvokableFactory::class,
+ Helper\RenderChildModel::class => InvokableFactory::class,
+ Helper\RenderToPlaceholder::class => InvokableFactory::class,
+ Helper\ServerUrl::class => InvokableFactory::class,
+ Helper\Url::class => InvokableFactory::class,
+ Helper\ViewModel::class => InvokableFactory::class,
+
+ // v2 canonical FQCNs
+
+ 'zendviewhelperasset' => Helper\Service\AssetFactory::class,
+ 'zendviewhelperflashmessenger' => Helper\Service\FlashMessengerFactory::class,
+ 'zendviewhelperidentity' => Helper\Service\IdentityFactory::class,
+ 'zendviewhelperbasepath' => InvokableFactory::class,
+ 'zendviewhelpercycle' => InvokableFactory::class,
+ 'zendviewhelperdeclarevars' => InvokableFactory::class,
+ 'zendviewhelperdoctype' => InvokableFactory::class,
+ 'zendviewhelperescapehtml' => InvokableFactory::class,
+ 'zendviewhelperescapehtmlattr' => InvokableFactory::class,
+ 'zendviewhelperescapejs' => InvokableFactory::class,
+ 'zendviewhelperescapecss' => InvokableFactory::class,
+ 'zendviewhelperescapeurl' => InvokableFactory::class,
+ 'zendviewhelpergravatar' => InvokableFactory::class,
+ 'zendviewhelperhtmltag' => InvokableFactory::class,
+ 'zendviewhelperheadlink' => InvokableFactory::class,
+ 'zendviewhelperheadmeta' => InvokableFactory::class,
+ 'zendviewhelperheadscript' => InvokableFactory::class,
+ 'zendviewhelperheadstyle' => InvokableFactory::class,
+ 'zendviewhelperheadtitle' => InvokableFactory::class,
+ 'zendviewhelperhtmlflash' => InvokableFactory::class,
+ 'zendviewhelperhtmllist' => InvokableFactory::class,
+ 'zendviewhelperhtmlobject' => InvokableFactory::class,
+ 'zendviewhelperhtmlpage' => InvokableFactory::class,
+ 'zendviewhelperhtmlquicktime' => InvokableFactory::class,
+ 'zendviewhelperinlinescript' => InvokableFactory::class,
+ 'zendviewhelperjson' => InvokableFactory::class,
+ 'zendviewhelperlayout' => InvokableFactory::class,
+ 'zendviewhelperpaginationcontrol' => InvokableFactory::class,
+ 'zendviewhelperpartialloop' => InvokableFactory::class,
+ 'zendviewhelperpartial' => InvokableFactory::class,
+ 'zendviewhelperplaceholder' => InvokableFactory::class,
+ 'zendviewhelperrenderchildmodel' => InvokableFactory::class,
+ 'zendviewhelperrendertoplaceholder' => InvokableFactory::class,
+ 'zendviewhelperserverurl' => InvokableFactory::class,
+ 'zendviewhelperurl' => InvokableFactory::class,
+ 'zendviewhelperviewmodel' => InvokableFactory::class,
+ ];
+
+ /**
+ * @var Renderer\RendererInterface
+ */
+ protected $renderer;
+
+ /**
+ * Constructor
+ *
+ * Merges provided configuration with default configuration.
+ *
+ * Adds initializers to inject the attached renderer and translator, if
+ * any, to the currently requested helper.
+ *
+ * @param null|ConfigInterface|ContainerInterface $configOrContainerInstance
+ * @param array $v3config If $configOrContainerInstance is a container, this
+ * value will be passed to the parent constructor.
+ */
+ public function __construct($configOrContainerInstance = null, array $v3config = [])
+ {
+ $this->initializers[] = [$this, 'injectRenderer'];
+ $this->initializers[] = [$this, 'injectTranslator'];
+ $this->initializers[] = [$this, 'injectEventManager'];
+
+ parent::__construct($configOrContainerInstance, $v3config);
+ }
+
+ /**
+ * Set renderer
+ *
+ * @param Renderer\RendererInterface $renderer
+ * @return HelperPluginManager
+ */
+ public function setRenderer(Renderer\RendererInterface $renderer)
+ {
+ $this->renderer = $renderer;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve renderer instance
+ *
+ * @return null|Renderer\RendererInterface
+ */
+ public function getRenderer()
+ {
+ return $this->renderer;
+ }
+
+ /**
+ * Inject a helper instance with the registered renderer
+ *
+ * @param ContainerInterface|Helper\HelperInterface $first helper instance
+ * under zend-servicemanager v2, ContainerInterface under v3.
+ * @param ContainerInterface|Helper\HelperInterface $second
+ * ContainerInterface under zend-servicemanager v3, helper instance
+ * under v2. Ignored regardless.
+ */
+ public function injectRenderer($first, $second)
+ {
+ $helper = ($first instanceof ContainerInterface)
+ ? $second
+ : $first;
+
+ if (! $helper instanceof Helper\HelperInterface) {
+ return;
+ }
+
+ $renderer = $this->getRenderer();
+ if (null === $renderer) {
+ return;
+ }
+ $helper->setView($renderer);
+ }
+
+ /**
+ * Inject a helper instance with the registered translator
+ *
+ * @param ContainerInterface|Helper\HelperInterface $first helper instance
+ * under zend-servicemanager v2, ContainerInterface under v3.
+ * @param ContainerInterface|Helper\HelperInterface $second
+ * ContainerInterface under zend-servicemanager v3, helper instance
+ * under v2. Ignored regardless.
+ */
+ public function injectTranslator($first, $second)
+ {
+ if ($first instanceof ContainerInterface) {
+ // v3 usage
+ $container = $first;
+ $helper = $second;
+ } else {
+ // v2 usage; grab the parent container
+ $container = $second->getServiceLocator();
+ $helper = $first;
+ }
+
+ // Allow either direct implementation or duck-typing.
+ if (! $helper instanceof TranslatorAwareInterface
+ && ! method_exists($helper, 'setTranslator')
+ ) {
+ return;
+ }
+
+ if (! $container) {
+ // Under zend-navigation v2.5, the navigation PluginManager is
+ // always lazy-loaded, which means it never has a parent
+ // container.
+ return;
+ }
+
+ if (method_exists($helper, 'hasTranslator') && $helper->hasTranslator()) {
+ return;
+ }
+
+ if ($container->has('MvcTranslator')) {
+ $helper->setTranslator($container->get('MvcTranslator'));
+ return;
+ }
+
+ if ($container->has(TranslatorInterface::class)) {
+ $helper->setTranslator($container->get(TranslatorInterface::class));
+ return;
+ }
+
+ if ($container->has('Translator')) {
+ $helper->setTranslator($container->get('Translator'));
+ return;
+ }
+ }
+
+ /**
+ * Inject a helper instance with the registered event manager
+ *
+ * @param ContainerInterface|Helper\HelperInterface $first helper instance
+ * under zend-servicemanager v2, ContainerInterface under v3.
+ * @param ContainerInterface|Helper\HelperInterface $second
+ * ContainerInterface under zend-servicemanager v3, helper instance
+ * under v2. Ignored regardless.
+ */
+ public function injectEventManager($first, $second)
+ {
+ if ($first instanceof ContainerInterface) {
+ // v3 usage
+ $container = $first;
+ $helper = $second;
+ } else {
+ // v2 usage; grab the parent container
+ $container = $second->getServiceLocator();
+ $helper = $first;
+ }
+
+ if (! $container) {
+ // Under zend-navigation v2.5, the navigation PluginManager is
+ // always lazy-loaded, which means it never has a parent
+ // container.
+ return;
+ }
+
+ if (! $helper instanceof EventManagerAwareInterface) {
+ return;
+ }
+
+ if (! $container->has('EventManager')) {
+ // If the container doesn't have an EM service, do nothing.
+ return;
+ }
+
+ $events = $helper->getEventManager();
+ if (! $events || ! $events->getSharedManager() instanceof SharedEventManagerInterface) {
+ $helper->setEventManager($container->get('EventManager'));
+ }
+ }
+
+ /**
+ * Validate the plugin is of the expected type (v3).
+ *
+ * Validates against callables and HelperInterface implementations.
+ *
+ * @param mixed $instance
+ * @throws InvalidServiceException
+ */
+ public function validate($instance)
+ {
+ if (! is_callable($instance) && ! $instance instanceof Helper\HelperInterface) {
+ throw new InvalidServiceException(
+ sprintf(
+ '%s can only create instances of %s and/or callables; %s is invalid',
+ get_class($this),
+ Helper\HelperInterface::class,
+ (is_object($instance) ? get_class($instance) : gettype($instance))
+ )
+ );
+ }
+ }
+
+ /**
+ * Validate the plugin is of the expected type (v2).
+ *
+ * Proxies to `validate()`.
+ *
+ * @param mixed $instance
+ * @throws InvalidHelperException
+ */
+ public function validatePlugin($instance)
+ {
+ try {
+ $this->validate($instance);
+ } catch (InvalidServiceException $e) {
+ throw new InvalidHelperException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+}
diff --git a/module/Zend/View/src/Model/ClearableModelInterface.php b/module/Zend/View/src/Model/ClearableModelInterface.php
new file mode 100644
index 00000000..b72b848a
--- /dev/null
+++ b/module/Zend/View/src/Model/ClearableModelInterface.php
@@ -0,0 +1,23 @@
+options['errorLevel'] = $errorLevel;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getErrorLevel()
+ {
+ if (array_key_exists('errorLevel', $this->options)) {
+ return $this->options['errorLevel'];
+ }
+ }
+
+ /**
+ * Set result text.
+ *
+ * @param string $text
+ * @return \Zend\View\Model\ConsoleModel
+ */
+ public function setResult($text)
+ {
+ $this->setVariable(self::RESULT, $text);
+ return $this;
+ }
+
+ /**
+ * Get result text.
+ *
+ * @return mixed
+ */
+ public function getResult()
+ {
+ return $this->getVariable(self::RESULT);
+ }
+}
diff --git a/module/Zend/View/src/Model/FeedModel.php b/module/Zend/View/src/Model/FeedModel.php
new file mode 100644
index 00000000..b6e8902f
--- /dev/null
+++ b/module/Zend/View/src/Model/FeedModel.php
@@ -0,0 +1,89 @@
+feed instanceof Feed) {
+ return $this->feed;
+ }
+
+ if (! $this->type) {
+ $options = $this->getOptions();
+ if (isset($options['feed_type'])) {
+ $this->type = $options['feed_type'];
+ }
+ }
+
+ $variables = $this->getVariables();
+ $feed = FeedFactory::factory($variables);
+ $this->setFeed($feed);
+
+ return $this->feed;
+ }
+
+ /**
+ * Set the feed object
+ *
+ * @param Feed $feed
+ * @return FeedModel
+ */
+ public function setFeed(Feed $feed)
+ {
+ $this->feed = $feed;
+ return $this;
+ }
+
+ /**
+ * Get the feed type
+ *
+ * @return false|string
+ */
+ public function getFeedType()
+ {
+ if ($this->type) {
+ return $this->type;
+ }
+
+ $options = $this->getOptions();
+ if (isset($options['feed_type'])) {
+ $this->type = $options['feed_type'];
+ }
+ return $this->type;
+ }
+}
diff --git a/module/Zend/View/src/Model/JsonModel.php b/module/Zend/View/src/Model/JsonModel.php
new file mode 100644
index 00000000..4855f6b7
--- /dev/null
+++ b/module/Zend/View/src/Model/JsonModel.php
@@ -0,0 +1,73 @@
+jsonpCallback = $callback;
+ return $this;
+ }
+
+ /**
+ * Serialize to JSON
+ *
+ * @return string
+ */
+ public function serialize()
+ {
+ $variables = $this->getVariables();
+ if ($variables instanceof Traversable) {
+ $variables = ArrayUtils::iteratorToArray($variables);
+ }
+
+ $options = [
+ 'prettyPrint' => $this->getOption('prettyPrint'),
+ ];
+
+ if (null !== $this->jsonpCallback) {
+ return $this->jsonpCallback.'('.Json::encode($variables, false, $options).');';
+ }
+ return Json::encode($variables, false, $options);
+ }
+}
diff --git a/module/Zend/View/src/Model/ModelInterface.php b/module/Zend/View/src/Model/ModelInterface.php
new file mode 100644
index 00000000..fa409677
--- /dev/null
+++ b/module/Zend/View/src/Model/ModelInterface.php
@@ -0,0 +1,167 @@
+setVariables($variables, true);
+
+ if (null !== $options) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Property overloading: set variable value
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ $this->setVariable($name, $value);
+ }
+
+ /**
+ * Property overloading: get variable value
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ if (! $this->__isset($name)) {
+ return;
+ }
+
+ $variables = $this->getVariables();
+ return $variables[$name];
+ }
+
+ /**
+ * Property overloading: do we have the requested variable value?
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ $variables = $this->getVariables();
+ return isset($variables[$name]);
+ }
+
+ /**
+ * Property overloading: unset the requested variable
+ *
+ * @param string $name
+ * @return void
+ */
+ public function __unset($name)
+ {
+ if (! $this->__isset($name)) {
+ return;
+ }
+
+ unset($this->variables[$name]);
+ }
+
+ /**
+ * Called after this view model is cloned.
+ *
+ * Clones $variables property so changes done to variables in the new
+ * instance don't change the current one.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ if (is_object($this->variables)) {
+ $this->variables = clone $this->variables;
+ }
+ }
+
+ /**
+ * Set a single option
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return ViewModel
+ */
+ public function setOption($name, $value)
+ {
+ $this->options[(string) $name] = $value;
+ return $this;
+ }
+
+ /**
+ * Get a single option
+ *
+ * @param string $name The option to get.
+ * @param mixed|null $default (optional) A default value if the option is not yet set.
+ * @return mixed
+ */
+ public function getOption($name, $default = null)
+ {
+ $name = (string) $name;
+ return array_key_exists($name, $this->options) ? $this->options[$name] : $default;
+ }
+
+ /**
+ * Set renderer options/hints en masse
+ *
+ * @param array|Traversable $options
+ * @throws \Zend\View\Exception\InvalidArgumentException
+ * @return ViewModel
+ */
+ public function setOptions($options)
+ {
+ // Assumption is that lowest common denominator for renderer configuration
+ // is an array
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (! is_array($options)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an array, or Traversable argument; received "%s"',
+ __METHOD__,
+ (is_object($options) ? get_class($options) : gettype($options))
+ ));
+ }
+
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * Get renderer options/hints
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Clear any existing renderer options/hints
+ *
+ * @return ViewModel
+ */
+ public function clearOptions()
+ {
+ $this->options = [];
+ return $this;
+ }
+
+ /**
+ * Get a single view variable
+ *
+ * @param string $name
+ * @param mixed|null $default (optional) default value if the variable is not present.
+ * @return mixed
+ */
+ public function getVariable($name, $default = null)
+ {
+ $name = (string) $name;
+ if (array_key_exists($name, $this->variables)) {
+ return $this->variables[$name];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Set view variable
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return ViewModel
+ */
+ public function setVariable($name, $value)
+ {
+ $this->variables[(string) $name] = $value;
+ return $this;
+ }
+
+ /**
+ * Set view variables en masse
+ *
+ * Can be an array or a Traversable + ArrayAccess object.
+ *
+ * @param array|ArrayAccess|Traversable $variables
+ * @param bool $overwrite Whether or not to overwrite the internal container with $variables
+ * @throws Exception\InvalidArgumentException
+ * @return ViewModel
+ */
+ public function setVariables($variables, $overwrite = false)
+ {
+ if (! is_array($variables) && ! $variables instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an array, or Traversable argument; received "%s"',
+ __METHOD__,
+ (is_object($variables) ? get_class($variables) : gettype($variables))
+ ));
+ }
+
+ if ($overwrite) {
+ if (is_object($variables) && ! $variables instanceof ArrayAccess) {
+ $variables = ArrayUtils::iteratorToArray($variables);
+ }
+
+ $this->variables = $variables;
+ return $this;
+ }
+
+ foreach ($variables as $key => $value) {
+ $this->setVariable($key, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get view variables
+ *
+ * @return array|ArrayAccess|Traversable
+ */
+ public function getVariables()
+ {
+ return $this->variables;
+ }
+
+ /**
+ * Clear all variables
+ *
+ * Resets the internal variable container to an empty container.
+ *
+ * @return ViewModel
+ */
+ public function clearVariables()
+ {
+ $this->variables = new ViewVariables();
+ return $this;
+ }
+
+ /**
+ * Set the template to be used by this model
+ *
+ * @param string $template
+ * @return ViewModel
+ */
+ public function setTemplate($template)
+ {
+ $this->template = (string) $template;
+ return $this;
+ }
+
+ /**
+ * Get the template to be used by this model
+ *
+ * @return string
+ */
+ public function getTemplate()
+ {
+ return $this->template;
+ }
+
+ /**
+ * Add a child model
+ *
+ * @param ModelInterface $child
+ * @param null|string $captureTo Optional; if specified, the "capture to" value to set on the child
+ * @param null|bool $append Optional; if specified, append to child with the same capture
+ * @return ViewModel
+ */
+ public function addChild(ModelInterface $child, $captureTo = null, $append = null)
+ {
+ $this->children[] = $child;
+ if (null !== $captureTo) {
+ $child->setCaptureTo($captureTo);
+ }
+ if (null !== $append) {
+ $child->setAppend($append);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return all children.
+ *
+ * Return specifies an array, but may be any iterable object.
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ return $this->children;
+ }
+
+ /**
+ * Does the model have any children?
+ *
+ * @return bool
+ */
+ public function hasChildren()
+ {
+ return (bool) $this->children;
+ }
+
+ /**
+ * Clears out all child models
+ *
+ * @return ViewModel
+ */
+ public function clearChildren()
+ {
+ $this->children = [];
+ return $this;
+ }
+
+ /**
+ * Returns an array of Viewmodels with captureTo value $capture
+ *
+ * @param string $capture
+ * @param bool $recursive search recursive through children, default true
+ * @return array
+ */
+ public function getChildrenByCaptureTo($capture, $recursive = true)
+ {
+ $children = [];
+
+ foreach ($this->children as $child) {
+ if ($recursive === true) {
+ $children += $child->getChildrenByCaptureTo($capture);
+ }
+
+ if ($child->captureTo() === $capture) {
+ $children[] = $child;
+ }
+ }
+
+ return $children;
+ }
+
+ /**
+ * Set the name of the variable to capture this model to, if it is a child model
+ *
+ * @param string $capture
+ * @return ViewModel
+ */
+ public function setCaptureTo($capture)
+ {
+ $this->captureTo = (string) $capture;
+ return $this;
+ }
+
+ /**
+ * Get the name of the variable to which to capture this model
+ *
+ * @return string
+ */
+ public function captureTo()
+ {
+ return $this->captureTo;
+ }
+
+ /**
+ * Set flag indicating whether or not this is considered a terminal or standalone model
+ *
+ * @param bool $terminate
+ * @return ViewModel
+ */
+ public function setTerminal($terminate)
+ {
+ $this->terminate = (bool) $terminate;
+ return $this;
+ }
+
+ /**
+ * Is this considered a terminal or standalone model?
+ *
+ * @return bool
+ */
+ public function terminate()
+ {
+ return $this->terminate;
+ }
+
+ /**
+ * Set flag indicating whether or not append to child with the same capture
+ *
+ * @param bool $append
+ * @return ViewModel
+ */
+ public function setAppend($append)
+ {
+ $this->append = (bool) $append;
+ return $this;
+ }
+
+ /**
+ * Is this append to child with the same capture?
+ *
+ * @return bool
+ */
+ public function isAppend()
+ {
+ return $this->append;
+ }
+
+ /**
+ * Return count of children
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->children);
+ }
+
+ /**
+ * Get iterator of children
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->children);
+ }
+}
diff --git a/module/Zend/View/src/Renderer/ConsoleRenderer.php b/module/Zend/View/src/Renderer/ConsoleRenderer.php
new file mode 100644
index 00000000..cc8ae0ae
--- /dev/null
+++ b/module/Zend/View/src/Renderer/ConsoleRenderer.php
@@ -0,0 +1,151 @@
+init();
+ }
+
+ public function setResolver(ResolverInterface $resolver)
+ {
+ return $this;
+ }
+
+ /**
+ * Return the template engine object
+ *
+ * Returns the object instance, as it is its own template engine
+ *
+ * @return ConsoleRenderer
+ */
+ public function getEngine()
+ {
+ return $this;
+ }
+
+ /**
+ * Allow custom object initialization when extending ConsoleRenderer
+ *
+ * Triggered by {@link __construct() the constructor} as its final action.
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Set filter chain
+ *
+ * @param FilterChain $filters
+ * @return ConsoleRenderer
+ */
+ public function setFilterChain(FilterChain $filters)
+ {
+ $this->__filterChain = $filters;
+ return $this;
+ }
+
+ /**
+ * Retrieve filter chain for post-filtering script content
+ *
+ * @return FilterChain
+ */
+ public function getFilterChain()
+ {
+ if (null === $this->__filterChain) {
+ $this->setFilterChain(new FilterChain());
+ }
+ return $this->__filterChain;
+ }
+
+ /**
+ * Recursively processes all ViewModels and returns output.
+ *
+ * @param string|ModelInterface $model A ViewModel instance.
+ * @param null|array|\Traversable $values Values to use when rendering. If none
+ * provided, uses those in the composed
+ * variables container.
+ * @return string Console output.
+ */
+ public function render($model, $values = null)
+ {
+ if (! $model instanceof ModelInterface) {
+ return '';
+ }
+
+ $result = '';
+ $options = $model->getOptions();
+ foreach ($options as $setting => $value) {
+ $method = 'set' . $setting;
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ unset($method, $setting, $value);
+ }
+ unset($options);
+
+ $values = $model->getVariables();
+
+ if (isset($values['result'])) {
+ // filter and append the result
+ $result .= $this->getFilterChain()->filter($values['result']);
+ }
+
+ if ($model->hasChildren()) {
+ // recursively render all children
+ foreach ($model->getChildren() as $child) {
+ $result .= $this->render($child, $values);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @see Zend\View\Renderer\TreeRendererInterface
+ * @return bool
+ */
+ public function canRenderTrees()
+ {
+ return true;
+ }
+}
diff --git a/module/Zend/View/src/Renderer/FeedRenderer.php b/module/Zend/View/src/Renderer/FeedRenderer.php
new file mode 100644
index 00000000..76a0e984
--- /dev/null
+++ b/module/Zend/View/src/Renderer/FeedRenderer.php
@@ -0,0 +1,139 @@
+resolver = $resolver;
+ return $this;
+ }
+
+ /**
+ * Renders values as JSON
+ *
+ * @todo Determine what use case exists for accepting only $nameOrModel
+ * @param string|Model $nameOrModel The script/resource process, or a view model
+ * @param null|array|\ArrayAccess $values Values to use during rendering
+ * @throws Exception\InvalidArgumentException
+ * @return string The script output.
+ */
+ public function render($nameOrModel, $values = null)
+ {
+ if ($nameOrModel instanceof Model) {
+ // Use case 1: View Model provided
+ // Non-FeedModel: cast to FeedModel
+ if (! $nameOrModel instanceof FeedModel) {
+ $vars = $nameOrModel->getVariables();
+ $options = $nameOrModel->getOptions();
+ $type = $this->getFeedType();
+ if (isset($options['feed_type'])) {
+ $type = $options['feed_type'];
+ } else {
+ $this->setFeedType($type);
+ }
+ $nameOrModel = new FeedModel($vars, ['feed_type' => $type]);
+ }
+ } elseif (is_string($nameOrModel)) {
+ // Use case 2: string $nameOrModel + array|Traversable|Feed $values
+ $nameOrModel = new FeedModel($values, (array) $nameOrModel);
+ } else {
+ // Use case 3: failure
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects a ViewModel or a string feed type as the first argument; received "%s"',
+ __METHOD__,
+ (is_object($nameOrModel) ? get_class($nameOrModel) : gettype($nameOrModel))
+ ));
+ }
+
+ // Get feed and type
+ $feed = $nameOrModel->getFeed();
+ $type = $nameOrModel->getFeedType();
+ if (! $type) {
+ $type = $this->getFeedType();
+ } else {
+ $this->setFeedType($type);
+ }
+
+ // Render feed
+ return $feed->export($type);
+ }
+
+ /**
+ * Set feed type ('rss' or 'atom')
+ *
+ * @param string $feedType
+ * @throws Exception\InvalidArgumentException
+ * @return FeedRenderer
+ */
+ public function setFeedType($feedType)
+ {
+ $feedType = strtolower($feedType);
+ if (! in_array($feedType, ['rss', 'atom'])) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects a string of either "rss" or "atom"',
+ __METHOD__
+ ));
+ }
+
+ $this->feedType = $feedType;
+ return $this;
+ }
+
+ /**
+ * Get feed type
+ *
+ * @return string
+ */
+ public function getFeedType()
+ {
+ return $this->feedType;
+ }
+}
diff --git a/module/Zend/View/src/Renderer/JsonRenderer.php b/module/Zend/View/src/Renderer/JsonRenderer.php
new file mode 100644
index 00000000..947c34f3
--- /dev/null
+++ b/module/Zend/View/src/Renderer/JsonRenderer.php
@@ -0,0 +1,243 @@
+resolver = $resolver;
+ }
+
+ /**
+ * Set flag indicating whether or not to merge unnamed children
+ *
+ * @param bool $mergeUnnamedChildren
+ * @return JsonRenderer
+ */
+ public function setMergeUnnamedChildren($mergeUnnamedChildren)
+ {
+ $this->mergeUnnamedChildren = (bool) $mergeUnnamedChildren;
+ return $this;
+ }
+
+ /**
+ * Set the JSONP callback function name
+ *
+ * @param string $callback
+ * @return JsonRenderer
+ */
+ public function setJsonpCallback($callback)
+ {
+ $callback = (string) $callback;
+ if (! empty($callback)) {
+ $this->jsonpCallback = $callback;
+ }
+ return $this;
+ }
+
+ /**
+ * Returns whether or not the jsonpCallback has been set
+ *
+ * @return bool
+ */
+ public function hasJsonpCallback()
+ {
+ return (null !== $this->jsonpCallback);
+ }
+
+ /**
+ * Should we merge unnamed children?
+ *
+ * @return bool
+ */
+ public function mergeUnnamedChildren()
+ {
+ return $this->mergeUnnamedChildren;
+ }
+
+ /**
+ * Renders values as JSON
+ *
+ * @todo Determine what use case exists for accepting both $nameOrModel and $values
+ * @param string|Model $nameOrModel The script/resource process, or a view model
+ * @param null|array|\ArrayAccess $values Values to use during rendering
+ * @throws Exception\DomainException
+ * @return string The script output.
+ */
+ public function render($nameOrModel, $values = null)
+ {
+ // use case 1: View Models
+ // Serialize variables in view model
+ if ($nameOrModel instanceof Model) {
+ if ($nameOrModel instanceof JsonModel) {
+ $children = $this->recurseModel($nameOrModel, false);
+ $this->injectChildren($nameOrModel, $children);
+ $values = $nameOrModel->serialize();
+ } else {
+ $values = $this->recurseModel($nameOrModel);
+ $values = Json::encode($values);
+ }
+
+ if ($this->hasJsonpCallback()) {
+ $values = $this->jsonpCallback . '(' . $values . ');';
+ }
+ return $values;
+ }
+
+ // use case 2: $nameOrModel is populated, $values is not
+ // Serialize $nameOrModel
+ if (null === $values) {
+ if (! is_object($nameOrModel) || $nameOrModel instanceof JsonSerializable) {
+ $return = Json::encode($nameOrModel);
+ } elseif ($nameOrModel instanceof Traversable) {
+ $nameOrModel = ArrayUtils::iteratorToArray($nameOrModel);
+ $return = Json::encode($nameOrModel);
+ } else {
+ $return = Json::encode(get_object_vars($nameOrModel));
+ }
+
+ if ($this->hasJsonpCallback()) {
+ $return = $this->jsonpCallback . '(' . $return . ');';
+ }
+ return $return;
+ }
+
+ // use case 3: Both $nameOrModel and $values are populated
+ throw new Exception\DomainException(sprintf(
+ '%s: Do not know how to handle operation when both $nameOrModel and $values are populated',
+ __METHOD__
+ ));
+ }
+
+ /**
+ * Can this renderer render trees of view models?
+ *
+ * Yes.
+ *
+ * @return true
+ */
+ public function canRenderTrees()
+ {
+ return true;
+ }
+
+ /**
+ * Retrieve values from a model and recurse its children to build a data structure
+ *
+ * @param Model $model
+ * @param bool $mergeWithVariables Whether or not to merge children with
+ * the variables of the $model
+ * @return array
+ */
+ protected function recurseModel(Model $model, $mergeWithVariables = true)
+ {
+ $values = [];
+ if ($mergeWithVariables) {
+ $values = $model->getVariables();
+ }
+
+ if ($values instanceof Traversable) {
+ $values = ArrayUtils::iteratorToArray($values);
+ }
+
+ if (! $model->hasChildren()) {
+ return $values;
+ }
+
+ $mergeChildren = $this->mergeUnnamedChildren();
+ foreach ($model as $child) {
+ $captureTo = $child->captureTo();
+ if (! $captureTo && ! $mergeChildren) {
+ // We don't want to do anything with this child
+ continue;
+ }
+
+ $childValues = $this->recurseModel($child);
+ if ($captureTo) {
+ // Capturing to a specific key
+ // TODO please complete if append is true. must change old
+ // value to array and append to array?
+ $values[$captureTo] = $childValues;
+ } elseif ($mergeChildren) {
+ // Merging values with parent
+ $values = array_replace_recursive($values, $childValues);
+ }
+ }
+ return $values;
+ }
+
+ /**
+ * Inject discovered child model values into parent model
+ *
+ * @todo detect collisions and decide whether to append and/or aggregate?
+ * @param Model $model
+ * @param array $children
+ */
+ protected function injectChildren(Model $model, array $children)
+ {
+ foreach ($children as $child => $value) {
+ // TODO detect collisions and decide whether to append and/or aggregate?
+ $model->setVariable($child, $value);
+ }
+ }
+}
diff --git a/module/Zend/View/src/Renderer/PhpRenderer.php b/module/Zend/View/src/Renderer/PhpRenderer.php
new file mode 100644
index 00000000..2063486c
--- /dev/null
+++ b/module/Zend/View/src/Renderer/PhpRenderer.php
@@ -0,0 +1,581 @@
+init();
+ }
+
+ /**
+ * Return the template engine object
+ *
+ * Returns the object instance, as it is its own template engine
+ *
+ * @return PhpRenderer
+ */
+ public function getEngine()
+ {
+ return $this;
+ }
+
+ /**
+ * Allow custom object initialization when extending PhpRenderer
+ *
+ * Triggered by {@link __construct() the constructor} as its final action.
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Set script resolver
+ *
+ * @param Resolver $resolver
+ * @return PhpRenderer
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setResolver(Resolver $resolver)
+ {
+ $this->__templateResolver = $resolver;
+ return $this;
+ }
+
+ /**
+ * Retrieve template name or template resolver
+ *
+ * @param null|string $name
+ * @return string|Resolver
+ */
+ public function resolver($name = null)
+ {
+ if (null === $this->__templateResolver) {
+ $this->setResolver(new TemplatePathStack());
+ }
+
+ if (null !== $name) {
+ return $this->__templateResolver->resolve($name, $this);
+ }
+
+ return $this->__templateResolver;
+ }
+
+ /**
+ * Set variable storage
+ *
+ * Expects either an array, or an object implementing ArrayAccess.
+ *
+ * @param array|ArrayAccess $variables
+ * @return PhpRenderer
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setVars($variables)
+ {
+ if (! is_array($variables) && ! $variables instanceof ArrayAccess) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Expected array or ArrayAccess object; received "%s"',
+ (is_object($variables) ? get_class($variables) : gettype($variables))
+ ));
+ }
+
+ // Enforce a Variables container
+ if (! $variables instanceof Variables) {
+ $variablesAsArray = [];
+ foreach ($variables as $key => $value) {
+ $variablesAsArray[$key] = $value;
+ }
+ $variables = new Variables($variablesAsArray);
+ }
+
+ $this->__vars = $variables;
+ return $this;
+ }
+
+ /**
+ * Get a single variable, or all variables
+ *
+ * @param mixed $key
+ * @return mixed
+ */
+ public function vars($key = null)
+ {
+ if (null === $this->__vars) {
+ $this->setVars(new Variables());
+ }
+
+ if (null === $key) {
+ return $this->__vars;
+ }
+ return $this->__vars[$key];
+ }
+
+ /**
+ * Get a single variable
+ *
+ * @param mixed $key
+ * @return mixed
+ */
+ public function get($key)
+ {
+ if (null === $this->__vars) {
+ $this->setVars(new Variables());
+ }
+
+ return $this->__vars[$key];
+ }
+
+ /**
+ * Overloading: proxy to Variables container
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ $vars = $this->vars();
+ return $vars[$name];
+ }
+
+ /**
+ * Overloading: proxy to Variables container
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ $vars = $this->vars();
+ $vars[$name] = $value;
+ }
+
+ /**
+ * Overloading: proxy to Variables container
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ $vars = $this->vars();
+ return isset($vars[$name]);
+ }
+
+ /**
+ * Overloading: proxy to Variables container
+ *
+ * @param string $name
+ * @return void
+ */
+ public function __unset($name)
+ {
+ $vars = $this->vars();
+ if (! isset($vars[$name])) {
+ return;
+ }
+ unset($vars[$name]);
+ }
+
+ /**
+ * Set helper plugin manager instance
+ *
+ * @param string|HelperPluginManager $helpers
+ * @return PhpRenderer
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setHelperPluginManager($helpers)
+ {
+ if (is_string($helpers)) {
+ if (! class_exists($helpers)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid helper helpers class provided (%s)',
+ $helpers
+ ));
+ }
+ $helpers = new $helpers(new ServiceManager());
+ }
+ if (! $helpers instanceof HelperPluginManager) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Helper helpers must extend Zend\View\HelperPluginManager; got type "%s" instead',
+ (is_object($helpers) ? get_class($helpers) : gettype($helpers))
+ ));
+ }
+ $helpers->setRenderer($this);
+ $this->__helpers = $helpers;
+
+ return $this;
+ }
+
+ /**
+ * Get helper plugin manager instance
+ *
+ * @return HelperPluginManager
+ */
+ public function getHelperPluginManager()
+ {
+ if (null === $this->__helpers) {
+ $this->setHelperPluginManager(new HelperPluginManager(new ServiceManager()));
+ }
+ return $this->__helpers;
+ }
+
+ /**
+ * Get plugin instance
+ *
+ * @param string $name Name of plugin to return
+ * @param null|array $options Options to pass to plugin constructor (if not already instantiated)
+ * @return AbstractHelper
+ */
+ public function plugin($name, array $options = null)
+ {
+ return $this->getHelperPluginManager()->get($name, $options);
+ }
+
+ /**
+ * Overloading: proxy to helpers
+ *
+ * Proxies to the attached plugin manager to retrieve, return, and potentially
+ * execute helpers.
+ *
+ * * If the helper does not define __invoke, it will be returned
+ * * If the helper does define __invoke, it will be called as a functor
+ *
+ * @param string $method
+ * @param array $argv
+ * @return mixed
+ */
+ public function __call($method, $argv)
+ {
+ $plugin = $this->plugin($method);
+
+ if (is_callable($plugin)) {
+ return call_user_func_array($plugin, $argv);
+ }
+
+ return $plugin;
+ }
+
+ /**
+ * Set filter chain
+ *
+ * @param FilterChain $filters
+ * @return PhpRenderer
+ */
+ public function setFilterChain(FilterChain $filters)
+ {
+ $this->__filterChain = $filters;
+ return $this;
+ }
+
+ /**
+ * Retrieve filter chain for post-filtering script content
+ *
+ * @return FilterChain
+ */
+ public function getFilterChain()
+ {
+ if (null === $this->__filterChain) {
+ $this->setFilterChain(new FilterChain());
+ }
+ return $this->__filterChain;
+ }
+
+ /**
+ * Processes a view script and returns the output.
+ *
+ * @param string|Model $nameOrModel Either the template to use, or a
+ * ViewModel. The ViewModel must have the
+ * template as an option in order to be
+ * valid.
+ * @param null|array|Traversable $values Values to use when rendering. If none
+ * provided, uses those in the composed
+ * variables container.
+ * @return string The script output.
+ * @throws Exception\DomainException if a ViewModel is passed, but does not
+ * contain a template option.
+ * @throws Exception\InvalidArgumentException if the values passed are not
+ * an array or ArrayAccess object
+ * @throws Exception\RuntimeException if the template cannot be rendered
+ */
+ public function render($nameOrModel, $values = null)
+ {
+ if ($nameOrModel instanceof Model) {
+ $model = $nameOrModel;
+ $nameOrModel = $model->getTemplate();
+ if (empty($nameOrModel)) {
+ throw new Exception\DomainException(sprintf(
+ '%s: received View Model argument, but template is empty',
+ __METHOD__
+ ));
+ }
+ $options = $model->getOptions();
+ foreach ($options as $setting => $value) {
+ $method = 'set' . $setting;
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ unset($method, $setting, $value);
+ }
+ unset($options);
+
+ // Give view model awareness via ViewModel helper
+ $helper = $this->plugin('view_model');
+ $helper->setCurrent($model);
+
+ $values = $model->getVariables();
+ unset($model);
+ }
+
+ // find the script file name using the parent private method
+ $this->addTemplate($nameOrModel);
+ unset($nameOrModel); // remove $name from local scope
+
+ $this->__varsCache[] = $this->vars();
+
+ if (null !== $values) {
+ $this->setVars($values);
+ }
+ unset($values);
+
+ // extract all assigned vars (pre-escaped), but not 'this'.
+ // assigns to a double-underscored variable, to prevent naming collisions
+ $__vars = $this->vars()->getArrayCopy();
+ if (array_key_exists('this', $__vars)) {
+ unset($__vars['this']);
+ }
+ extract($__vars);
+ unset($__vars); // remove $__vars from local scope
+
+ $this->__content = '';
+ while ($this->__template = array_pop($this->__templates)) {
+ $this->__file = $this->resolver($this->__template);
+ if (! $this->__file) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: Unable to render template "%s"; resolver could not resolve to a file',
+ __METHOD__,
+ $this->__template
+ ));
+ }
+ try {
+ ob_start();
+ $includeReturn = include $this->__file;
+ $this->__content = ob_get_clean();
+ } catch (\Throwable $ex) {
+ ob_end_clean();
+ throw $ex;
+ } catch (\Exception $ex) { // @TODO clean up once PHP 7 requirement is enforced
+ ob_end_clean();
+ throw $ex;
+ }
+ if ($includeReturn === false && empty($this->__content)) {
+ throw new Exception\UnexpectedValueException(sprintf(
+ '%s: Unable to render template "%s"; file include failed',
+ __METHOD__,
+ $this->__file
+ ));
+ }
+ }
+
+ $this->setVars(array_pop($this->__varsCache));
+
+ if ($this->__filterChain instanceof FilterChain) {
+ return $this->__filterChain->filter($this->__content); // filter output
+ }
+
+ return $this->__content;
+ }
+
+ /**
+ * Set flag indicating whether or not we should render trees of view models
+ *
+ * If set to true, the View instance will not attempt to render children
+ * separately, but instead pass the root view model directly to the PhpRenderer.
+ * It is then up to the developer to render the children from within the
+ * view script.
+ *
+ * @param bool $renderTrees
+ * @return PhpRenderer
+ */
+ public function setCanRenderTrees($renderTrees)
+ {
+ $this->__renderTrees = (bool) $renderTrees;
+ return $this;
+ }
+
+ /**
+ * Can we render trees, or are we configured to do so?
+ *
+ * @return bool
+ */
+ public function canRenderTrees()
+ {
+ return $this->__renderTrees;
+ }
+
+ /**
+ * Add a template to the stack
+ *
+ * @param string $template
+ * @return PhpRenderer
+ */
+ public function addTemplate($template)
+ {
+ $this->__templates[] = $template;
+ return $this;
+ }
+
+ /**
+ * Make sure View variables are cloned when the view is cloned.
+ *
+ * @return PhpRenderer
+ */
+ public function __clone()
+ {
+ $this->__vars = clone $this->vars();
+ }
+}
diff --git a/module/Zend/View/src/Renderer/RendererInterface.php b/module/Zend/View/src/Renderer/RendererInterface.php
new file mode 100644
index 00000000..70228c93
--- /dev/null
+++ b/module/Zend/View/src/Renderer/RendererInterface.php
@@ -0,0 +1,47 @@
+queue = new PriorityQueue();
+ }
+
+ /**
+ * Return count of attached resolvers
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->queue->count();
+ }
+
+ /**
+ * IteratorAggregate: return internal iterator
+ *
+ * @return PriorityQueue
+ */
+ public function getIterator()
+ {
+ return $this->queue;
+ }
+
+ /**
+ * Attach a resolver
+ *
+ * @param Resolver $resolver
+ * @param int $priority
+ * @return AggregateResolver
+ */
+ public function attach(Resolver $resolver, $priority = 1)
+ {
+ $this->queue->insert($resolver, $priority);
+ return $this;
+ }
+
+ /**
+ * Resolve a template/pattern name to a resource the renderer can consume
+ *
+ * @param string $name
+ * @param null|Renderer $renderer
+ * @return false|string
+ */
+ public function resolve($name, Renderer $renderer = null)
+ {
+ $this->lastLookupFailure = false;
+ $this->lastSuccessfulResolver = null;
+
+ if (0 === count($this->queue)) {
+ $this->lastLookupFailure = static::FAILURE_NO_RESOLVERS;
+ return false;
+ }
+
+ foreach ($this->queue as $resolver) {
+ $resource = $resolver->resolve($name, $renderer);
+ if ($resource) {
+ // Resource found; return it
+ $this->lastSuccessfulResolver = $resolver;
+ return $resource;
+ }
+ }
+
+ $this->lastLookupFailure = static::FAILURE_NOT_FOUND;
+ return false;
+ }
+
+ /**
+ * Return the last successful resolver, if any
+ *
+ * @return Resolver
+ */
+ public function getLastSuccessfulResolver()
+ {
+ return $this->lastSuccessfulResolver;
+ }
+
+ /**
+ * Get last lookup failure
+ *
+ * @return false|string
+ */
+ public function getLastLookupFailure()
+ {
+ return $this->lastLookupFailure;
+ }
+}
diff --git a/module/Zend/View/src/Resolver/PrefixPathStackResolver.php b/module/Zend/View/src/Resolver/PrefixPathStackResolver.php
new file mode 100644
index 00000000..9ac95aa1
--- /dev/null
+++ b/module/Zend/View/src/Resolver/PrefixPathStackResolver.php
@@ -0,0 +1,58 @@
+prefixes = $prefixes;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function resolve($name, Renderer $renderer = null)
+ {
+ foreach ($this->prefixes as $prefix => & $resolver) {
+ if (strpos($name, $prefix) !== 0) {
+ continue;
+ }
+
+ if (! $resolver instanceof ResolverInterface) {
+ $resolver = new TemplatePathStack(['script_paths' => (array) $resolver]);
+ }
+
+ if ($result = $resolver->resolve(substr($name, strlen($prefix)), $renderer)) {
+ return $result;
+ }
+ }
+
+ return;
+ }
+}
diff --git a/module/Zend/View/src/Resolver/RelativeFallbackResolver.php b/module/Zend/View/src/Resolver/RelativeFallbackResolver.php
new file mode 100644
index 00000000..db2de2d1
--- /dev/null
+++ b/module/Zend/View/src/Resolver/RelativeFallbackResolver.php
@@ -0,0 +1,74 @@
+resolver = $resolver;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function resolve($name, RendererInterface $renderer = null)
+ {
+ $plugin = [$renderer, 'plugin'];
+
+ if (! is_callable($plugin)) {
+ return false;
+ }
+
+ $helper = call_user_func($plugin, 'view_model');
+
+ if (! $helper instanceof ViewModelHelper) {
+ return false;
+ }
+
+ $currentModel = $helper->getCurrent();
+
+ if (! $currentModel instanceof ModelInterface) {
+ return false;
+ }
+
+ $currentTemplate = $currentModel->getTemplate();
+ $position = strrpos($currentTemplate, self::NS_SEPARATOR);
+
+ if (! $position) {
+ return false;
+ }
+
+ return $this->resolver->resolve(substr($currentTemplate, 0, $position) . self::NS_SEPARATOR . $name, $renderer);
+ }
+}
diff --git a/module/Zend/View/src/Resolver/ResolverInterface.php b/module/Zend/View/src/Resolver/ResolverInterface.php
new file mode 100644
index 00000000..d3d8270e
--- /dev/null
+++ b/module/Zend/View/src/Resolver/ResolverInterface.php
@@ -0,0 +1,24 @@
+setMap($map);
+ }
+
+ /**
+ * IteratorAggregate: return internal iterator
+ *
+ * @return Traversable
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->map);
+ }
+
+ /**
+ * Set (overwrite) template map
+ *
+ * Maps should be arrays or Traversable objects with name => path pairs
+ *
+ * @param array|Traversable $map
+ * @throws Exception\InvalidArgumentException
+ * @return TemplateMapResolver
+ */
+ public function setMap($map)
+ {
+ if (! is_array($map) && ! $map instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an array or Traversable, received "%s"',
+ __METHOD__,
+ (is_object($map) ? get_class($map) : gettype($map))
+ ));
+ }
+
+ if ($map instanceof Traversable) {
+ $map = ArrayUtils::iteratorToArray($map);
+ }
+
+ $this->map = $map;
+ return $this;
+ }
+
+ /**
+ * Add an entry to the map
+ *
+ * @param string|array|Traversable $nameOrMap
+ * @param null|string $path
+ * @throws Exception\InvalidArgumentException
+ * @return TemplateMapResolver
+ */
+ public function add($nameOrMap, $path = null)
+ {
+ if (is_array($nameOrMap) || $nameOrMap instanceof Traversable) {
+ $this->merge($nameOrMap);
+ return $this;
+ }
+
+ if (! is_string($nameOrMap)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects a string, array, or Traversable for the first argument; received "%s"',
+ __METHOD__,
+ (is_object($nameOrMap) ? get_class($nameOrMap) : gettype($nameOrMap))
+ ));
+ }
+
+ if (empty($path)) {
+ if (isset($this->map[$nameOrMap])) {
+ unset($this->map[$nameOrMap]);
+ }
+ return $this;
+ }
+
+ $this->map[$nameOrMap] = $path;
+ return $this;
+ }
+
+ /**
+ * Merge internal map with provided map
+ *
+ * @param array|Traversable $map
+ * @throws Exception\InvalidArgumentException
+ * @return TemplateMapResolver
+ */
+ public function merge($map)
+ {
+ if (! is_array($map) && ! $map instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an array or Traversable, received "%s"',
+ __METHOD__,
+ (is_object($map) ? get_class($map) : gettype($map))
+ ));
+ }
+
+ if ($map instanceof Traversable) {
+ $map = ArrayUtils::iteratorToArray($map);
+ }
+
+ $this->map = array_replace_recursive($this->map, $map);
+ return $this;
+ }
+
+ /**
+ * Does the resolver contain an entry for the given name?
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function has($name)
+ {
+ return array_key_exists($name, $this->map);
+ }
+
+ /**
+ * Retrieve a template path by name
+ *
+ * @param string $name
+ * @return false|string
+ * @throws Exception\DomainException if no entry exists
+ */
+ public function get($name)
+ {
+ if (! $this->has($name)) {
+ return false;
+ }
+ return $this->map[$name];
+ }
+
+ /**
+ * Retrieve the template map
+ *
+ * @return array
+ */
+ public function getMap()
+ {
+ return $this->map;
+ }
+
+ /**
+ * Resolve a template/pattern name to a resource the renderer can consume
+ *
+ * @param string $name
+ * @param null|Renderer $renderer
+ * @return string
+ */
+ public function resolve($name, Renderer $renderer = null)
+ {
+ return $this->get($name);
+ }
+}
diff --git a/module/Zend/View/src/Resolver/TemplatePathStack.php b/module/Zend/View/src/Resolver/TemplatePathStack.php
new file mode 100644
index 00000000..f0399f75
--- /dev/null
+++ b/module/Zend/View/src/Resolver/TemplatePathStack.php
@@ -0,0 +1,338 @@
+useViewStream = (bool) ini_get('short_open_tag');
+ if ($this->useViewStream) {
+ if (! in_array('zend.view', stream_get_wrappers())) {
+ stream_wrapper_register('zend.view', 'Zend\View\Stream');
+ }
+ }
+
+ $this->paths = new SplStack;
+ if (null !== $options) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Configure object
+ *
+ * @param array|Traversable $options
+ * @return void
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setOptions($options)
+ {
+ if (! is_array($options) && ! $options instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Expected array or Traversable object; received "%s"',
+ (is_object($options) ? get_class($options) : gettype($options))
+ ));
+ }
+
+ foreach ($options as $key => $value) {
+ switch (strtolower($key)) {
+ case 'lfi_protection':
+ $this->setLfiProtection($value);
+ break;
+ case 'script_paths':
+ $this->addPaths($value);
+ break;
+ case 'use_stream_wrapper':
+ $this->setUseStreamWrapper($value);
+ break;
+ case 'default_suffix':
+ $this->setDefaultSuffix($value);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Set default file suffix
+ *
+ * @param string $defaultSuffix
+ * @return TemplatePathStack
+ */
+ public function setDefaultSuffix($defaultSuffix)
+ {
+ $this->defaultSuffix = (string) $defaultSuffix;
+ $this->defaultSuffix = ltrim($this->defaultSuffix, '.');
+ return $this;
+ }
+
+ /**
+ * Get default file suffix
+ *
+ * @return string
+ */
+ public function getDefaultSuffix()
+ {
+ return $this->defaultSuffix;
+ }
+
+ /**
+ * Add many paths to the stack at once
+ *
+ * @param array $paths
+ * @return TemplatePathStack
+ */
+ public function addPaths(array $paths)
+ {
+ foreach ($paths as $path) {
+ $this->addPath($path);
+ }
+ return $this;
+ }
+
+ /**
+ * Rest the path stack to the paths provided
+ *
+ * @param SplStack|array $paths
+ * @return TemplatePathStack
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setPaths($paths)
+ {
+ if ($paths instanceof SplStack) {
+ $this->paths = $paths;
+ } elseif (is_array($paths)) {
+ $this->clearPaths();
+ $this->addPaths($paths);
+ } else {
+ throw new Exception\InvalidArgumentException(
+ "Invalid argument provided for \$paths, expecting either an array or SplStack object"
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Normalize a path for insertion in the stack
+ *
+ * @param string $path
+ * @return string
+ */
+ public static function normalizePath($path)
+ {
+ $path = rtrim($path, '/');
+ $path = rtrim($path, '\\');
+ $path .= DIRECTORY_SEPARATOR;
+ return $path;
+ }
+
+ /**
+ * Add a single path to the stack
+ *
+ * @param string $path
+ * @return TemplatePathStack
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addPath($path)
+ {
+ if (! is_string($path)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid path provided; must be a string, received %s',
+ gettype($path)
+ ));
+ }
+ $this->paths[] = static::normalizePath($path);
+ return $this;
+ }
+
+ /**
+ * Clear all paths
+ *
+ * @return void
+ */
+ public function clearPaths()
+ {
+ $this->paths = new SplStack;
+ }
+
+ /**
+ * Returns stack of paths
+ *
+ * @return SplStack
+ */
+ public function getPaths()
+ {
+ return $this->paths;
+ }
+
+ /**
+ * Set LFI protection flag
+ *
+ * @param bool $flag
+ * @return TemplatePathStack
+ */
+ public function setLfiProtection($flag)
+ {
+ $this->lfiProtectionOn = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Return status of LFI protection flag
+ *
+ * @return bool
+ */
+ public function isLfiProtectionOn()
+ {
+ return $this->lfiProtectionOn;
+ }
+
+ /**
+ * Set flag indicating if stream wrapper should be used if short_open_tag is off
+ *
+ * @param bool $flag
+ * @return TemplatePathStack
+ */
+ public function setUseStreamWrapper($flag)
+ {
+ $this->useStreamWrapper = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Should the stream wrapper be used if short_open_tag is off?
+ *
+ * Returns true if the use_stream_wrapper flag is set, and if short_open_tag
+ * is disabled.
+ *
+ * @return bool
+ */
+ public function useStreamWrapper()
+ {
+ return ($this->useViewStream && $this->useStreamWrapper);
+ }
+
+ /**
+ * Retrieve the filesystem path to a view script
+ *
+ * @param string $name
+ * @param null|Renderer $renderer
+ * @return string
+ * @throws Exception\DomainException
+ */
+ public function resolve($name, Renderer $renderer = null)
+ {
+ $this->lastLookupFailure = false;
+
+ if ($this->isLfiProtectionOn() && preg_match('#\.\.[\\\/]#', $name)) {
+ throw new Exception\DomainException(
+ 'Requested scripts may not include parent directory traversal ("../", "..\\" notation)'
+ );
+ }
+
+ if (! count($this->paths)) {
+ $this->lastLookupFailure = static::FAILURE_NO_PATHS;
+ return false;
+ }
+
+ // Ensure we have the expected file extension
+ $defaultSuffix = $this->getDefaultSuffix();
+ if (pathinfo($name, PATHINFO_EXTENSION) == '') {
+ $name .= '.' . $defaultSuffix;
+ }
+
+ foreach ($this->paths as $path) {
+ $file = new SplFileInfo($path . $name);
+ if ($file->isReadable()) {
+ // Found! Return it.
+ if (($filePath = $file->getRealPath()) === false && 0 === strpos($path, 'phar://')) {
+ // Do not try to expand phar paths (realpath + phars == fail)
+ $filePath = $path . $name;
+ if (! file_exists($filePath)) {
+ break;
+ }
+ }
+ if ($this->useStreamWrapper()) {
+ // If using a stream wrapper, prepend the spec to the path
+ $filePath = 'zend.view://' . $filePath;
+ }
+ return $filePath;
+ }
+ }
+
+ $this->lastLookupFailure = static::FAILURE_NOT_FOUND;
+ return false;
+ }
+
+ /**
+ * Get the last lookup failure message, if any
+ *
+ * @return false|string
+ */
+ public function getLastLookupFailure()
+ {
+ return $this->lastLookupFailure;
+ }
+}
diff --git a/module/Zend/View/src/Strategy/FeedStrategy.php b/module/Zend/View/src/Strategy/FeedStrategy.php
new file mode 100644
index 00000000..63a1ac62
--- /dev/null
+++ b/module/Zend/View/src/Strategy/FeedStrategy.php
@@ -0,0 +1,110 @@
+renderer = $renderer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function attach(EventManagerInterface $events, $priority = 1)
+ {
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, [$this, 'selectRenderer'], $priority);
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, [$this, 'injectResponse'], $priority);
+ }
+
+ /**
+ * Detect if we should use the FeedRenderer based on model type
+ *
+ * @param ViewEvent $e
+ * @return null|FeedRenderer
+ */
+ public function selectRenderer(ViewEvent $e)
+ {
+ $model = $e->getModel();
+
+ if (! $model instanceof Model\FeedModel) {
+ // no FeedModel present; do nothing
+ return;
+ }
+
+ // FeedModel found
+ return $this->renderer;
+ }
+
+ /**
+ * Inject the response with the feed payload and appropriate Content-Type header
+ *
+ * @param ViewEvent $e
+ * @return void
+ */
+ public function injectResponse(ViewEvent $e)
+ {
+ $renderer = $e->getRenderer();
+ if ($renderer !== $this->renderer) {
+ // Discovered renderer is not ours; do nothing
+ return;
+ }
+
+ $result = $e->getResult();
+ if (! is_string($result) && ! $result instanceof Feed) {
+ // We don't have a string, and thus, no feed
+ return;
+ }
+
+ // If the result is a feed, export it
+ if ($result instanceof Feed) {
+ $result = $result->export($renderer->getFeedType());
+ }
+
+ // Get the content-type header based on feed type
+ $feedType = $renderer->getFeedType();
+ $feedType = ('rss' == $feedType)
+ ? 'application/rss+xml'
+ : 'application/atom+xml';
+
+ $model = $e->getModel();
+ $charset = '';
+
+ if ($model instanceof Model\FeedModel) {
+ $feed = $model->getFeed();
+
+ $charset = '; charset=' . $feed->getEncoding() . ';';
+ }
+
+ // Populate response
+ $response = $e->getResponse();
+ $response->setContent($result);
+ $headers = $response->getHeaders();
+ $headers->addHeaderLine('content-type', $feedType . $charset);
+ }
+}
diff --git a/module/Zend/View/src/Strategy/JsonStrategy.php b/module/Zend/View/src/Strategy/JsonStrategy.php
new file mode 100644
index 00000000..7e7d9697
--- /dev/null
+++ b/module/Zend/View/src/Strategy/JsonStrategy.php
@@ -0,0 +1,140 @@
+renderer = $renderer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function attach(EventManagerInterface $events, $priority = 1)
+ {
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, [$this, 'selectRenderer'], $priority);
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, [$this, 'injectResponse'], $priority);
+ }
+
+ /**
+ * Set the content-type character set
+ *
+ * @param string $charset
+ * @return JsonStrategy
+ */
+ public function setCharset($charset)
+ {
+ $this->charset = (string) $charset;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current character set
+ *
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Detect if we should use the JsonRenderer based on model type
+ *
+ * @param ViewEvent $e
+ * @return null|JsonRenderer
+ */
+ public function selectRenderer(ViewEvent $e)
+ {
+ $model = $e->getModel();
+
+ if (! $model instanceof Model\JsonModel) {
+ // no JsonModel; do nothing
+ return;
+ }
+
+ // JsonModel found
+ return $this->renderer;
+ }
+
+ /**
+ * Inject the response with the JSON payload and appropriate Content-Type header
+ *
+ * @param ViewEvent $e
+ * @return void
+ */
+ public function injectResponse(ViewEvent $e)
+ {
+ $renderer = $e->getRenderer();
+ if ($renderer !== $this->renderer) {
+ // Discovered renderer is not ours; do nothing
+ return;
+ }
+
+ $result = $e->getResult();
+ if (! is_string($result)) {
+ // We don't have a string, and thus, no JSON
+ return;
+ }
+
+ // Populate response
+ $response = $e->getResponse();
+ $response->setContent($result);
+ $headers = $response->getHeaders();
+
+ if ($this->renderer->hasJsonpCallback()) {
+ $contentType = 'application/javascript';
+ } else {
+ $contentType = 'application/json';
+ }
+
+ $contentType .= '; charset=' . $this->charset;
+ $headers->addHeaderLine('content-type', $contentType);
+
+ if (in_array(strtoupper($this->charset), $this->multibyteCharsets)) {
+ $headers->addHeaderLine('content-transfer-encoding', 'BINARY');
+ }
+ }
+}
diff --git a/module/Zend/View/src/Strategy/PhpRendererStrategy.php b/module/Zend/View/src/Strategy/PhpRendererStrategy.php
new file mode 100644
index 00000000..e6602d92
--- /dev/null
+++ b/module/Zend/View/src/Strategy/PhpRendererStrategy.php
@@ -0,0 +1,127 @@
+renderer = $renderer;
+ }
+
+ /**
+ * Retrieve the composed renderer
+ *
+ * @return PhpRenderer
+ */
+ public function getRenderer()
+ {
+ return $this->renderer;
+ }
+
+ /**
+ * Set list of possible content placeholders
+ *
+ * @param array $contentPlaceholders
+ * @return PhpRendererStrategy
+ */
+ public function setContentPlaceholders(array $contentPlaceholders)
+ {
+ $this->contentPlaceholders = $contentPlaceholders;
+ return $this;
+ }
+
+ /**
+ * Get list of possible content placeholders
+ *
+ * @return array
+ */
+ public function getContentPlaceholders()
+ {
+ return $this->contentPlaceholders;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function attach(EventManagerInterface $events, $priority = 1)
+ {
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, [$this, 'selectRenderer'], $priority);
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, [$this, 'injectResponse'], $priority);
+ }
+
+ /**
+ * Select the PhpRenderer; typically, this will be registered last or at
+ * low priority.
+ *
+ * @param ViewEvent $e
+ * @return PhpRenderer
+ */
+ public function selectRenderer(ViewEvent $e)
+ {
+ return $this->renderer;
+ }
+
+ /**
+ * Populate the response object from the View
+ *
+ * Populates the content of the response object from the view rendering
+ * results.
+ *
+ * @param ViewEvent $e
+ * @return void
+ */
+ public function injectResponse(ViewEvent $e)
+ {
+ $renderer = $e->getRenderer();
+ $response = $e->getResponse();
+ if ($renderer !== $this->renderer || $response === null) {
+ return;
+ }
+
+ $result = $e->getResult();
+
+ // Set content
+ // If content is empty, check common placeholders to determine if they are
+ // populated, and set the content from them.
+ if (empty($result)) {
+ $placeholders = $renderer->plugin('placeholder');
+ foreach ($this->contentPlaceholders as $placeholder) {
+ if ($placeholders->containerExists($placeholder)) {
+ $result = (string) $placeholders->getContainer($placeholder);
+ break;
+ }
+ }
+ }
+ $response->setContent($result);
+ }
+}
diff --git a/module/Zend/View/src/Stream.php b/module/Zend/View/src/Stream.php
new file mode 100644
index 00000000..820d7af2
--- /dev/null
+++ b/module/Zend/View/src/Stream.php
@@ -0,0 +1,197 @@
+data = file_get_contents($path);
+
+ /**
+ * If reading the file failed, update our local stat store
+ * to reflect the real stat of the file, then return on failure
+ */
+ if ($this->data === false) {
+ $this->stat = stat($path);
+ return false;
+ }
+
+ /**
+ * Convert = ?> to long-form and to
+ *
+ */
+ $this->data = preg_replace('/\<\?\=/', "data);
+ $this->data = preg_replace('/<\?(?!xml|php)/s', 'data);
+
+ /**
+ * file_get_contents() won't update PHP's stat cache, so we grab a stat
+ * of the file to prevent additional reads should the script be
+ * requested again, which will make include() happy.
+ */
+ $this->stat = stat($path);
+
+ return true;
+ }
+
+ /**
+ * Included so that __FILE__ returns the appropriate info
+ *
+ * @return array
+ */
+ // @codingStandardsIgnoreStart
+ public function url_stat()
+ {
+ // @codingStandardsIgnoreEnd
+ return $this->stat;
+ }
+
+ /**
+ * Reads from the stream.
+ *
+ * @param int $count
+ * @return string
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_read($count)
+ {
+ // @codingStandardsIgnoreEnd
+ $ret = substr($this->data, $this->pos, $count);
+ $this->pos += strlen($ret);
+ return $ret;
+ }
+
+ /**
+ * Tells the current position in the stream.
+ *
+ * @return int
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_tell()
+ {
+ // @codingStandardsIgnoreEnd
+ return $this->pos;
+ }
+
+ /**
+ * Tells if we are at the end of the stream.
+ *
+ * @return bool
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_eof()
+ {
+ // @codingStandardsIgnoreEnd
+ return $this->pos >= strlen($this->data);
+ }
+
+ /**
+ * Stream statistics.
+ *
+ * @return array
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_stat()
+ {
+ // @codingStandardsIgnoreEnd
+ return $this->stat;
+ }
+
+ /**
+ * Seek to a specific point in the stream.
+ *
+ * @param $offset
+ * @param $whence
+ * @return bool
+ */
+ // @codingStandardsIgnoreStart
+ public function stream_seek($offset, $whence)
+ {
+ // @codingStandardsIgnoreEnd
+ switch ($whence) {
+ case SEEK_SET:
+ if ($offset < strlen($this->data) && $offset >= 0) {
+ $this->pos = $offset;
+ return true;
+ } else {
+ return false;
+ }
+ break;
+
+ case SEEK_CUR:
+ if ($offset >= 0) {
+ $this->pos += $offset;
+ return true;
+ } else {
+ return false;
+ }
+ break;
+
+ case SEEK_END:
+ if (strlen($this->data) + $offset >= 0) {
+ $this->pos = strlen($this->data) + $offset;
+ return true;
+ } else {
+ return false;
+ }
+ break;
+
+ default:
+ return false;
+ }
+ }
+}
diff --git a/module/Zend/View/src/Variables.php b/module/Zend/View/src/Variables.php
new file mode 100644
index 00000000..719fd1d1
--- /dev/null
+++ b/module/Zend/View/src/Variables.php
@@ -0,0 +1,163 @@
+setOptions($options);
+ }
+
+ /**
+ * Configure object
+ *
+ * @param array $options
+ * @return Variables
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ switch (strtolower($key)) {
+ case 'strict_vars':
+ $this->setStrictVars($value);
+ break;
+ default:
+ // Unknown options are considered variables
+ $this[$key] = $value;
+ break;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set status of "strict vars" flag
+ *
+ * @param bool $flag
+ * @return Variables
+ */
+ public function setStrictVars($flag)
+ {
+ $this->strictVars = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Are we operating with strict variables?
+ *
+ * @return bool
+ */
+ public function isStrict()
+ {
+ return $this->strictVars;
+ }
+
+ /**
+ * Assign many values at once
+ *
+ * @param array|object $spec
+ * @return Variables
+ * @throws Exception\InvalidArgumentException
+ */
+ public function assign($spec)
+ {
+ if (is_object($spec)) {
+ if (method_exists($spec, 'toArray')) {
+ $spec = $spec->toArray();
+ } else {
+ $spec = (array) $spec;
+ }
+ }
+ if (! is_array($spec)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'assign() expects either an array or an object as an argument; received "%s"',
+ gettype($spec)
+ ));
+ }
+ foreach ($spec as $key => $value) {
+ $this[$key] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the variable value
+ *
+ * If the value has not been defined, a null value will be returned; if
+ * strict vars on in place, a notice will also be raised.
+ *
+ * Otherwise, returns _escaped_ version of the value.
+ *
+ * @param mixed $key
+ * @return mixed
+ */
+ public function offsetGet($key)
+ {
+ if (! $this->offsetExists($key)) {
+ if ($this->isStrict()) {
+ trigger_error(sprintf(
+ 'View variable "%s" does not exist',
+ $key
+ ), E_USER_NOTICE);
+ }
+ return;
+ }
+
+ $return = parent::offsetGet($key);
+
+ // If we have a closure/functor, invoke it, and return its return value
+ if (is_object($return) && is_callable($return)) {
+ $return = call_user_func($return);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Clear all variables
+ *
+ * @return void
+ */
+ public function clear()
+ {
+ $this->exchangeArray([]);
+ }
+}
diff --git a/module/Zend/View/src/View.php b/module/Zend/View/src/View.php
new file mode 100644
index 00000000..45487618
--- /dev/null
+++ b/module/Zend/View/src/View.php
@@ -0,0 +1,267 @@
+request = $request;
+ return $this;
+ }
+
+ /**
+ * Set MVC response object
+ *
+ * @param Response $response
+ * @return View
+ */
+ public function setResponse(Response $response)
+ {
+ $this->response = $response;
+ return $this;
+ }
+
+ /**
+ * Get MVC request object
+ *
+ * @return null|Request
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Get MVC response object
+ *
+ * @return null|Response
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Set the event manager instance
+ *
+ * @param EventManagerInterface $events
+ * @return View
+ */
+ public function setEventManager(EventManagerInterface $events)
+ {
+ $events->setIdentifiers([
+ __CLASS__,
+ get_class($this),
+ ]);
+ $this->events = $events;
+ return $this;
+ }
+
+ /**
+ * Retrieve the event manager instance
+ *
+ * Lazy-loads a default instance if none available
+ *
+ * @return EventManagerInterface
+ */
+ public function getEventManager()
+ {
+ if (! $this->events instanceof EventManagerInterface) {
+ $this->setEventManager(new EventManager());
+ }
+ return $this->events;
+ }
+
+ /**
+ * Add a rendering strategy
+ *
+ * Expects a callable. Strategies should accept a ViewEvent object, and should
+ * return a Renderer instance if the strategy is selected.
+ *
+ * Internally, the callable provided will be subscribed to the "renderer"
+ * event, at the priority specified.
+ *
+ * @param callable $callable
+ * @param int $priority
+ * @return View
+ */
+ public function addRenderingStrategy($callable, $priority = 1)
+ {
+ $this->getEventManager()->attach(ViewEvent::EVENT_RENDERER, $callable, $priority);
+ return $this;
+ }
+
+ /**
+ * Add a response strategy
+ *
+ * Expects a callable. Strategies should accept a ViewEvent object. The return
+ * value will be ignored.
+ *
+ * Typical usages for a response strategy are to populate the Response object.
+ *
+ * Internally, the callable provided will be subscribed to the "response"
+ * event, at the priority specified.
+ *
+ * @param callable $callable
+ * @param int $priority
+ * @return View
+ */
+ public function addResponseStrategy($callable, $priority = 1)
+ {
+ $this->getEventManager()->attach(ViewEvent::EVENT_RESPONSE, $callable, $priority);
+ return $this;
+ }
+
+ /**
+ * Render the provided model.
+ *
+ * Internally, the following workflow is used:
+ *
+ * - Trigger the "renderer" event to select a renderer.
+ * - Call the selected renderer with the provided Model
+ * - Trigger the "response" event
+ *
+ * @triggers renderer(ViewEvent)
+ * @triggers response(ViewEvent)
+ * @param Model $model
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function render(Model $model)
+ {
+ $event = $this->getEvent();
+ $event->setModel($model);
+ $event->setName(ViewEvent::EVENT_RENDERER);
+ $events = $this->getEventManager();
+ $results = $events->triggerEventUntil(function ($result) {
+ return ($result instanceof Renderer);
+ }, $event);
+ $renderer = $results->last();
+ if (! $renderer instanceof Renderer) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: no renderer selected!',
+ __METHOD__
+ ));
+ }
+
+ $event->setRenderer($renderer);
+ $event->setName(ViewEvent::EVENT_RENDERER_POST);
+ $events->triggerEvent($event);
+
+ // If EVENT_RENDERER or EVENT_RENDERER_POST changed the model, make sure
+ // we use this new model instead of the current $model
+ $model = $event->getModel();
+
+ // If we have children, render them first, but only if:
+ // a) the renderer does not implement TreeRendererInterface, or
+ // b) it does, but canRenderTrees() returns false
+ if ($model->hasChildren()
+ && (! $renderer instanceof TreeRendererInterface
+ || ! $renderer->canRenderTrees())
+ ) {
+ $this->renderChildren($model);
+ }
+
+ // Reset the model, in case it has changed, and set the renderer
+ $event->setModel($model);
+ $event->setRenderer($renderer);
+
+ $rendered = $renderer->render($model);
+
+ // If this is a child model, return the rendered content; do not
+ // invoke the response strategy.
+ $options = $model->getOptions();
+ if (array_key_exists('has_parent', $options) && $options['has_parent']) {
+ return $rendered;
+ }
+
+ $event->setResult($rendered);
+ $event->setName(ViewEvent::EVENT_RESPONSE);
+
+ $events->triggerEvent($event);
+ }
+
+ /**
+ * Loop through children, rendering each
+ *
+ * @param Model $model
+ * @throws Exception\DomainException
+ * @return void
+ */
+ protected function renderChildren(Model $model)
+ {
+ foreach ($model as $child) {
+ if ($child->terminate()) {
+ throw new Exception\DomainException('Inconsistent state; child view model is marked as terminal');
+ }
+ $child->setOption('has_parent', true);
+ $result = $this->render($child);
+ $child->setOption('has_parent', null);
+ $capture = $child->captureTo();
+ if (! empty($capture)) {
+ if ($child->isAppend()) {
+ $oldResult = $model->{$capture};
+ $model->setVariable($capture, $oldResult . $result);
+ } else {
+ $model->setVariable($capture, $result);
+ }
+ }
+ }
+ }
+
+ /**
+ * Create and return ViewEvent used by render()
+ *
+ * @return ViewEvent
+ */
+ protected function getEvent()
+ {
+ $event = new ViewEvent();
+ $event->setTarget($this);
+ if (null !== ($request = $this->getRequest())) {
+ $event->setRequest($request);
+ }
+ if (null !== ($response = $this->getResponse())) {
+ $event->setResponse($response);
+ }
+ return $event;
+ }
+}
diff --git a/module/Zend/View/src/ViewEvent.php b/module/Zend/View/src/ViewEvent.php
new file mode 100644
index 00000000..5b1d135e
--- /dev/null
+++ b/module/Zend/View/src/ViewEvent.php
@@ -0,0 +1,258 @@
+model = $model;
+ return $this;
+ }
+
+ /**
+ * Set the MVC request object
+ *
+ * @param Request $request
+ * @return ViewEvent
+ */
+ public function setRequest(Request $request)
+ {
+ $this->request = $request;
+ return $this;
+ }
+
+ /**
+ * Set the MVC response object
+ *
+ * @param Response $response
+ * @return ViewEvent
+ */
+ public function setResponse(Response $response)
+ {
+ $this->response = $response;
+ return $this;
+ }
+
+ /**
+ * Set result of rendering
+ *
+ * @param mixed $result
+ * @return ViewEvent
+ */
+ public function setResult($result)
+ {
+ $this->result = $result;
+ return $this;
+ }
+
+ /**
+ * Retrieve the view model
+ *
+ * @return null|Model
+ */
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+ /**
+ * Set value for renderer
+ *
+ * @param Renderer $renderer
+ * @return ViewEvent
+ */
+ public function setRenderer(Renderer $renderer)
+ {
+ $this->renderer = $renderer;
+ return $this;
+ }
+
+ /**
+ * Get value for renderer
+ *
+ * @return null|Renderer
+ */
+ public function getRenderer()
+ {
+ return $this->renderer;
+ }
+
+ /**
+ * Retrieve the MVC request object
+ *
+ * @return null|Request
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Retrieve the MVC response object
+ *
+ * @return null|Response
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Retrieve the result of rendering
+ *
+ * @return mixed
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ /**
+ * Get event parameter
+ *
+ * @param string $name
+ * @param mixed $default
+ * @return mixed
+ */
+ public function getParam($name, $default = null)
+ {
+ switch ($name) {
+ case 'model':
+ return $this->getModel();
+ case 'renderer':
+ return $this->getRenderer();
+ case 'request':
+ return $this->getRequest();
+ case 'response':
+ return $this->getResponse();
+ case 'result':
+ return $this->getResult();
+ default:
+ return parent::getParam($name, $default);
+ }
+ }
+
+ /**
+ * Get all event parameters
+ *
+ * @return array|\ArrayAccess
+ */
+ public function getParams()
+ {
+ $params = parent::getParams();
+ $params['model'] = $this->getModel();
+ $params['renderer'] = $this->getRenderer();
+ $params['request'] = $this->getRequest();
+ $params['response'] = $this->getResponse();
+ $params['result'] = $this->getResult();
+ return $params;
+ }
+
+ /**
+ * Set event parameters
+ *
+ * @param array|object|ArrayAccess $params
+ * @return ViewEvent
+ */
+ public function setParams($params)
+ {
+ parent::setParams($params);
+ if (! is_array($params) && ! $params instanceof ArrayAccess) {
+ return $this;
+ }
+
+ foreach (['model', 'renderer', 'request', 'response', 'result'] as $param) {
+ if (isset($params[$param])) {
+ $method = 'set' . $param;
+ $this->$method($params[$param]);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set an individual event parameter
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return ViewEvent
+ */
+ public function setParam($name, $value)
+ {
+ switch ($name) {
+ case 'model':
+ $this->setModel($value);
+ break;
+ case 'renderer':
+ $this->setRenderer($value);
+ break;
+ case 'request':
+ $this->setRequest($value);
+ break;
+ case 'response':
+ $this->setResponse($value);
+ break;
+ case 'result':
+ $this->setResult($value);
+ break;
+ default:
+ parent::setParam($name, $value);
+ break;
+ }
+ return $this;
+ }
+}