From 604110e7379aa4cead48ec91c22b501ba49b7e18 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Wed, 1 Mar 2023 12:38:59 +0100 Subject: [PATCH] Initial migration We will use and maintain the extension for one of our customers. We add our basic setup. We add tests. We refactor code. We use newer APIs of TYPO3. We will integrate Matomo A/B Testing afterwards as next step. --- .gitattributes | 13 + .github/workflows/ci.yaml | 133 ++ .gitignore | 3 + .php-cs-fixer.dist.php | 62 + Classes/Cookie.php | 81 + Classes/DeviceDetector/DeviceDetector.php | 263 --- Classes/DeviceDetector/Parser/Bot.php | 72 - .../DeviceDetector/Parser/ParserAbstract.php | 269 --- Classes/DeviceDetector/Yaml/Inline.php | 567 ------ .../DeviceDetector/Yaml/ParseException.php | 141 -- Classes/DeviceDetector/Yaml/Parser.php | 777 -------- Classes/DeviceDetector/Yaml/Unescaper.php | 141 -- Classes/Helper.php | 148 -- Classes/Middleware/SetCookie.php | 65 + Classes/PageRepository.php | 64 + Classes/Switcher.php | 149 ++ Classes/TCA/VariantFilter.php | 51 + Configuration/RequestMiddlewares.php | 15 + Configuration/Services.php | 23 + Configuration/TCA/Overrides/pages.php | 120 +- Configuration/YAML/bots.yml | 1572 ----------------- .../BestPractice/Index.rst | 16 - Documentation/AdministratorManual/Index.rst | 23 - .../Installation/Index.rst | 42 - Documentation/Images/demo.gif | Bin 134089 -> 0 bytes Documentation/Images/page_settings.png | Bin 21187 -> 0 bytes Documentation/Includes.txt | 29 - Documentation/Index.rst | 54 - Documentation/Introduction/About/Index.rst | 23 - Documentation/Introduction/Index.rst | 21 - Documentation/Introduction/Support/Index.rst | 26 - Documentation/Introduction/Thanks/Index.rst | 32 - Documentation/Settings.cfg | 48 - Documentation/Settings.yml | 30 - README.md | 59 +- .../Private/Language/de.locallang_db.xlf | 105 +- Resources/Private/Language/locallang_db.xlf | 83 +- Tests/Fixtures/BasicDatabase.csv | 10 + Tests/Fixtures/FrontendRendering.typoscript | 16 + Tests/Fixtures/Sites/default/config.yaml | 32 + Tests/Functional/FrontendRenderingTest.php | 309 ++++ composer.json | 73 +- ext_emconf.php | 37 - ext_localconf.php | 3 +- ext_tables.php | 3 - ext_tables.sql | 10 +- patches/testing-framework-cookies.patch | 19 + phpstan.neon | 9 + phpunit.xml.dist | 35 + shell.nix | 83 + 50 files changed, 1396 insertions(+), 4563 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 .php-cs-fixer.dist.php create mode 100644 Classes/Cookie.php delete mode 100644 Classes/DeviceDetector/DeviceDetector.php delete mode 100644 Classes/DeviceDetector/Parser/Bot.php delete mode 100644 Classes/DeviceDetector/Parser/ParserAbstract.php delete mode 100644 Classes/DeviceDetector/Yaml/Inline.php delete mode 100644 Classes/DeviceDetector/Yaml/ParseException.php delete mode 100644 Classes/DeviceDetector/Yaml/Parser.php delete mode 100644 Classes/DeviceDetector/Yaml/Unescaper.php delete mode 100644 Classes/Helper.php create mode 100644 Classes/Middleware/SetCookie.php create mode 100644 Classes/PageRepository.php create mode 100644 Classes/Switcher.php create mode 100644 Classes/TCA/VariantFilter.php create mode 100644 Configuration/RequestMiddlewares.php create mode 100644 Configuration/Services.php delete mode 100644 Configuration/YAML/bots.yml delete mode 100644 Documentation/AdministratorManual/BestPractice/Index.rst delete mode 100644 Documentation/AdministratorManual/Index.rst delete mode 100644 Documentation/AdministratorManual/Installation/Index.rst delete mode 100644 Documentation/Images/demo.gif delete mode 100644 Documentation/Images/page_settings.png delete mode 100644 Documentation/Includes.txt delete mode 100644 Documentation/Index.rst delete mode 100644 Documentation/Introduction/About/Index.rst delete mode 100644 Documentation/Introduction/Index.rst delete mode 100644 Documentation/Introduction/Support/Index.rst delete mode 100644 Documentation/Introduction/Thanks/Index.rst delete mode 100644 Documentation/Settings.cfg delete mode 100644 Documentation/Settings.yml create mode 100644 Tests/Fixtures/BasicDatabase.csv create mode 100644 Tests/Fixtures/FrontendRendering.typoscript create mode 100644 Tests/Fixtures/Sites/default/config.yaml create mode 100644 Tests/Functional/FrontendRenderingTest.php delete mode 100644 ext_emconf.php delete mode 100644 ext_tables.php create mode 100644 patches/testing-framework-cookies.patch create mode 100644 phpstan.neon create mode 100644 phpunit.xml.dist create mode 100644 shell.nix diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..07889f5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +Tests export-ignore +patches export-ignore +.github export-ignore + +.gitattributes export-ignore +.gitignore export-ignore + +rector export-ignore +.php-cs-fixer.dist.php export-ignore +phpstan.neon export-ignore +phpunit.xml.dist export-ignore + +shell.nix export-ignore diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..3496821 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,133 @@ +name: CI + +on: + - pull_request + +jobs: + check-composer: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: cachix/install-nix-action@v17 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Validate composer.json + run: nix-shell --pure --run project-validate-composer + + php-linting: + runs-on: ubuntu-20.04 + strategy: + matrix: + php-version: + - 7.4 + - 8.0 + - 8.1 + - 8.2 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php-version }}" + coverage: none + tools: composer:v2 + + - name: PHP lint + run: "find *.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l" + + xml-linting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: cachix/install-nix-action@v17 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Validate XML + run: nix-shell --pure --run project-validate-xml + + coding-guideline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: cachix/install-nix-action@v17 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Check Coding Guideline + run: nix-shell --pure --run project-coding-guideline + + code-quality: + runs-on: ubuntu-20.04 + strategy: + matrix: + include: + - php-version: '7.4' + - php-version: '8.0' + - php-version: '8.1' + - php-version: '8.2' + steps: + - uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php-version }}" + coverage: none + tools: composer:v2 + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: Code Quality (by PHPStan) + run: ./vendor/bin/phpstan analyse + + tests: + runs-on: ubuntu-20.04 + strategy: + matrix: + include: + - php-version: '7.4' + - php-version: '8.0' + - php-version: '8.1' + - php-version: '8.2' + steps: + - uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php-version }}" + coverage: none + tools: composer:v2 + + - name: Setup MySQL + uses: mirromutth/mysql-action@v1.1 + with: + mysql version: '8' + mysql database: 'typo3' + mysql root password: 'root' + + - name: Wait for MySQL + run: | + while ! mysqladmin ping --host=127.0.0.1 --password=root --silent; do + sleep 1 + done + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: PHPUnit Tests + env: + typo3DatabaseDriver: "pdo_mysql" + typo3DatabaseName: "typo3" + typo3DatabaseHost: "127.0.0.1" + typo3DatabaseUsername: "root" + typo3DatabasePassword: "root" + run: ./vendor/bin/phpunit --testdox diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b32ebe --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.Build/ +/vendor/ +/composer.lock diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..80e0228 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,62 @@ +ignoreVCSIgnored(true) + ->in(realpath(__DIR__)); + +return (new \PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@DoctrineAnnotation' => true, + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'blank_line_after_opening_tag' => true, + 'braces' => ['allow_single_line_closure' => true], + 'cast_spaces' => ['space' => 'none'], + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'none'], + 'dir_constant' => true, + 'function_to_constant' => ['functions' => ['get_called_class', 'get_class', 'get_class_this', 'php_sapi_name', 'phpversion', 'pi']], + 'function_typehint_space' => true, + 'lowercase_cast' => true, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'modernize_strpos' => true, + 'modernize_types_casting' => true, + 'native_function_casing' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_null_property_initialization' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_superfluous_elseif' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_whitespace_in_blank_line' => true, + 'ordered_imports' => true, + 'php_unit_construct' => ['assertions' => ['assertEquals', 'assertSame', 'assertNotEquals', 'assertNotSame']], + 'php_unit_mock_short_will_return' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'phpdoc_no_access' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'return_type_declaration' => ['space_before' => 'none'], + 'single_quote' => true, + 'single_line_comment_style' => ['comment_types' => ['hash']], + 'single_trait_insert_per_statement' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays']], + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], + ]) + ->setFinder($finder); diff --git a/Classes/Cookie.php b/Classes/Cookie.php new file mode 100644 index 0000000..209de45 --- /dev/null +++ b/Classes/Cookie.php @@ -0,0 +1,81 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace WerkraumMedia\ABTest; + +use TYPO3\CMS\Core\SingletonInterface; + +class Cookie implements SingletonInterface +{ + /** + * @var int + */ + private $requestedPageUid = 0; + + /** + * @var int + */ + private $actualPageUid = 0; + + /** + * @var int + */ + private $lifetime = 604800; + + public function setRequestedPage(int $uid): void + { + $this->requestedPageUid = $uid; + } + + public function setActualPage(int $uid): void + { + $this->actualPageUid = $uid; + } + + public function setLifetime(int $seconds): void + { + if ($seconds > 0) { + $this->lifetime = $seconds; + } + } + + public function getRequestedPage(): int + { + return $this->requestedPageUid; + } + + public function getActualPage(): int + { + return $this->actualPageUid; + } + + public function getLifetime(): int + { + return $this->lifetime; + } + + public function needsToBeSet(): bool + { + return $this->actualPageUid > 0; + } +} diff --git a/Classes/DeviceDetector/DeviceDetector.php b/Classes/DeviceDetector/DeviceDetector.php deleted file mode 100644 index d76b473..0000000 --- a/Classes/DeviceDetector/DeviceDetector.php +++ /dev/null @@ -1,263 +0,0 @@ -setUserAgent($userAgent); - } - - } - - /** - * Sets the useragent to be parsed - * - * @param string $userAgent - */ - public function setUserAgent($userAgent) - { - if ($this->userAgent != $userAgent) { - $this->reset(); - } - $this->userAgent = $userAgent; - } - - protected function reset() - { - $this->bot = null; - $this->parsed = false; - } - - - - /** - * Sets whether to discard additional bot information - * If information is discarded it's only possible check whether UA was detected as bot or not. - * (Discarding information speeds up the detection a bit) - * - * @param bool $discard - */ - public function discardBotInformation($discard = true) - { - $this->discardBotInformation = $discard; - } - - /** - * Sets whether to skip bot detection. - * It is needed if we want bots to be processed as a simple clients. So we can detect if it is mobile client, - * or desktop, or enything else. By default all this information is not retrieved for the bots. - * - * @param bool $skip - */ - public function skipBotDetection($skip = true) - { - $this->skipBotDetection = $skip; - } - - /** - * Returns if the parsed UA was identified as a Bot - * - * @see bots.yml for a list of detected bots - * - * @return bool - */ - public function isBot() - { - return !empty($this->bot); - } - - - /** - * Returns the user agent that is set to be parsed - * - * @return string - */ - public function getUserAgent() - { - return $this->userAgent; - } - - /** - * Returns the bot extracted from the parsed UA - * - * @return array - */ - public function getBot() - { - return $this->bot; - } - - /** - * Returns true, if userAgent was already parsed with parse() - * - * @return bool - */ - public function isParsed() - { - return $this->parsed; - } - - /** - * Triggers the parsing of the current user agent - * @throws \Exception - */ - public function parse() - { - if ($this->isParsed()) { - return; - } - - $this->parsed = true; - - // skip parsing for empty useragents or those not containing any letter - if (empty($this->userAgent) || !preg_match('/([a-z])/i', $this->userAgent)) { - return; - } - - $this->parseBot(); - if ($this->isBot()) { - return; - } - - } - - /** - * Parses the UA for bot information using the Bot parser - * @throws \Exception - * @return void - */ - protected function parseBot() - { - if ($this->skipBotDetection) { - $this->bot = false; - return; - } - - $botParser = new Bot(); - $botParser->setUserAgent($this->getUserAgent()); - $botParser->setYamlParser($this->getYamlParser()); - if ($this->discardBotInformation) { - $botParser->discardDetails(); - } - $this->bot = $botParser->parse(); - - } - - - - protected function matchUserAgent($regex) - { - $regex = '/(?:^|[^A-Z_-])(?:' . str_replace('/', '\/', $regex) . ')/i'; - - if (preg_match($regex, $this->userAgent, $matches)) { - return $matches; - } - - return false; - } - - - - /** - * Sets the Yaml Parser class - * - * @param YamlParser - * @throws \Exception - */ - public function setYamlParser($yamlParser) - { - if ($yamlParser instanceof YamlParser) { - $this->yamlParser = $yamlParser; - return; - } - - throw new \Exception('Yaml Parser not supported'); - } - - /** - * Returns Yaml Parser object - * - * @return YamlParser - */ - public function getYamlParser() - { - if (!empty($this->yamlParser)) { - return $this->yamlParser; - } - - return new Spyc(); - } -} diff --git a/Classes/DeviceDetector/Parser/Bot.php b/Classes/DeviceDetector/Parser/Bot.php deleted file mode 100644 index a2da5b6..0000000 --- a/Classes/DeviceDetector/Parser/Bot.php +++ /dev/null @@ -1,72 +0,0 @@ -discardDetails = true; - } - - /** - * Parses the current UA and checks whether it contains bot information - * - * @see bots.yml for list of detected bots - * - * Step 1: Build a big regex containing all regexes and match UA against it - * -> If no matches found: return - * -> Otherwise: - * Step 2: Walk through the list of regexes in bots.yml and try to match every one - * -> Return the matched data - * - * If $discardDetails is set to TRUE, the Step 2 will be skipped - * $bot will be set to TRUE instead - * - * NOTE: Doing the big match before matching every single regex speeds up the detection - */ - public function parse() - { - $result = null; - - if ($this->preMatchOverall()) { - foreach ($this->getRegexes() as $regex) { - $matches = $this->matchUserAgent($regex['regex']); - - if ($matches) { - if ($this->discardDetails) { - $result = true; - break; - } - - unset($regex['regex']); - $result = $regex; - break; - } - } - } - - return $result; - } -} diff --git a/Classes/DeviceDetector/Parser/ParserAbstract.php b/Classes/DeviceDetector/Parser/ParserAbstract.php deleted file mode 100644 index 8fa98b5..0000000 --- a/Classes/DeviceDetector/Parser/ParserAbstract.php +++ /dev/null @@ -1,269 +0,0 @@ -setUserAgent($ua); - } - - /** - * Set how DeviceDetector should return versions - * @param int|null $type Any of the VERSION_TRUNCATION_* constants - */ - public static function setVersionTruncation($type) - { - if (in_array($type, array(self::VERSION_TRUNCATION_BUILD, - self::VERSION_TRUNCATION_NONE, - self::VERSION_TRUNCATION_MAJOR, - self::VERSION_TRUNCATION_MINOR, - self::VERSION_TRUNCATION_PATCH))) { - self::$maxMinorParts = $type; - } - } - - /** - * Sets the user agent to parse - * - * @param string $ua user agent - */ - public function setUserAgent($ua) - { - $this->userAgent = $ua; - } - - /** - * Returns the internal name of the parser - * - * @return string - */ - public function getName() - { - return $this->parserName; - } - - /** - * Returns the result of the parsed yml file defined in $fixtureFile - * - * @return array - */ - protected function getRegexes() - { - if (empty($this->regexList)) { - $this->regexList = $this->getYamlParser()->parse( - file_get_contents(GeneralUtility::getFileAbsFileName('EXT:abtest2/Configuration/YAML/'.$this->fixtureFile)) - ); - } - return $this->regexList; - } - - /** - * @return string - */ - protected function getRegexesDirectory() - { - return dirname(__DIR__); - } - - /** - * Matches the useragent against the given regex - * - * @param $regex - * @return array|bool - */ - protected function matchUserAgent($regex) - { - // only match if useragent begins with given regex or there is no letter before it - $regex = '/(?:^|[^A-Z0-9\-_]|[^A-Z0-9\-]_|sprd-)(?:' . str_replace('/', '\/', $regex) . ')/i'; - - if (preg_match($regex, $this->userAgent, $matches)) { - return $matches; - } - - return false; - } - - /** - * @param string $item - * @param array $matches - * @return string type - */ - protected function buildByMatch($item, $matches) - { - for ($nb=1;$nb<=3;$nb++) { - if (strpos($item, '$' . $nb) === false) { - continue; - } - - $replace = isset($matches[$nb]) ? $matches[$nb] : ''; - $item = trim(str_replace('$' . $nb, $replace, $item)); - } - return $item; - } - - /** - * Builds the version with the given $versionString and $matches - * - * Example: - * $versionString = 'v$2' - * $matches = array('version_1_0_1', '1_0_1') - * return value would be v1.0.1 - * - * @param $versionString - * @param $matches - * @return mixed|string - */ - protected function buildVersion($versionString, $matches) - { - $versionString = $this->buildByMatch($versionString, $matches); - $versionString = str_replace('_', '.', $versionString); - if (null !== self::$maxMinorParts && substr_count($versionString, '.') > self::$maxMinorParts) { - $versionParts = explode('.', $versionString); - $versionParts = array_slice($versionParts, 0, 1+self::$maxMinorParts); - $versionString = implode('.', $versionParts); - } - return trim($versionString, ' .'); - } - - /** - * Tests the useragent against a combination of all regexes - * - * All regexes returned by getRegexes() will be reversed and concated with '|' - * Afterwards the big regex will be tested against the user agent - * - * Method can be used to speed up detections by making a big check before doing checks for every single regex - * - * @return bool - */ - protected function preMatchOverall() - { - $regexes = $this->getRegexes(); - - static $overAllMatch; - - if (empty($overAllMatch)) { - // reverse all regexes, so we have the generic one first, which already matches most patterns - $overAllMatch = array_reduce(array_reverse($regexes), function ($val1, $val2) { - if (!empty($val1)) { - return $val1.'|'.$val2['regex']; - } else { - return $val2['regex']; - } - }); - } - - return $this->matchUserAgent($overAllMatch); - } - - - - /** - * Sets the YamlParser class - * - * @param Parser - */ - public function setYamlParser($yamlParser) - { - $this->yamlParser = $yamlParser; - } - - /** - * Returns Parser object - * - * @return Parser - */ - public function getYamlParser() - { - return $this->yamlParser; - } -} diff --git a/Classes/DeviceDetector/Yaml/Inline.php b/Classes/DeviceDetector/Yaml/Inline.php deleted file mode 100644 index 988ad3b..0000000 --- a/Classes/DeviceDetector/Yaml/Inline.php +++ /dev/null @@ -1,567 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace WapplerSystems\ABTest2\DeviceDetector\Yaml; - -use Symfony\Component\Yaml\Exception\DumpException; - -/** - * Inline implements a YAML parser/dumper for the YAML inline syntax. - * - * @author Fabien Potencier - */ -class Inline -{ - const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')'; - - private static $exceptionOnInvalidType = false; - private static $objectSupport = false; - private static $objectForMap = false; - - /** - * Converts a YAML string to a PHP array. - * - * @param string $value A YAML string - * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise - * @param bool $objectSupport true if object support is enabled, false otherwise - * @param bool $objectForMap true if maps should return a stdClass instead of array() - * @param array $references Mapping of variable names to values - * - * @return array A PHP array representing the YAML string - * - * @throws ParseException - */ - public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array()) - { - self::$exceptionOnInvalidType = $exceptionOnInvalidType; - self::$objectSupport = $objectSupport; - self::$objectForMap = $objectForMap; - - $value = trim($value); - - if ('' === $value) { - return ''; - } - - if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { - $mbEncoding = mb_internal_encoding(); - mb_internal_encoding('ASCII'); - } - - $i = 0; - switch ($value[0]) { - case '[': - $result = self::parseSequence($value, $i, $references); - ++$i; - break; - case '{': - $result = self::parseMapping($value, $i, $references); - ++$i; - break; - default: - $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references); - } - - // some comments are allowed at the end - if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) { - throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i))); - } - - if (isset($mbEncoding)) { - mb_internal_encoding($mbEncoding); - } - - return $result; - } - - /** - * Dumps a given PHP variable to a YAML string. - * - * @param mixed $value The PHP variable to convert - * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise - * @param bool $objectSupport true if object support is enabled, false otherwise - * - * @return string The YAML string representing the PHP array - * - * @throws DumpException When trying to dump PHP resource - */ - public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false) - { - switch (true) { - case is_resource($value): - if ($exceptionOnInvalidType) { - throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); - } - - return 'null'; - case is_object($value): - if ($objectSupport) { - return '!php/object:'.serialize($value); - } - - if ($exceptionOnInvalidType) { - throw new DumpException('Object support when dumping a YAML file has been disabled.'); - } - - return 'null'; - case is_array($value): - return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport); - case null === $value: - return 'null'; - case true === $value: - return 'true'; - case false === $value: - return 'false'; - case ctype_digit($value): - return is_string($value) ? "'$value'" : (int) $value; - case is_numeric($value): - $locale = setlocale(LC_NUMERIC, 0); - if (false !== $locale) { - setlocale(LC_NUMERIC, 'C'); - } - if (is_float($value)) { - $repr = (string) $value; - if (is_infinite($value)) { - $repr = str_ireplace('INF', '.Inf', $repr); - } elseif (floor($value) == $value && $repr == $value) { - // Preserve float data type since storing a whole number will result in integer value. - $repr = '!!float '.$repr; - } - } else { - $repr = is_string($value) ? "'$value'" : (string) $value; - } - if (false !== $locale) { - setlocale(LC_NUMERIC, $locale); - } - - return $repr; - case '' == $value: - return "''"; - case Escaper::requiresDoubleQuoting($value): - return Escaper::escapeWithDoubleQuotes($value); - case Escaper::requiresSingleQuoting($value): - case preg_match(self::getHexRegex(), $value): - case preg_match(self::getTimestampRegex(), $value): - return Escaper::escapeWithSingleQuotes($value); - default: - return $value; - } - } - - /** - * Dumps a PHP array to a YAML string. - * - * @param array $value The PHP array to dump - * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise - * @param bool $objectSupport true if object support is enabled, false otherwise - * - * @return string The YAML string representing the PHP array - */ - private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport) - { - // array - $keys = array_keys($value); - $keysCount = count($keys); - if ((1 === $keysCount && '0' == $keys[0]) - || ($keysCount > 1 && array_reduce($keys, function ($v, $w) { return (int) $v + $w; }, 0) === $keysCount * ($keysCount - 1) / 2) - ) { - $output = array(); - foreach ($value as $val) { - $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport); - } - - return sprintf('[%s]', implode(', ', $output)); - } - - // mapping - $output = array(); - foreach ($value as $key => $val) { - $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport)); - } - - return sprintf('{ %s }', implode(', ', $output)); - } - - /** - * Parses a scalar to a YAML string. - * - * @param string $scalar - * @param string $delimiters - * @param array $stringDelimiters - * @param int &$i - * @param bool $evaluate - * @param array $references - * - * @return string A YAML string - * - * @throws ParseException When malformed inline YAML string is parsed - * - * @internal - */ - public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array()) - { - if (in_array($scalar[$i], $stringDelimiters)) { - // quoted scalar - $output = self::parseQuotedScalar($scalar, $i); - - if (null !== $delimiters) { - $tmp = ltrim(substr($scalar, $i), ' '); - if (!in_array($tmp[0], $delimiters)) { - throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i))); - } - } - } else { - // "normal" string - if (!$delimiters) { - $output = substr($scalar, $i); - $i += strlen($output); - - // remove comments - if (preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) { - $output = substr($output, 0, $match[0][1]); - } - } elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { - $output = $match[1]; - $i += strlen($output); - } else { - throw new ParseException(sprintf('Malformed inline YAML string (%s).', $scalar)); - } - - // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) - if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) { - throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0])); - } - - if ($evaluate) { - $output = self::evaluateScalar($output, $references); - } - } - - return $output; - } - - /** - * Parses a quoted scalar to YAML. - * - * @param string $scalar - * @param int &$i - * - * @return string A YAML string - * - * @throws ParseException When malformed inline YAML string is parsed - */ - private static function parseQuotedScalar($scalar, &$i) - { - if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { - throw new ParseException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i))); - } - - $output = substr($match[0], 1, strlen($match[0]) - 2); - - $unescaper = new Unescaper(); - if ('"' == $scalar[$i]) { - $output = $unescaper->unescapeDoubleQuotedString($output); - } else { - $output = $unescaper->unescapeSingleQuotedString($output); - } - - $i += strlen($match[0]); - - return $output; - } - - /** - * Parses a sequence to a YAML string. - * - * @param string $sequence - * @param int &$i - * @param array $references - * - * @return string A YAML string - * - * @throws ParseException When malformed inline YAML string is parsed - */ - private static function parseSequence($sequence, &$i = 0, $references = array()) - { - $output = array(); - $len = strlen($sequence); - ++$i; - - // [foo, bar, ...] - while ($i < $len) { - switch ($sequence[$i]) { - case '[': - // nested sequence - $output[] = self::parseSequence($sequence, $i, $references); - break; - case '{': - // nested mapping - $output[] = self::parseMapping($sequence, $i, $references); - break; - case ']': - return $output; - case ',': - case ' ': - break; - default: - $isQuoted = in_array($sequence[$i], array('"', "'")); - $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references); - - // the value can be an array if a reference has been resolved to an array var - if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) { - // embedded mapping? - try { - $pos = 0; - $value = self::parseMapping('{'.$value.'}', $pos, $references); - } catch (\InvalidArgumentException $e) { - // no, it's not - } - } - - $output[] = $value; - - --$i; - } - - ++$i; - } - - throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence)); - } - - /** - * Parses a mapping to a YAML string. - * - * @param string $mapping - * @param int &$i - * @param array $references - * - * @return string A YAML string - * - * @throws ParseException When malformed inline YAML string is parsed - */ - private static function parseMapping($mapping, &$i = 0, $references = array()) - { - $output = array(); - $len = strlen($mapping); - ++$i; - - // {foo: bar, bar:foo, ...} - while ($i < $len) { - switch ($mapping[$i]) { - case ' ': - case ',': - ++$i; - continue 2; - case '}': - if (self::$objectForMap) { - return (object) $output; - } - - return $output; - } - - // key - $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); - - // value - $done = false; - - while ($i < $len) { - switch ($mapping[$i]) { - case '[': - // nested sequence - $value = self::parseSequence($mapping, $i, $references); - // Spec: Keys MUST be unique; first one wins. - // Parser cannot abort this mapping earlier, since lines - // are processed sequentially. - if (!isset($output[$key])) { - $output[$key] = $value; - } - $done = true; - break; - case '{': - // nested mapping - $value = self::parseMapping($mapping, $i, $references); - // Spec: Keys MUST be unique; first one wins. - // Parser cannot abort this mapping earlier, since lines - // are processed sequentially. - if (!isset($output[$key])) { - $output[$key] = $value; - } - $done = true; - break; - case ':': - case ' ': - break; - default: - $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references); - // Spec: Keys MUST be unique; first one wins. - // Parser cannot abort this mapping earlier, since lines - // are processed sequentially. - if (!isset($output[$key])) { - $output[$key] = $value; - } - $done = true; - --$i; - } - - ++$i; - - if ($done) { - continue 2; - } - } - } - - throw new ParseException(sprintf('Malformed inline YAML string %s', $mapping)); - } - - /** - * Evaluates scalars and replaces magic values. - * - * @param string $scalar - * @param array $references - * - * @return string A YAML string - * - * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved - */ - private static function evaluateScalar($scalar, $references = array()) - { - $scalar = trim($scalar); - $scalarLower = strtolower($scalar); - - if (0 === strpos($scalar, '*')) { - if (false !== $pos = strpos($scalar, '#')) { - $value = substr($scalar, 1, $pos - 2); - } else { - $value = substr($scalar, 1); - } - - // an unquoted * - if (false === $value || '' === $value) { - throw new ParseException('A reference must contain at least one character.'); - } - - if (!array_key_exists($value, $references)) { - throw new ParseException(sprintf('Reference "%s" does not exist.', $value)); - } - - return $references[$value]; - } - - switch (true) { - case 'null' === $scalarLower: - case '' === $scalar: - case '~' === $scalar: - return; - case 'true' === $scalarLower: - return true; - case 'false' === $scalarLower: - return false; - // Optimise for returning strings. - case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]): - switch (true) { - case 0 === strpos($scalar, '!str'): - return (string) substr($scalar, 5); - case 0 === strpos($scalar, '! '): - return (int) self::parseScalar(substr($scalar, 2)); - case 0 === strpos($scalar, '!php/object:'): - if (self::$objectSupport) { - return unserialize(substr($scalar, 12)); - } - - if (self::$exceptionOnInvalidType) { - throw new ParseException('Object support when parsing a YAML file has been disabled.'); - } - - return; - case 0 === strpos($scalar, '!!php/object:'): - if (self::$objectSupport) { - return unserialize(substr($scalar, 13)); - } - - if (self::$exceptionOnInvalidType) { - throw new ParseException('Object support when parsing a YAML file has been disabled.'); - } - - return; - case 0 === strpos($scalar, '!!float '): - return (float) substr($scalar, 8); - case ctype_digit($scalar): - $raw = $scalar; - $cast = (int) $scalar; - - return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); - case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): - $raw = $scalar; - $cast = (int) $scalar; - - return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw); - case is_numeric($scalar): - case preg_match(self::getHexRegex(), $scalar): - return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; - case '.inf' === $scalarLower: - case '.nan' === $scalarLower: - return -log(0); - case '-.inf' === $scalarLower: - return log(0); - case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): - return (float) str_replace(',', '', $scalar); - case preg_match(self::getTimestampRegex(), $scalar): - $timeZone = date_default_timezone_get(); - date_default_timezone_set('UTC'); - $time = strtotime($scalar); - date_default_timezone_set($timeZone); - - return $time; - } - default: - return (string) $scalar; - } - } - - /** - * Gets a regex that matches a YAML date. - * - * @return string The regular expression - * - * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 - */ - private static function getTimestampRegex() - { - return <<[0-9][0-9][0-9][0-9]) - -(?P[0-9][0-9]?) - -(?P[0-9][0-9]?) - (?:(?:[Tt]|[ \t]+) - (?P[0-9][0-9]?) - :(?P[0-9][0-9]) - :(?P[0-9][0-9]) - (?:\.(?P[0-9]*))? - (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) - (?::(?P[0-9][0-9]))?))?)? - $~x -EOF; - } - - /** - * Gets a regex that matches a YAML number in hexadecimal notation. - * - * @return string - */ - private static function getHexRegex() - { - return '~^0x[0-9a-f]++$~i'; - } -} diff --git a/Classes/DeviceDetector/Yaml/ParseException.php b/Classes/DeviceDetector/Yaml/ParseException.php deleted file mode 100644 index 8a230c3..0000000 --- a/Classes/DeviceDetector/Yaml/ParseException.php +++ /dev/null @@ -1,141 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace WapplerSystems\ABTest2\DeviceDetector\Yaml; - -/** - * Exception class thrown when an error occurs during parsing. - * - * @author Fabien Potencier - */ -class ParseException extends \RuntimeException -{ - private $parsedFile; - private $parsedLine; - private $snippet; - private $rawMessage; - - /** - * Constructor. - * - * @param string $message The error message - * @param int $parsedLine The line where the error occurred - * @param int $snippet The snippet of code near the problem - * @param string $parsedFile The file name where the error occurred - * @param \Exception $previous The previous exception - */ - public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null) - { - $this->parsedFile = $parsedFile; - $this->parsedLine = $parsedLine; - $this->snippet = $snippet; - $this->rawMessage = $message; - - $this->updateRepr(); - - parent::__construct($this->message, 0, $previous); - } - - /** - * Gets the snippet of code near the error. - * - * @return string The snippet of code - */ - public function getSnippet() - { - return $this->snippet; - } - - /** - * Sets the snippet of code near the error. - * - * @param string $snippet The code snippet - */ - public function setSnippet($snippet) - { - $this->snippet = $snippet; - - $this->updateRepr(); - } - - /** - * Gets the filename where the error occurred. - * - * This method returns null if a string is parsed. - * - * @return string The filename - */ - public function getParsedFile() - { - return $this->parsedFile; - } - - /** - * Sets the filename where the error occurred. - * - * @param string $parsedFile The filename - */ - public function setParsedFile($parsedFile) - { - $this->parsedFile = $parsedFile; - - $this->updateRepr(); - } - - /** - * Gets the line where the error occurred. - * - * @return int The file line - */ - public function getParsedLine() - { - return $this->parsedLine; - } - - /** - * Sets the line where the error occurred. - * - * @param int $parsedLine The file line - */ - public function setParsedLine($parsedLine) - { - $this->parsedLine = $parsedLine; - - $this->updateRepr(); - } - - private function updateRepr() - { - $this->message = $this->rawMessage; - - $dot = false; - if ('.' === substr($this->message, -1)) { - $this->message = substr($this->message, 0, -1); - $dot = true; - } - - if (null !== $this->parsedFile) { - $this->message .= sprintf(' in %s', json_encode($this->parsedFile, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); - } - - if ($this->parsedLine >= 0) { - $this->message .= sprintf(' at line %d', $this->parsedLine); - } - - if ($this->snippet) { - $this->message .= sprintf(' (near "%s")', $this->snippet); - } - - if ($dot) { - $this->message .= '.'; - } - } -} diff --git a/Classes/DeviceDetector/Yaml/Parser.php b/Classes/DeviceDetector/Yaml/Parser.php deleted file mode 100644 index 304d47e..0000000 --- a/Classes/DeviceDetector/Yaml/Parser.php +++ /dev/null @@ -1,777 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace WapplerSystems\ABTest2\DeviceDetector\Yaml; - - -/** - * Parser parses YAML strings to convert them to PHP arrays. - * - * @author Fabien Potencier - */ -class Parser -{ - const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; - - private $offset = 0; - private $lines = array(); - private $currentLineNb = -1; - private $currentLine = ''; - private $refs = array(); - - /** - * Constructor. - * - * @param int $offset The offset of YAML document (used for line numbers in error messages) - */ - public function __construct($offset = 0) - { - $this->offset = $offset; - } - - /** - * Parses a YAML string to a PHP value. - * - * @param string $value A YAML string - * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise - * @param bool $objectSupport true if object support is enabled, false otherwise - * @param bool $objectForMap true if maps should return a stdClass instead of array() - * - * @return mixed A PHP value - * - * @throws ParseException If the YAML is not valid - */ - public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) - { - if (!preg_match('//u', $value)) { - throw new ParseException('The YAML value does not appear to be valid UTF-8.'); - } - $this->currentLineNb = -1; - $this->currentLine = ''; - $value = $this->cleanup($value); - $this->lines = explode("\n", $value); - - if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { - $mbEncoding = mb_internal_encoding(); - mb_internal_encoding('UTF-8'); - } - - $data = array(); - $context = null; - $allowOverwrite = false; - while ($this->moveToNextLine()) { - if ($this->isCurrentLineEmpty()) { - continue; - } - - // tab? - if ("\t" === $this->currentLine[0]) { - throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine); - } - - $isRef = $mergeNode = false; - if (preg_match('#^\-((?P\s+)(?P.+?))?\s*$#u', $this->currentLine, $values)) { - if ($context && 'mapping' == $context) { - throw new ParseException('You cannot define a sequence item when in a mapping'); - } - $context = 'sequence'; - - if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { - $isRef = $matches['ref']; - $values['value'] = $matches['value']; - } - - // array - if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { - $c = $this->getRealCurrentLineNb() + 1; - $parser = new self($c); - $parser->refs = &$this->refs; - $data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap); - } else { - if (isset($values['leadspaces']) - && preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $values['value'], $matches) - ) { - // this is a compact notation element, add to next block and parse - $c = $this->getRealCurrentLineNb(); - $parser = new self($c); - $parser->refs = &$this->refs; - - $block = $values['value']; - if ($this->isNextLineIndented()) { - $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1); - } - - $data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport, $objectForMap); - } else { - $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context); - } - } - if ($isRef) { - $this->refs[$isRef] = end($data); - } - } elseif (preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P.+?))?\s*$#u', $this->currentLine, $values) && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))) { - if ($context && 'sequence' == $context) { - throw new ParseException('You cannot define a mapping item when in a sequence'); - } - $context = 'mapping'; - - // force correct settings - Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); - try { - $key = Inline::parseScalar($values['key']); - } catch (ParseException $e) { - $e->setParsedLine($this->getRealCurrentLineNb() + 1); - $e->setSnippet($this->currentLine); - - throw $e; - } - - // Convert float keys to strings, to avoid being converted to integers by PHP - if (is_float($key)) { - $key = (string) $key; - } - - if ('<<' === $key) { - $mergeNode = true; - $allowOverwrite = true; - if (isset($values['value']) && 0 === strpos($values['value'], '*')) { - $refName = substr($values['value'], 1); - if (!array_key_exists($refName, $this->refs)) { - throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine); - } - - $refValue = $this->refs[$refName]; - - if (!is_array($refValue)) { - throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); - } - - foreach ($refValue as $key => $value) { - if (!isset($data[$key])) { - $data[$key] = $value; - } - } - } else { - if (isset($values['value']) && $values['value'] !== '') { - $value = $values['value']; - } else { - $value = $this->getNextEmbedBlock(); - } - $c = $this->getRealCurrentLineNb() + 1; - $parser = new self($c); - $parser->refs = &$this->refs; - $parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap); - - if (!is_array($parsed)) { - throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); - } - - if (isset($parsed[0])) { - // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes - // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier - // in the sequence override keys specified in later mapping nodes. - foreach ($parsed as $parsedItem) { - if (!is_array($parsedItem)) { - throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem); - } - - foreach ($parsedItem as $key => $value) { - if (!isset($data[$key])) { - $data[$key] = $value; - } - } - } - } else { - // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the - // current mapping, unless the key already exists in it. - foreach ($parsed as $key => $value) { - if (!isset($data[$key])) { - $data[$key] = $value; - } - } - } - } - } elseif (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { - $isRef = $matches['ref']; - $values['value'] = $matches['value']; - } - - if ($mergeNode) { - // Merge keys - } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { - // hash - // if next line is less indented or equal, then it means that the current value is null - if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { - // Spec: Keys MUST be unique; first one wins. - // But overwriting is allowed when a merge node is used in current block. - if ($allowOverwrite || !isset($data[$key])) { - $data[$key] = null; - } - } else { - $c = $this->getRealCurrentLineNb() + 1; - $parser = new self($c); - $parser->refs = &$this->refs; - $value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap); - // Spec: Keys MUST be unique; first one wins. - // But overwriting is allowed when a merge node is used in current block. - if ($allowOverwrite || !isset($data[$key])) { - $data[$key] = $value; - } - } - } else { - $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context); - // Spec: Keys MUST be unique; first one wins. - // But overwriting is allowed when a merge node is used in current block. - if ($allowOverwrite || !isset($data[$key])) { - $data[$key] = $value; - } - } - if ($isRef) { - $this->refs[$isRef] = $data[$key]; - } - } else { - // multiple documents are not supported - if ('---' === $this->currentLine) { - throw new ParseException('Multiple documents are not supported.'); - } - - // 1-liner optionally followed by newline(s) - if (is_string($value) && $this->lines[0] === trim($value)) { - try { - $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); - } catch (ParseException $e) { - $e->setParsedLine($this->getRealCurrentLineNb() + 1); - $e->setSnippet($this->currentLine); - - throw $e; - } - - if (is_array($value)) { - $first = reset($value); - if (is_string($first) && 0 === strpos($first, '*')) { - $data = array(); - foreach ($value as $alias) { - $data[] = $this->refs[substr($alias, 1)]; - } - $value = $data; - } - } - - if (isset($mbEncoding)) { - mb_internal_encoding($mbEncoding); - } - - return $value; - } - - switch (preg_last_error()) { - case PREG_INTERNAL_ERROR: - $error = 'Internal PCRE error.'; - break; - case PREG_BACKTRACK_LIMIT_ERROR: - $error = 'pcre.backtrack_limit reached.'; - break; - case PREG_RECURSION_LIMIT_ERROR: - $error = 'pcre.recursion_limit reached.'; - break; - case PREG_BAD_UTF8_ERROR: - $error = 'Malformed UTF-8 data.'; - break; - case PREG_BAD_UTF8_OFFSET_ERROR: - $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; - break; - default: - $error = 'Unable to parse.'; - } - - throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine); - } - } - - if (isset($mbEncoding)) { - mb_internal_encoding($mbEncoding); - } - - if ($objectForMap && !is_object($data) && 'mapping' === $context) { - $object = new \stdClass(); - - foreach ($data as $key => $value) { - $object->$key = $value; - } - - $data = $object; - } - - return empty($data) ? null : $data; - } - - /** - * Returns the current line number (takes the offset into account). - * - * @return int The current line number - */ - private function getRealCurrentLineNb() - { - return $this->currentLineNb + $this->offset; - } - - /** - * Returns the current line indentation. - * - * @return int The current line indentation - */ - private function getCurrentLineIndentation() - { - return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); - } - - /** - * Returns the next embed block of YAML. - * - * @param int $indentation The indent level at which the block is to be read, or null for default - * @param bool $inSequence True if the enclosing data structure is a sequence - * - * @return string A YAML string - * - * @throws ParseException When indentation problem are detected - */ - private function getNextEmbedBlock($indentation = null, $inSequence = false) - { - $oldLineIndentation = $this->getCurrentLineIndentation(); - $blockScalarIndentations = array(); - - if ($this->isBlockScalarHeader()) { - $blockScalarIndentations[] = $this->getCurrentLineIndentation(); - } - - if (!$this->moveToNextLine()) { - return; - } - - if (null === $indentation) { - $newIndent = $this->getCurrentLineIndentation(); - - $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); - - if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { - throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); - } - } else { - $newIndent = $indentation; - } - - $data = array(); - if ($this->getCurrentLineIndentation() >= $newIndent) { - $data[] = substr($this->currentLine, $newIndent); - } else { - $this->moveToPreviousLine(); - - return; - } - - if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { - // the previous line contained a dash but no item content, this line is a sequence item with the same indentation - // and therefore no nested list or mapping - $this->moveToPreviousLine(); - - return; - } - - $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); - - if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) { - $blockScalarIndentations[] = $this->getCurrentLineIndentation(); - } - - $previousLineIndentation = $this->getCurrentLineIndentation(); - - while ($this->moveToNextLine()) { - $indent = $this->getCurrentLineIndentation(); - - // terminate all block scalars that are more indented than the current line - if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && trim($this->currentLine) !== '') { - foreach ($blockScalarIndentations as $key => $blockScalarIndentation) { - if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) { - unset($blockScalarIndentations[$key]); - } - } - } - - if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) { - $blockScalarIndentations[] = $this->getCurrentLineIndentation(); - } - - $previousLineIndentation = $indent; - - if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { - $this->moveToPreviousLine(); - break; - } - - if ($this->isCurrentLineBlank()) { - $data[] = substr($this->currentLine, $newIndent); - continue; - } - - // we ignore "comment" lines only when we are not inside a scalar block - if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) { - continue; - } - - if ($indent >= $newIndent) { - $data[] = substr($this->currentLine, $newIndent); - } elseif (0 == $indent) { - $this->moveToPreviousLine(); - - break; - } else { - throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); - } - } - - return implode("\n", $data); - } - - /** - * Moves the parser to the next line. - * - * @return bool - */ - private function moveToNextLine() - { - if ($this->currentLineNb >= count($this->lines) - 1) { - return false; - } - - $this->currentLine = $this->lines[++$this->currentLineNb]; - - return true; - } - - /** - * Moves the parser to the previous line. - */ - private function moveToPreviousLine() - { - $this->currentLine = $this->lines[--$this->currentLineNb]; - } - - /** - * Parses a YAML value. - * - * @param string $value A YAML value - * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise - * @param bool $objectSupport True if object support is enabled, false otherwise - * @param bool $objectForMap true if maps should return a stdClass instead of array() - * @param string $context The parser context (either sequence or mapping) - * - * @return mixed A PHP value - * - * @throws ParseException When reference does not exist - */ - private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $context) - { - if (0 === strpos($value, '*')) { - if (false !== $pos = strpos($value, '#')) { - $value = substr($value, 1, $pos - 2); - } else { - $value = substr($value, 1); - } - - if (!array_key_exists($value, $this->refs)) { - throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLine); - } - - return $this->refs[$value]; - } - - if (preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { - $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; - - return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers)); - } - - try { - $parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); - - if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { - throw new ParseException('A colon cannot be used in an unquoted mapping value.'); - } - - return $parsedValue; - } catch (ParseException $e) { - $e->setParsedLine($this->getRealCurrentLineNb() + 1); - $e->setSnippet($this->currentLine); - - throw $e; - } - } - - /** - * Parses a block scalar. - * - * @param string $style The style indicator that was used to begin this block scalar (| or >) - * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) - * @param int $indentation The indentation indicator that was used to begin this block scalar - * - * @return string The text value - */ - private function parseBlockScalar($style, $chomping = '', $indentation = 0) - { - $notEOF = $this->moveToNextLine(); - if (!$notEOF) { - return ''; - } - - $isCurrentLineBlank = $this->isCurrentLineBlank(); - $blockLines = array(); - - // leading blank lines are consumed before determining indentation - while ($notEOF && $isCurrentLineBlank) { - // newline only if not EOF - if ($notEOF = $this->moveToNextLine()) { - $blockLines[] = ''; - $isCurrentLineBlank = $this->isCurrentLineBlank(); - } - } - - // determine indentation if not specified - if (0 === $indentation) { - if (preg_match('/^ +/', $this->currentLine, $matches)) { - $indentation = strlen($matches[0]); - } - } - - if ($indentation > 0) { - $pattern = sprintf('/^ {%d}(.*)$/', $indentation); - - while ( - $notEOF && ( - $isCurrentLineBlank || - preg_match($pattern, $this->currentLine, $matches) - ) - ) { - if ($isCurrentLineBlank && strlen($this->currentLine) > $indentation) { - $blockLines[] = substr($this->currentLine, $indentation); - } elseif ($isCurrentLineBlank) { - $blockLines[] = ''; - } else { - $blockLines[] = $matches[1]; - } - - // newline only if not EOF - if ($notEOF = $this->moveToNextLine()) { - $isCurrentLineBlank = $this->isCurrentLineBlank(); - } - } - } elseif ($notEOF) { - $blockLines[] = ''; - } - - if ($notEOF) { - $blockLines[] = ''; - $this->moveToPreviousLine(); - } - - // folded style - if ('>' === $style) { - $text = ''; - $previousLineIndented = false; - $previousLineBlank = false; - - for ($i = 0; $i < count($blockLines); ++$i) { - if ('' === $blockLines[$i]) { - $text .= "\n"; - $previousLineIndented = false; - $previousLineBlank = true; - } elseif (' ' === $blockLines[$i][0]) { - $text .= "\n".$blockLines[$i]; - $previousLineIndented = true; - $previousLineBlank = false; - } elseif ($previousLineIndented) { - $text .= "\n".$blockLines[$i]; - $previousLineIndented = false; - $previousLineBlank = false; - } elseif ($previousLineBlank || 0 === $i) { - $text .= $blockLines[$i]; - $previousLineIndented = false; - $previousLineBlank = false; - } else { - $text .= ' '.$blockLines[$i]; - $previousLineIndented = false; - $previousLineBlank = false; - } - } - } else { - $text = implode("\n", $blockLines); - } - - // deal with trailing newlines - if ('' === $chomping) { - $text = preg_replace('/\n+$/', "\n", $text); - } elseif ('-' === $chomping) { - $text = preg_replace('/\n+$/', '', $text); - } - - return $text; - } - - /** - * Returns true if the next line is indented. - * - * @return bool Returns true if the next line is indented, false otherwise - */ - private function isNextLineIndented() - { - $currentIndentation = $this->getCurrentLineIndentation(); - $EOF = !$this->moveToNextLine(); - - while (!$EOF && $this->isCurrentLineEmpty()) { - $EOF = !$this->moveToNextLine(); - } - - if ($EOF) { - return false; - } - - $ret = false; - if ($this->getCurrentLineIndentation() > $currentIndentation) { - $ret = true; - } - - $this->moveToPreviousLine(); - - return $ret; - } - - /** - * Returns true if the current line is blank or if it is a comment line. - * - * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise - */ - private function isCurrentLineEmpty() - { - return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); - } - - /** - * Returns true if the current line is blank. - * - * @return bool Returns true if the current line is blank, false otherwise - */ - private function isCurrentLineBlank() - { - return '' == trim($this->currentLine, ' '); - } - - /** - * Returns true if the current line is a comment line. - * - * @return bool Returns true if the current line is a comment line, false otherwise - */ - private function isCurrentLineComment() - { - //checking explicitly the first char of the trim is faster than loops or strpos - $ltrimmedLine = ltrim($this->currentLine, ' '); - - return '' !== $ltrimmedLine && $ltrimmedLine[0] === '#'; - } - - /** - * Cleanups a YAML string to be parsed. - * - * @param string $value The input YAML string - * - * @return string A cleaned up YAML string - */ - private function cleanup($value) - { - $value = str_replace(array("\r\n", "\r"), "\n", $value); - - // strip YAML header - $count = 0; - $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); - $this->offset += $count; - - // remove leading comments - $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); - if ($count == 1) { - // items have been removed, update the offset - $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); - $value = $trimmedValue; - } - - // remove start of the document marker (---) - $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); - if ($count == 1) { - // items have been removed, update the offset - $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); - $value = $trimmedValue; - - // remove end of the document marker (...) - $value = preg_replace('#\.\.\.\s*$#', '', $value); - } - - return $value; - } - - /** - * Returns true if the next line starts unindented collection. - * - * @return bool Returns true if the next line starts unindented collection, false otherwise - */ - private function isNextLineUnIndentedCollection() - { - $currentIndentation = $this->getCurrentLineIndentation(); - $notEOF = $this->moveToNextLine(); - - while ($notEOF && $this->isCurrentLineEmpty()) { - $notEOF = $this->moveToNextLine(); - } - - if (false === $notEOF) { - return false; - } - - $ret = false; - if ( - $this->getCurrentLineIndentation() == $currentIndentation - && - $this->isStringUnIndentedCollectionItem() - ) { - $ret = true; - } - - $this->moveToPreviousLine(); - - return $ret; - } - - /** - * Returns true if the string is un-indented collection item. - * - * @return bool Returns true if the string is un-indented collection item, false otherwise - */ - private function isStringUnIndentedCollectionItem() - { - return 0 === strpos($this->currentLine, '- '); - } - - /** - * Tests whether or not the current line is the header of a block scalar. - * - * @return bool - */ - private function isBlockScalarHeader() - { - return (bool) preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine); - } -} diff --git a/Classes/DeviceDetector/Yaml/Unescaper.php b/Classes/DeviceDetector/Yaml/Unescaper.php deleted file mode 100644 index 62623e1..0000000 --- a/Classes/DeviceDetector/Yaml/Unescaper.php +++ /dev/null @@ -1,141 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace WapplerSystems\ABTest2\DeviceDetector\Yaml; - - -/** - * Unescaper encapsulates unescaping rules for single and double-quoted - * YAML strings. - * - * @author Matthew Lewinski - * - * @internal - */ -class Unescaper -{ - /** - * Regex fragment that matches an escaped character in a double quoted string. - */ - const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; - - /** - * Unescapes a single quoted string. - * - * @param string $value A single quoted string. - * - * @return string The unescaped string. - */ - public function unescapeSingleQuotedString($value) - { - return str_replace('\'\'', '\'', $value); - } - - /** - * Unescapes a double quoted string. - * - * @param string $value A double quoted string. - * - * @return string The unescaped string. - */ - public function unescapeDoubleQuotedString($value) - { - $callback = function ($match) { - return $this->unescapeCharacter($match[0]); - }; - - // evaluate the string - return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); - } - - /** - * Unescapes a character that was found in a double-quoted string. - * - * @param string $value An escaped character - * - * @return string The unescaped character - */ - private function unescapeCharacter($value) - { - switch ($value[1]) { - case '0': - return "\x0"; - case 'a': - return "\x7"; - case 'b': - return "\x8"; - case 't': - return "\t"; - case "\t": - return "\t"; - case 'n': - return "\n"; - case 'v': - return "\xB"; - case 'f': - return "\xC"; - case 'r': - return "\r"; - case 'e': - return "\x1B"; - case ' ': - return ' '; - case '"': - return '"'; - case '/': - return '/'; - case '\\': - return '\\'; - case 'N': - // U+0085 NEXT LINE - return "\xC2\x85"; - case '_': - // U+00A0 NO-BREAK SPACE - return "\xC2\xA0"; - case 'L': - // U+2028 LINE SEPARATOR - return "\xE2\x80\xA8"; - case 'P': - // U+2029 PARAGRAPH SEPARATOR - return "\xE2\x80\xA9"; - case 'x': - return self::utf8chr(hexdec(substr($value, 2, 2))); - case 'u': - return self::utf8chr(hexdec(substr($value, 2, 4))); - case 'U': - return self::utf8chr(hexdec(substr($value, 2, 8))); - default: - throw new ParseException(sprintf('Found unknown escape character "%s".', $value)); - } - } - - /** - * Get the UTF-8 character for the given code point. - * - * @param int $c The unicode code point - * - * @return string The corresponding UTF-8 character - */ - private static function utf8chr($c) - { - if (0x80 > $c %= 0x200000) { - return chr($c); - } - if (0x800 > $c) { - return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F); - } - if (0x10000 > $c) { - return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); - } - - return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); - } -} diff --git a/Classes/Helper.php b/Classes/Helper.php deleted file mode 100644 index 55261b8..0000000 --- a/Classes/Helper.php +++ /dev/null @@ -1,148 +0,0 @@ - - */ -class Helper -{ - - /** - * - * @param array $params - * @param $tsFeController TypoScriptFrontendController - * @return void - * @throws \InvalidArgumentException - */ - public function determineContentId(array $params, &$tsFeController) - { - $deviceDetector = new DeviceDetector(); - $deviceDetector->setUserAgent($_SERVER['HTTP_USER_AGENT']); - try { - $deviceDetector->setYamlParser(new Parser()); - $deviceDetector->parse(); - if ($deviceDetector->isBot()) return; - } catch (\Exception $e) { - } - - $currentPageId = $targetPageId = $tsFeController->id; - - // Get the rootpage_id - $pageRepository = GeneralUtility::makeInstance(PageRepository::class); - $rootpage_id = array_pop($pageRepository->getRootLine($GLOBALS['TSFE']->id)); - - // If the ID is NULL, then we set this value to the rootpage_id. NULL is the "Home"page, ID is a specific sub-page, e.g. www.domain.de (NULL) - www.domain.de/page.html (ID) - if (!$currentPageId) { - if ($rootpage_id) { - $currentPageId = $rootpage_id; - } else { - // Leave the function because we can not determine the ID. - return; - } - } - - $currentPagePropertiesArray = $pageRepository->getPage($currentPageId); - - $pageBPageId = $currentPagePropertiesArray['tx_abtest2_b_id']; - /* TODO: check if page b exists */ - $cookieLifeTime = $currentPagePropertiesArray['tx_abtest2_cookie_time']; - - if ($pageBPageId) { - - $pageBPropertiesArray = $pageRepository->getPage($pageBPageId); - $cookieValue = $_COOKIE['abtest2']; - - if ($cookieValue === 'b') { - $targetPageId = $pageBPageId; - $currentPagePropertiesArray = $pageBPropertiesArray; - } else if ($cookieValue === 'a') { - - } else { - $cookieValue = 'a'; - /* select least used page */ - - if ((int)$currentPagePropertiesArray['tx_abtest2_counter'] > (int)$pageBPropertiesArray['tx_abtest2_counter']) { - /* take b */ - $targetPageId = $pageBPageId; - $currentPagePropertiesArray = $pageBPropertiesArray; - $cookieValue = 'b'; - } - - /* rise counter */ - $GLOBALS['TYPO3_DB']->exec_UPDATEquery('pages', 'uid='. (int)$targetPageId, array('tx_abtest2_counter' => $currentPagePropertiesArray['tx_abtest2_counter'] + 1)); - - setcookie('abtest2', $cookieValue, time() + $cookieLifeTime); - } - - // If current page ID is different from the random page ID we set the correct page ID. - if ($currentPageId !== $targetPageId) { - $tsFeController->contentPid = $targetPageId; - $tsFeController->page['content_from_pid'] = $targetPageId; - } - - $_GET['abtest'] = $cookieValue; - - $this->makeCacheHash($tsFeController); - - - if ($currentPagePropertiesArray) { - $additionalHeaderData = $currentPagePropertiesArray['tx_abtest2_header']; - $additionalFooterData = $currentPagePropertiesArray['tx_abtest2_footer']; - if ($additionalHeaderData) { - $tsFeController->additionalHeaderData['abtest2'] = $additionalHeaderData; - } - if ($additionalFooterData) { - $tsFeController->additionalFooterData['abtest2'] = $additionalFooterData; - } - } - - } - - - - } - - - /** - * - * @param $tsFeController TypoScriptFrontendController - * @return void - * @throws \InvalidArgumentException - */ - private function makeCacheHash(&$tsFeController) - { - $GET = GeneralUtility::_GET(); - - /* Fix for root pages */ - if (!isset($GET['id'])) { - $GET['id'] = $tsFeController->id; - } - - /** @var CacheHashCalculator $cacheHash */ - $cacheHash = GeneralUtility::makeInstance(CacheHashCalculator::class); - - $tsFeController->cHash_array = $cacheHash->getRelevantParameters(GeneralUtility::implodeArrayForUrl('', $GET)); - $tsFeController->cHash = $cacheHash->calculateCacheHash($tsFeController->cHash_array); - - } -} - - diff --git a/Classes/Middleware/SetCookie.php b/Classes/Middleware/SetCookie.php new file mode 100644 index 0000000..0ff4765 --- /dev/null +++ b/Classes/Middleware/SetCookie.php @@ -0,0 +1,65 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace WerkraumMedia\ABTest\Middleware; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Symfony\Component\HttpFoundation\Cookie as SymfonyCookie; +use WerkraumMedia\ABTest\Cookie; + +class SetCookie implements MiddlewareInterface +{ + /** + * @var Cookie + */ + private $cookie; + + public function __construct( + Cookie $cookie + ) { + $this->cookie = $cookie; + } + + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + $response = $handler->handle($request); + + if ($this->cookie->needsToBeSet()) { + $cookie = new SymfonyCookie( + 'ab-' . $this->cookie->getRequestedPage(), + (string)$this->cookie->getActualPage(), + time() + $this->cookie->getLifetime() + ); + + // Do we need to adjust response header for caching to not cache the cookie within proxies? + $response = $response->withAddedHeader('Set-Cookie', $cookie->__toString()); + } + + return $response; + } +} diff --git a/Classes/PageRepository.php b/Classes/PageRepository.php new file mode 100644 index 0000000..a38f0d8 --- /dev/null +++ b/Classes/PageRepository.php @@ -0,0 +1,64 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace WerkraumMedia\ABTest; + +use TYPO3\CMS\Core\Database\ConnectionPool; + +class PageRepository +{ + private ConnectionPool $connectionPool; + + public function __construct( + ConnectionPool $connectionPool + ) { + $this->connectionPool = $connectionPool; + } + + public function getPage(int $uid): array + { + $result = $this->connectionPool->getConnectionForTable('pages') + ->select( + ['*'], + 'pages', + ['uid' => $uid] + ) + ->fetchAssociative() + ; + + if (is_array($result) === false) { + $result = []; + } + + return $result; + } + + public function updateCounter(int $uid, int $counter): void + { + $this->connectionPool->getConnectionForTable('pages')->update( + 'pages', + ['tx_abtest_counter' => $counter], + ['uid' => $uid] + ); + } +} diff --git a/Classes/Switcher.php b/Classes/Switcher.php new file mode 100644 index 0000000..1d9d6ea --- /dev/null +++ b/Classes/Switcher.php @@ -0,0 +1,149 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace WerkraumMedia\ABTest; + +use DeviceDetector\DeviceDetector; +use TYPO3\CMS\Core\Http\ServerRequest; +use TYPO3\CMS\Core\Site\Entity\Site; +use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; + +/** + * Will decide whether to switch to another variant. + */ +class Switcher +{ + private PageRepository $pageRepository; + + private Cookie $cookie; + + public function __construct( + PageRepository $pageRepository, + Cookie $cookie + ) { + $this->pageRepository = $pageRepository; + $this->cookie = $cookie; + } + + public function determineContentId( + array $params, + TypoScriptFrontendController $frontendController + ): void { + if ($this->isRequestByBot()) { + return; + } + + $currentPageId = $frontendController->id; + if (is_numeric($currentPageId) === false) { + $currentPageId = $this->getRootPageId(); + } else { + $currentPageId = (int)$currentPageId; + } + + if ($currentPageId === 0) { + return; + } + + $currentPagePropertiesArray = $this->pageRepository->getPage($currentPageId); + if ((int)$currentPagePropertiesArray['tx_abtest_variant'] === 0) { + return; + } + + $requestedViaCookie = (int)($this->getRequest()->getCookieParams()['ab-' . $currentPageId] ?? '0'); + $targetPage = $this->getTargetPage($currentPagePropertiesArray, $requestedViaCookie); + + if ($frontendController->id !== (int)$targetPage['uid']) { + $frontendController->id = (int)$targetPage['uid']; + $frontendController->contentPid = (int)$targetPage['uid']; + $frontendController->page = $targetPage; + } + + if ( + $requestedViaCookie === 0 + || (int)$targetPage['uid'] !== $requestedViaCookie + ) { + $this->pageRepository->updateCounter((int)$targetPage['uid'], ++$targetPage['tx_abtest_counter']); + } + + $this->cookie->setRequestedPage($currentPageId); + $this->cookie->setActualPage($targetPage['uid']); + $this->cookie->setLifetime($targetPage['tx_abtest_cookie_time']); + } + + private function isRequestByBot(): bool + { + $deviceDetector = new DeviceDetector(); + $deviceDetector->setUserAgent($_SERVER['HTTP_USER_AGENT']); + try { + $deviceDetector->parse(); + return $deviceDetector->isBot(); + } catch (\Exception $e) { + } + + return false; + } + + /** + * Returns 0 if no site could be fetched. + */ + private function getRootPageId(): int + { + $site = $this->getRequest()->getAttribute('site'); + if (!$site instanceof Site) { + return 0; + } + + return $site->getRootPageId(); + } + + private function getRequest(): ServerRequest + { + return $GLOBALS['TYPO3_REQUEST']; + } + + private function getTargetPage(array $page, int $cookiePageUid): array + { + if ($cookiePageUid > 0 && $cookiePageUid === (int)$page['uid']) { + return $page; + } + + $variantPage = $this->pageRepository->getPage((int)$page['tx_abtest_variant']); + + if ( + $variantPage !== [] + && ( + ($cookiePageUid > 0 && $cookiePageUid === (int)$variantPage['uid']) + || ((int)$variantPage['tx_abtest_counter'] < (int)$page['tx_abtest_counter']) + ) + ) { + return $variantPage; + } + + return $page; + } + + public static function register(): void + { + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PostProc'][self::class] = self::class . '->determineContentId'; + } +} diff --git a/Classes/TCA/VariantFilter.php b/Classes/TCA/VariantFilter.php new file mode 100644 index 0000000..577bbd5 --- /dev/null +++ b/Classes/TCA/VariantFilter.php @@ -0,0 +1,51 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace WerkraumMedia\ABTest\TCA; + +use TYPO3\CMS\Core\DataHandling\DataHandler; +use TYPO3\CMS\Core\Domain\Repository\PageRepository; + +class VariantFilter +{ + private PageRepository $pageRepository; + + public function __construct( + PageRepository $pageRepository + ) { + $this->pageRepository = $pageRepository; + } + + public function doFilter(array $parameters, DataHandler $dataHandler): array + { + return array_filter($parameters['values'], [$this, 'filterPage']); + } + + private function filterPage(string $pageIdentifier): bool + { + $uid = (int)str_replace('pages_', '', $pageIdentifier); + $page = $this->pageRepository->getPage($uid); + $doktype = (int)($page['doktype'] ?? 0); + return $doktype === PageRepository::DOKTYPE_DEFAULT; + } +} diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php new file mode 100644 index 0000000..218cd8d --- /dev/null +++ b/Configuration/RequestMiddlewares.php @@ -0,0 +1,15 @@ + [ + 'abtest-cookie' => [ + 'target' => \WerkraumMedia\ABTest\Middleware\SetCookie::class, + 'after' => [ + 'typo3/cms-frontend/prepare-tsfe-rendering', + ], + 'before' => [ + 'typo3/cms-frontend/output-compression', + ], + ], + ], +]; diff --git a/Configuration/Services.php b/Configuration/Services.php new file mode 100644 index 0000000..e0d6052 --- /dev/null +++ b/Configuration/Services.php @@ -0,0 +1,23 @@ +services() + ->defaults() + ->autowire() + ->autoconfigure() + ; + + $services->load('WerkraumMedia\\ABTest\\', '../Classes/'); + + $services->set(Switcher::class)->public(); + $services->set(VariantFilter::class)->public(); +}; diff --git a/Configuration/TCA/Overrides/pages.php b/Configuration/TCA/Overrides/pages.php index 7b6f6f5..56b1b40 100644 --- a/Configuration/TCA/Overrides/pages.php +++ b/Configuration/TCA/Overrides/pages.php @@ -1,61 +1,67 @@ [ - 'exclude' => 1, - 'label' => 'LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:pages.tx_abtest2_b_id', - 'config' => [ - 'type' => 'group', - 'internal_type' => 'db', - 'allowed' => 'pages', - 'maxitems' => 1, - 'size' => 1 - ] - ], - 'tx_abtest2_cookie_time' => [ - 'exclude' => 1, - 'label' => 'LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:pages.tx_abtest2_cookie_time', - 'config' => [ - 'type' => 'select', - 'renderType' => 'selectSingle', - 'items' => [ - ['LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:cookie_1_month', 2419200], - ['LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:cookie_1_week', 604800], - ['LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:cookie_1_day', 86400], - ['LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:cookie_12_day', 43200], - ['LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:cookie_1_hour', 3600], - ['LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:cookie_1_minute', 60] - ] - ] - ], - 'tx_abtest2_header' => [ - 'exclude' => 1, - 'label' => 'LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:pages.tx_abtest2_header', - 'config' => [ - 'type' => 'text' - ] - ], - 'tx_abtest2_footer' => [ - 'exclude' => 1, - 'label' => 'LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:pages.tx_abtest2_footer', - 'config' => [ - 'type' => 'text' - ] - ], - 'tx_abtest2_counter' => [ - 'exclude' => 1, - 'label' => 'LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:pages.tx_abtest2_counter', - 'config' => [ - 'type' => 'input' - ] - ] -]); +(static function ( + string $extensionName = 'abtest', + string $tableName = 'pages' +) { + $languagePath = 'LLL:EXT:' . $extensionName . '/Resources/Private/Language/locallang_db.xlf:' . $tableName . '.'; -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('pages', - '--palette--;LLL:EXT:abtest2/Resources/Private/Language/locallang_db.xlf:palette_title;tx_abtest2', '', - 'after:subtitle'); - -$GLOBALS['TCA']['pages']['palettes']['tx_abtest2'] = array( - 'showitem' => 'tx_abtest2_b_id,--linebreak--,tx_abtest2_header,tx_abtest2_footer,tx_abtest2_cookie_time,tx_abtest2_counter' -); + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns($tableName, [ + 'tx_abtest_variant' => [ + 'exclude' => 1, + 'label' => $languagePath . 'tx_abtest_variant', + 'config' => [ + 'type' => 'group', + 'allowed' => 'pages', + 'maxitems' => 1, + 'minitems' => 0, + 'size' => 1, + 'suggestOptions' => [ + 'default' => [ + 'addWhere' => 'AND pages.doktype = ' . \TYPO3\CMS\Core\Domain\Repository\PageRepository::DOKTYPE_DEFAULT, + ], + ], + 'filter' => [ + [ + 'userFunc' => \WerkraumMedia\ABTest\TCA\VariantFilter::class . '->doFilter', + ], + ], + ], + ], + 'tx_abtest_cookie_time' => [ + 'exclude' => 1, + 'label' => $languagePath . 'tx_abtest_cookie_time', + 'config' => [ + 'type' => 'input', + 'eval' => 'int', + 'size' => 10, + 'valuePicker' => [ + 'items' => [ + [$languagePath . 'tx_abtest_cookie_time.cookie_1_month', 2419200], + [$languagePath . 'tx_abtest_cookie_time.cookie_1_week', 604800], + [$languagePath . 'tx_abtest_cookie_time.cookie_1_day', 86400], + [$languagePath . 'tx_abtest_cookie_time.cookie_12_days', 43200], + [$languagePath . 'tx_abtest_cookie_time.cookie_1_hour', 3600], + [$languagePath . 'tx_abtest_cookie_time.cookie_1_minute', 60], + ], + ], + ], + ], + 'tx_abtest_counter' => [ + 'exclude' => 1, + 'label' => $languagePath . 'tx_abtest_counter', + 'config' => [ + 'type' => 'input', + 'eval' => 'int', + 'size' => 10, + ], + ], + ]); + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( + $tableName, + '--div--;' . $languagePath . 'div_title,tx_abtest_variant,tx_abtest_cookie_time,tx_abtest_counter', + '', + 'after:content_from_pid' + ); +})(); diff --git a/Configuration/YAML/bots.yml b/Configuration/YAML/bots.yml deleted file mode 100644 index ff5da52..0000000 --- a/Configuration/YAML/bots.yml +++ /dev/null @@ -1,1572 +0,0 @@ -############### -# Device Detector - The Universal Device Detection library for parsing User Agents -# -# @link http://piwik.org -# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later -############### - -- regex: '360Spider(-Image|-Video)?' - name: '360Spider' - category: 'Search bot' - url: 'http://www.so.com/help/help_3_2.html' - producer: - name: 'Online Media Group, Inc.' - url: '' - -- regex: 'Aboundex' - name: 'Aboundexbot' - category: 'Search bot' - url: 'http://www.aboundex.com/crawler/' - producer: - name: 'Aboundex.com' - url: 'http://www.aboundex.com' - -- regex: 'AcoonBot' - name: 'Acoon' - category: 'Search bot' - url: 'http://www.acoon.de/robot.asp' - producer: - name: 'Acoon GmbH' - url: 'http://www.acoon.de' - -- regex: 'AddThis\.com' - name: 'AddThis.com' - category: 'Social Media Agent' - url: '' - producer: - name: 'Clearspring Technologies, Inc.' - url: 'http://www.clearspring.com' - -- regex: 'AhrefsBot' - name: 'aHrefs Bot' - category: 'Crawler' - url: 'http://ahrefs.com/robot' - producer: - name: 'Ahrefs Pte Ltd' - url: 'http://ahrefs.com/robot' - -- regex: 'ia_archiver|alexabot|verifybot' - name: 'Alexa Crawler' - category: 'Search bot' - url: 'https://alexa.zendesk.com/hc/en-us/sections/200100794-Crawlers' - producer: - name: 'Alexa Internet' - url: 'http://www.alexa.com' - -- regex: 'AmorankSpider' - name: 'Amorank Spider' - category: 'Crawler' - url: 'http://amorank.com/webcrawler.html' - producer: - name: 'Amorank' - url: 'http://www.amorank.com' - -- regex: 'ApacheBench' - name: 'ApacheBench' - category: 'Benchmark' - url: 'https://httpd.apache.org/docs/2.4/programs/ab.html' - producer: - name: 'The Apache Software Foundation' - url: 'http://www.apache.org/foundation/' - -- regex: 'Applebot' - name: 'Applebot' - category: 'Crawler' - url: 'http://www.apple.com/go/applebot' - producer: - name: 'Apple Inc' - url: 'http://www.apple.com' - -- regex: 'Castro 2, Episode Duration Lookup' - name: 'Castro 2' - category: 'Service Agent' - url: 'http://supertop.co/castro/' - producer: - name: 'Supertop' - url: 'http://supertop.co' - -- regex: 'Curious George' - name: 'Analytics SEO Crawler' - category: 'Crawler' - url: 'http://www.analyticsseo.com/crawler' - producer: - name: 'Analytics SEO' - url: 'http://www.analyticsseo.com' - -- regex: 'archive\.org_bot|special_archiver' - name: 'archive.org bot' - category: 'Crawler' - url: 'http://www.archive.org/details/archive.org_bot' - producer: - name: 'The Internet Archive' - url: 'http://www.archive.org' - -- regex: 'Ask Jeeves/Teoma' - name: 'Ask Jeeves' - category: 'Search bot' - url: '' - producer: - name: 'Ask Jeeves Inc.' - url: 'http://www.ask.com' - -- regex: 'Backlink-Check\.de' - name: 'Backlink-Check.de' - category: 'Crawler' - url: 'http://www.backlink-check.de/bot.html' - producer: - name: 'Mediagreen Medienservice' - url: 'http://www.backlink-check.de' - -- regex: 'BacklinkCrawler' - name: 'BacklinkCrawler' - category: 'Crawler' - url: 'http://www.backlinktest.com/crawler.html' - producer: - name: '2.0Promotion GbR' - url: 'http://www.backlinktest.com' - -- regex: 'baiduspider(-image)?|baidu Transcoder|baidu.*spider' - name: 'Baidu Spider' - category: 'Search bot' - url: 'http://www.baidu.com/search/spider.htm' - producer: - name: 'Baidu' - url: 'http://www.baidu.com' - -- regex: 'BazQux' - name: 'BazQux Reader' - url: 'https://bazqux.com/fetcher' - category: 'Feed Fetcher' - producer: - name: '' - url: '' - -- regex: 'MSNBot|msrbot|bingbot|BingPreview|msnbot-(UDiscovery|NewsBlogs)|adidxbot' - name: 'BingBot' - category: 'Search bot' - url: 'http://search.msn.com/msnbot.htmn' - producer: - name: 'Microsoft Corporation' - url: 'http://www.microsoft.com' - -- regex: 'Blekkobot' - name: 'Blekkobot' - category: 'Search bot' - url: 'http://blekko.com/about/blekkobot' - producer: - name: 'Blekko' - url: 'http://blekko.com' - -- regex: 'BLEXBot(Test)?' - name: 'BLEXBot Crawler' - category: 'Crawler' - url: 'http://webmeup-crawler.com' - producer: - name: 'WebMeUp' - url: 'http://webmeup.com' - -- regex: 'Bloglovin' - name: 'Bloglovin' - url: 'http://www.bloglovin.com' - category: 'Feed Fetcher' - producer: - name: '' - url: '' - -- regex: 'Blogtrottr' - name: 'Blogtrottr' - url: '' - category: 'Feed Fetcher' - producer: - name: 'Blogtrottr Ltd' - url: 'https://blogtrottr.com/' - -- regex: 'BountiiBot' - name: 'Bountii Bot' - category: 'Search bot' - url: 'http://bountii.com/contact.php' - producer: - name: 'Bountii Inc.' - url: 'http://bountii.com' - -- regex: 'Browsershots' - name: 'Browsershots' - category: 'Service Agent' - url: 'http://browsershots.org/faq' - producer: - name: 'Browsershots.org' - url: 'http://browsershots.org' - -- regex: 'BUbiNG' - name: 'BUbiNG' - category: 'Crawler' - url: 'http://law.di.unimi.it/BUbiNG.html' - producer: - name: 'The Laboratory for Web Algorithmics (LAW)' - url: 'http://law.di.unimi.it/software.php#buging' - -- regex: '(?`_ by - downloading the zip version. Upload - the file afterwards in the Extension Manager. - - #. **Use composer**: Use `composer require svewap/abtest2`. - -#. The Extension Manager offers some basic configuration which is - explained :ref:`here `. - -Latest version from git ------------------------ -You can get the latest version from git by using the git command: - -.. code-block:: bash - - git clone https://github.com/svewap/abtest2.git diff --git a/Documentation/Images/demo.gif b/Documentation/Images/demo.gif deleted file mode 100644 index 38477cfb8a8ab6d539cb9fb9e91d51c0c51b8b56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134089 zcmce6Wmg-F?>15xPH~3<1zK!Kac!~UR$PV;A1;G&O^g z-Shi@i6?Jzk~hgY$#tEaBwE^VG$wjsMHsp$q{ZD z|9N17?b2hsbK<;<5`9WAKKXHOr6~dViT*F+0;fpsqfYBGbHa)N8K z!khBKTMHvv3PL*bg1QPrI`YH2iz5fiA_q!BI&z*3RYvtzM2%NR&D2MYmPg8j#sx>j z%7*XAhHk?A=i&eDM#UsJ=ayTg7s{c3D`fw(%RY*Wk4Gn`M!zgcOwCHmD#*^x&dDz> zDJjh@EGf=LcbCNslt;IhVVaBMdMjgxt72a@BoEi2d+H1FOR#B0HDy%|MYZiOI|r+( zs+yXbYU-P6n>$-Ndb+y1hlYl``-g_wuoJa$Qw<4o&54uEDGRNbH+Aug9Vu_xQ`fpv zcluv$^rS3wXU}zKulHuI59I6*XKxG_?Tr*|_hl}&r5wD@Js2sNA8vX#R`PBv?`SOV zWTN=uP07dEvg6s(cN5sRlYPh2*t6NHtA*;1OO5Aqb)T22Z mTT|UTJG2CZ`V4$ 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,Lo=PaD62H~+ z4=^Y>5Kuj%SbhNr;`%-KgH>X-yH@&@$B!ieQ?2GXl$hBQ!yyY2B#AmsJON#R-?Syj zBL|exI9^E(LK%K!j!6e8EI5;8&TQTx!nZx5k9g;YJL}6dg z622@c;M7YU4wDxEN z(nD-hgFJO-Zh#izQ#I+qk!n3+ND~rX!A$gt9Iv)XD2b*6?MvU|!57%dK*jB4X{d!9 z1Ace!$Tw#pp+L)MS0HWN15@NAtjhUL52nKsqpbk%L5bbUPJJ zW$zdFES9chqne+JabKVGQ#8euVx7!8tEDhvZZ-ZdF@bTT`w_nS6gGjz%&kK_j~Ew& z)MDQSV*o}hPXq=`I@1wNL9<&{F!_A2`e$o~(xgk#aE{Y=cZq)<32Ay;K&LoEAEJ?x zJKa_269ev!gfhb}jx}SmyXMB|2v}FS)d*>H{fwXL*7VWmxS;pnq`%MWH0P@WZb~OK73+5p z@kf*ol9#v!7!3kYwrnEc|NBUba=@NZ!70>%aVdJQI79)7sd=|>4(wRfvmO_#E(yq~8n48%TABT-mF;Wo6^Zp^0 zel54;!x_hhdLVYW;Fo`0%uV4V8Ym)9qx>~|j&t~e#H~K$3iW8i(yZl5viEuTYfA4e zq6TWDPH5KBjdS}}dMLTR%y#%w+Y^I(9Z)eY&DKCr@kX{zZ%WSPbFc8QiR7~|vxWB> zU6LHfo^Qf-R}8eBTZ|ofo%-)yH?c*69m7K^9K92ai;P$%1 zu>$A3he!Mv-P@h?p73#ZHicML7|X*4=1~%S-*SPUT=>sd|BX>SoMva16mM6KF;jr> zThSOXyR!>b;Gbx!^hOhQwil3E^`7IDlg7n6NY+YfAAaEi`Rr4cX5|3&uh$Nn3MwNVFl-$9Q z(f5#|yMk;#nyg-xydRIHSC!mhjS^M4IIGI~espPZe~EHiW)>8487)P_oOpo;y~6`e z(ovJdKo~f@OOYt4{j!?$wH6YeJ7j9E{pA>yD(S4U#+%FfJZ!J~R5= zfQV7kL9+UWMi9lJ2L8^W2P(upDGQG!%g#wyQ;XI8QO1IkCJj@t%R1lP630V9(Itno z(WB-?9tPtw_JKK-nx&LVeB5y53^X`BapoW_tz7kx55Fzmdjt>z8JRtu_fj| z*#xaK-)Slyq}VS=_J%p&zzbFCpzTD+((y9h?+rW(KQKkmNoTOj-xnw387Er1TsngX zb3SN_UwAAo$bUNrG!IV{R!CUxRf!VE^p8yOlCuO3vzb%jMpQM*X%=U;7036g$HFz# z!lx#TF{W2|RInu(p9A&jY?Nlf;;U%#VO#P7FhzR_1z?*J%r~)8eGcKF%A!g|R{ZF3 zEk9&tbtGKJ{KaC&ec3uO;8(@tA{r zV!7}&ORtbeA4`_5=rNf_`d0;Ge7F3W^a9row5<1VR(8^C*m@cte(U7XEi>;Y2N#BowSH~#IM0SsS?YI)oc4ctKBAbk#Y?BBo z@!P`dV{wc*-zZ&Lp$C1Zr$1KsiPG^|^gyYg1HBRV)TQb&t0hd3<*By=O1kEVosF{p z3G%rMvaDLGtU8m`L%S1?0=!LbX&U=el_DDd=yTl}s=>p)A(yiTgI>iXz!m?ppBgQl ze8$trx_l%}bsTAGF3Lx>9gT`QKuK8A{28TO#&-NI@2nu{Oj*FFS0*w zmhM^Wmf$lJ=1Ag|JEpctm%dFxvp&P*xX?_2&&eKR?=DyuaK+j~!})+_zK>ysz_}>+ z8_CWbi1`~iYhA6~oRhg-t^6E{CbftL!XRX}6# zOT#>t^onYEctEK}AbxDjWad_M9_M#VeegC_R$5(%VEv?P`7o`EAdN7>Iw|urG6U?; zHik4^5x@jI@hO8xM;YL~uYQP0r~FlyE790NWtS+sq%O*Lyj^KJ*g&~F`f_)aQc*<9 zTm;E_NydJe>DH1}_P<*rH+jYWJSlSymOTe2I2l5x`zu;L0h*XYw6?BB{BGR z6q6-vA*SQTYb%KsxE@Bdq6NCZm?eCZ8aPJX%}DbQ{S>qE(LgW_0&hsnq}>*6%)=wE zRwe5eZ2aQU(m49L5s!?$jm4asY*dgr{_rzdE%|{5dHdl!2lL!bbS(bIeMV0?aY1Ud z=t{yfYM9U|9*?3Al|fYc`S!s>5qk5&RM}DJPZ?Fu?H=0SK5&J<$E1ncEsrpGv8^;F z)AlF>X*WpOuG~-zNW{-ntN{C_v3M^stG64Gxe|?#q}qBJO;i1E(S6jz>B6E|!3+iw zlDTG*xEkfXM?;)aGnVoaW7@+F6o0A$#RMNITk2X<{l49t2t}QzacdvZSb(Q@A1Nt*Y&-9WJFux`q{eM3VE}gV|!*I%BdZn93YP zWu5+O75Sj)?U`Kb`Mz2{jsM8oR7OOEYT-cU6pw0I)g0$fr6?jb@Fd9_oDp?GM&Gb0 zEm8hlL}&Md#cqSn$N`1P8r3r11@6k-#CbxrrqB~qL?y8*U2}})HsVj!B^yg8w z{xix{6jf>}%y|5hmcjwEi~gpi7c;#S1^K}g?I~cqz3TarS`_DM=`#&%!>U9&B;n^iZ{iWVY>WVmUP*YrN;YM4WAES(y_e1ZjS=Ml#wo!{iEesF4|=-+97-_ zUj)wtyOwr({nUKmp))VP)Jwssj__hSf@ey0N2&3~qLOH?M?}#DEB`%upgCzJyI7+} zk5NDP{ts*NlCq5w{dZVZr4NBE*ikYZ`(uUqXk`I@@i-aRk$ua-%2>E$tOwuu^gh32 zkgog^RH%lkf^JNth9avWH}BI~`56dy=aq+l=|hHR>mTg zKmG~onr0FKQ>J0hjq67K{_qgnV-WTJ9MIb?8V-xS7clEFq${cNq%>G{`KRx#`NW{h znu^`Rg;J}g$fQR2@ej%%5=hhk16Dw(zb(++;S6@u&#jI z!``tt9qk;FDVc(k2+JJ2g5=fH2>C-tsIDmTu#`vc*G_IRdH0eV?)4WZ54SyK=W)A1+0GMz7P5@#~Nr!YvZ%szaedfe@j>>syjy=VddmpNQY_QiC72;9_8rF+m#+` z9xDErqNbVlz$WiHLVq%i@4A!Pzy^Mkn@)Ga|Lw42e=T==ax2?LzJRiO_eb*bcx$Z- zVxhEmWNp7(wriwtCmVgA`!{hQbN_&wmX=hyY2OZVX(cW|{3v3hh>s#QiX>E+5XVA? z34ah=i13Gs8aHaxz(M}wMaLL4V7!RY(ZvfICvTK|A!B5W7&3C)NO>a$4x2G@*mz+h zCyg35fadsV0|*diELs?5aTtb-#4crsbV*aBOqw!t zWY(O+hh`s3L4VG%!zpMTPY?GeO> zZ{g6P%Z9z)NB$2o>j8wVKOS}{(1!zW;Oqt(c+$+K987yJGa53}AtA=iz z3X({nAdJd2q`sRfIHrnXZn^b?$}1nmbG zCGz2js*0ek2qDlMs4A)~)By;I6cQ*QB_86+RVBa*m57BJa#bLwoN8zz266CVr;~;~ z$yggc{#rCL&OF4aH6Aj{u*p?FBZ{b^iuyr_qKFv6sUVyp;wm7jLQ2*j()tQ4u+BPb zD_Y+b<1G^0f=jLv1hcnZzTWChx$`j4*RD+jMiPJVDkFE zh!j$F)`h65wTN5Q4OJi_s-nLlC0fSN1_m44D0$CPCnN|d=-y9A~pNS0` zfdHGN%xGq+k&yrwx1pTeAb2s^OaaGe8rBuCM{ILh0$=tv${{I<9YNAac9H`cEQoHI zsg}mn)~5CBXgAo&+!~z-l!}S+B(z)*M;HV+;?Mv*XX2a=im;SlX0CkXBAr1-cc7^a z=SZgUV-Sk+n*;r_HXCD;rf`Fls)&!3Z#)fadK49?{B9thONc?<_q|&=1SkY;uoui=1_)zVWMbd~ zBHGPv6FZ_KOBs~5g>j2d+!0iu_@JC*@?&z$pJ`eH8XV+AN-d+$XMPZ$__z@%xFO>h z-xE5herw*ftfCM$8(`dU8NOQ_i(BkCkG{#+X{GtdT0& zgB<`Ncf- z8k4%%n&uHWXWeEtg>%0}N|qdG%!4KeTU1%jsf17#tI*Gt!PTZwq-)M~y>Gq>S*NL3 zv(Mf2mp%5yG52EVDN!pVX#2C644Cr6jnr_ zl?LGjuLfg94D7w2w!j5lKcTd*fi(-1=JOoqI7bXeu(6G|#b6+u#ls~8ArgR`ge2gh z4|q@lQHiRABsg>}YEerc`mmNH02RnUHnI|+yp=vMRfcb&VP0BLL#xgph*m{`RiWw_ z#4KzEjaf-#9`$CHg!KiKp+Q+^D-$|0l0wmH838%|=OduS6^i?G4W255w$qT2qlep( z$xLI-ND5L2>M5*puIMvu8CD0lnG6s+KeR5{8@SaRI&u+)m86s7O2#D^*b?^Oq+9I${r$q`}g#Ckx0 zmKm>-@DEG4TN0&TyWADw2WN1E4(9O9T@L;$CF5M8TJia~HdngR&3HLO2!mR}VDvH! zfr3LQgIXpS!3p{sEJ#p-)wd$#B(2yk))|es$!p$ev zmo^K-fuDNj^xz1d5>O8UiTZkXG&7_=Q&K=pn~nBru< z;>1E|V7WR%CwxFe0Ea>dY~=3EpUexOM(8Z8?#f2!%B*gLNN5O4h=i;T>u&1i`e4b7 z>Rpyh>z)wo-o?Xs%7U~az0&JBL{J15Eb5wW>B_+E%wP=3FoMJY4~*apqz(zp!0oCG z37`-P^@0x_Im9i~30x9)!0|T=n3`z>q)WY?6WQ@EBj7aU(o<3Twpz*Ts9RtJD6*axNw&;7YD) z5KMA}0kcHCesF~T-Y}-H4D70GE8eaN!*0oT4l098$%M?Pylx4VYzgB~>$2_%opLLT zEG4C`gj|a00?-KX!0WUu?uv}a@GuYB4#@IAE4Q)^?NG=fArkNs3iXmNg)9&R!7lYc z5Cw5C?GVcJpb*O}s-_ANgXkq5QwCgMF=YS;9@7PGAc$n5FKnVO$lwNiq%T0zCYGf3 zEF=azszRDXnK-8zzeOteC}^+)GzciQR&R9pW0i8`27M8@QV+UJ>ijSe8_yBbu3|WU zpcsi0xHxJcqat$-=NYqOIiHa>Ff9zQLdKlzB%KT!yR#UpV>+=>T(S{20t-h}r8r}a z;j*NpfS~@wvXh{UGYGJ&AflqXl8F`}2^$ln2sc8>O*U;%7UuO z=yJ(i6v&2vsA`l4V+slK0I1eZ?UEqsj`9tek|`-lg5piP*d%T+57=Jd_KadVc8tf` zyj(4GAY6GgM!ozb5y4CKt~C) z4;BG21(6T*;1B*V54BDZ096q5Ku`k{3MXMN`!W&^l@WxDQ2}#M`@l@^&<+Ka>-1m| z?*5SLs!B08mG2g_FI-|WHIp(+!V)>t6N5knQl>9?0{Z0tFzQ3%A~8HaN_aibVH=oeFO(`=&$bT3sT ziGXzEaFSCxm2(({)5*-$SFGD#%*w10$T*oERLyk*aYALZd(`-0tGPY zAZP-8j3!6cBPmFxM5v}p$nAhY?L^4xk|4-r^h&Yr4Sm$>{BUMxw#wXcX0Hwp{^oS; zRy3yOFlVohXVui@Vyfb7tSs)I~hj#)<;wB({X!uqwAQM$Er3xio|ibE)jP4V_hchC`g4myCvjFU8;^5Zsc~JM3|-AYUDq}K%G4r#kIsDG zw>l{D{Fa0DQg41I%sEcd!!j`8II!O)@;9|@qgZ3|R%?x}ZO3-3!kDgstB}K@%xgK4 zT|UqV1n&2a_x!*sS(8;P7I^B;vg%fp2UXSqH4;cQk|R?zg^eI8X|_s%Oe^7#X0>i^ zziuf5QE1&zXmd7awNCBImWH`9Fe# z^D61`F3&V@x9$$j)NLcxOvyM>|MXAQHgO}>4zc(u=h86g&<@`iZMp7FDOV2w*K+{@ z5G;3a{eTbrfR6){Q7gBQlYnt2K~a<7aTPfU7}b#rxpOHuP%-}ZQUP_6KX+7xh%!|G z3@UL{AyEc0vob|lRaHO?FflY|w-QHluf#)k{fu^i1JK%|SaU-~bZ1KP>q+}9H9GA12skefl z*U8#2eNhO7)HQw6nO)B?eVx?|U=Eq(IXJf?Sz+!BPZEVHNS<#@Sli|NdUHnt3p;jT z!tyt~)`deyQe_J?UE$8is1Sr$vP0V?ODqWe0?yzB4!b4@iBa?i7rIBSj3nh0W_h%P zrwlE96lRgqDwmM!;P7naa1Q^_4=pu|<9JNnE==c;?fzt1aJTXg{17Xl@D1mbq%{(W zGgxYKQ;A)q_&lZfj=6zrl!|+H4)Z{8xvo<0aHzj{XUmkP%XF;OwyfROtP?kJ0dvs&6Ho(zQ5kh{Ay-lRGLa8e zu>}Eh2fL6JVG#f!5HJ_BF?WvvVG$M+GAWZ1MZ3%(!!jz@4UNgNxV3%Ak17LOYf_p2hi}RdVXM z6J1HH#4M;gv9T+%A~|ZoyC9*tJyi0&gdvI=}uCdvm%+L_?V%$3Y!d>A@T^Cl{*;jSm(3S%&RzIE`$n> zq}@>LkPOO*?8?H@MUxtt&C8|6qN3-`qc75H$xaL+D5Z(k%Gxdo&|u1TG^LTC#sLux z)Xog1Ovi;RrsuY&Emcp8T&n^3rOR}U2lH$#)ye5tsONBS z$_yA#f|~b6qFQRRB&s!3L(QNJ(2}UE45#CerrCIJpS-TmxN^sstP44eD>tnJ70S#s zQUk%S7ulxI7ElM1j0yLW0aX$Bn2#0yL9hqAumj<;1^dzso3J(gkr{y!3cb@0RWCg~ z5-8!X1Dg>9fe{p8)mI%5SpC%tVUG)eCO&)4qH1C$QIvzoGDR7-GqW?rpc5~Vl}Us| z3WPuk-YSOPC2wye*K$%wLM4JoB`QDK(eMQFgsnIccTm`@x*@qmAc&^gLNJIi;s<#QNOKARToWKDRc zT9%5BtPT6X+=~hi(J-clY-av1-Yn7;K~vJDn$9fEz{%e%e-B#=8x^pen%B@_e`9R3jmZk^QFasBilR!`7 zP-y3r-KFjb$^dJdH>%P6#wLi((X#E_d@k7-kUzKW={l^p?rrCK@e#ex_Z-jJI`SiX z@&OgG8&&NEA+9r5b1D1PLBA1JUDE;8(l!0`BSCUa-H}0EawT^X7$Gkgc~M#auu*;0 z1G^DaU9&;|5EP*hz(6K+Ju~r4ly^Y1FH!h^9Sl%=2SoV>P?xK8onLm?L8j9XN8}z;Tgd5F8B~@+c`V zB|XpEMcO-Lvv=InluH{?8!1HPM9%=7%?)$%#k5O#4r^SgfMN(6vz=INRSRel2i#&oOtqd?X#%Q-##<( z7)gS&Ze6~8hA4S#^e9pyk-y%hj0cGrB`y0LK9eL*5+q6V^vNT-Ym(5QSL;E%I*%T| zs8Q>AorI6+{yu_kFItpDkZwMFgcm<?4F=L+haN`A$ZC-!aiWDPPT1jvjfe1Ba|fZqmPpuNl7G=Py(cnjTFg9hfz+6;f#$Oa)=^YVZ?n>;b(*yNx{@qszkYz)DZTy zwCYR(o#j(lPPvuoLMS~1tPVf`k%J8x>9E5N$L5)lLO2w*c^qa0YA%V4E;8 zX-$s@9dCju=UQnw+jfR%qj3x$z5Ag`uViN!hDOKX-pn%}NB>q{g{4)<+=L9SD2SFc z22S|k9nN^+j3QphNa7$`m&B1QYPlieG*XTwj)yP;Ngz9V{`ribp9s2+rk{R#k|$1L zdXP)9o&=MOJozK-P}+D%AcsJLr6M?d8N&_0gL#7ra?%N>nkuk~!<;+(j%M>8-T6Ej zaw_X54}~Us=%I%8Fh)tsOBx0lK|m@{TaJcNP^M_Hk87q{mErDLLxe$9MEA*8U#joU zS1PQ$%U(u-kE;BaDs%CLB{X@8{cx2&MMbMvKd?baXb=*Od}L1_u^>mJh5iy>5sWWo zVA-~al82di%O>F}418{aEu1jLFsTX^y*w2v{0IREN4ZL=9G0(td8H<-T9pw9hD0Q~ z&wY5H%28B>6D176D=QNW$d)r0K7q)|H|!w<+HsgpnoDr;#UTJGnmP2Epb_n}`H-X$v>_OE~c ztB(QqqAIb#WPtWEV7^-NF@m|TEfQ>qrJi*W8+ecgduph%_{1k1(7+=gDal0|q7X?T zaa9v5OiuRTEhE72C4>PGOo&)RnUK;|p^8ZUP*tC=yvnNk5(0c^3KN>_WGlH!Vpaqq z87S^VRga07xcWdbEcSs9duU}J_Ar}`)rK)$Z3)Qy`V%Vt;(;@aK}gHQH8;A=jE;5l z!)V?hNo$bOoZ}(YDuQT$cpA|dbT>f&!VgTFBqf#8$OZke zY0V*R)#mn^#HPoKk}<*`oO=||bSyM-tBr2{(4C~IHj&$v+TSW9wMFdmaf!oajie-^ zUq-1)J&I8?4`(AFt*%GO^bwBY>!V0q=}ENXrs)o`qa68nz}{4nfq`@+*9qyIOgd*d z9sDBzM*)nAmoALZ6dRNz6yctqT8yZNCTb+- z6NCABWjnf1BCed2@f|q1w*cWe242u-w zmSFZE7|_n9TYT7-#a1?1mQa?m5$kIhTXveze0JBOH4k>Z10C~7Cu_nM_SJso90mbG z5w~HQZe6QdrOg)Fy;+*G2k9K%DlL+xCERQO_P66WEjn5}&u~P;$&Ni2x<^?z8a4*X zJ)mX}*!{yF49VIMZTFUe8xb>abRo()PX2L4(q+Nd`=j(0sYii8(U9VMU;D=QMFMVw ze$Q#~0c-r5tm}B|Jf7f?Be+SFpag>(VRDQ^n3@y55+e?gtS}wao^*PiK|lNvZq`6`E6*$Ycx-VC zr)Wbv?P|Ge?bkwkL$`@-v;&vhKYqL4O(XA!JY-8gY&(+AKTmSKvs}rn+X#9%%pVe) z2jZZHI@lQ+ZL_~@E#g`sLV$%+BRHZZ5T}5?#DKTMfW9;(_?3V~LJ8z_J4FIv7H4si zpd*r?O|8RCADCbqH%=oMaw1o9lYnw5S8^EF39=I;=M*I0q+go@bHTG>@Dv0!$34*_ zVk*EsHkT)H0(95Y5Fz$Zeu6zncXWf2PzjY|PZtJrwmy%t77@`9LNF*mkcCqx24!$5 zXSHaa!W2w)1Vr{OXc!j$WLO3-F;i~%5;7qZZ-@p2Qx=x;g+A66LNI3|!993FCu!ya zXeI-JH;BTsCuRl`Y-R%>K@tX05bQHm4D>B|wpNAKcz7^Ck(M@}wiI$vA3!iKJ!C&j z=6R$NdTwYFRrX0q@G6{!2V<~lT46vm1Rqm0MX6VJg&ozX8`roXi$ENJbT`WuI0|w!vlW0H@>(#WTZ{8- zzad*p6I;W@A&MYg{Z=^}vLQzUBDMqw-3Uq9p&PkzUD{!9&n1xBfivF02iV02en5@1 z1tWkkJIj_kkAwa==H*N6bt4OhUl)jh5Xg}qSt3O8k>rGN0u~99z=0g7O)E))G&oKi zcY+3XBtTMu36_FO!U&y!a_6Lzo!}!C*d#`RIzIw*a}p=VlVW^gbNp0L%ku*Rl_$^R zhcib8N$3Mb$2~H3C=aD5=96RQBPm(f5M0-FL69hDVU}$%C=6pCC1qA>5mIM(FD@a8 zmy%Pg0*7%Z5N()cNOW~ik$5M;5kK&R&U2JBFo-S?Jb?ECiRmRTKva7YnS7!Hj=}?v zXb_W9Mye=jarGW%5I1wz67SM>R`D+T!$18(Do(U${bL4XmlQHlhn4p+b4XU-A{3dn zFOdZ+tp3u8NI)(u5i-YjYk+ZB#<>Wn_F2R@8;7+;#92kKh6hAoMw>y2SOiDX!Ai_$ z9$6y@3`vbSQyhM<2n>lG77|{$^&1e0$CaYdVgA^kOq35*Jy3#7=X*haO4#@^p#8u_e+8UB@yQ&wS!Kz z<0BpkVHapSB#BPvG)^dql1Ji!9S5T?`ElTMf-o9#Hz_0~r=u;{2rr0oKS@rnbCRcX zPDS}V*Mp=#=W`!%VPs%FY+|H^83Rn(Vnz2*3zY*saHR^x0~(bq8r4%gr6^sRD1(9) z{yh*8JwTRi0fuMV1Nq|vof39m=q^GwcV9(hG0}KTL1eeWK9f>ojZ$YNp{9X(P#h)$ zE)XUz-~uvGh%ErAXa<>H!a;uLhK*H-XgR%fBbP(|U-Sim z69|EUz+bbIP7Zdl7>j`+Y9vMCIx1*#D%c1r=z#|IqAePeD~lvGdXp~OvO21hJF1gE zT5>|tvI*va92f~c@`ovwEEn-}f1+X?5wr!REEhJkE(R!8sS$sIcb9n*P&g6_K@w6) z26zW8Z|Va&ASiv9c+dhBJs^r@dJ$PycR~=S`GcnQ@ji_r5NL=LopMs{QU>^QKV5e} zL57ExvJjFeDP!9f$f5&i(j{Ip17nh?jmiR$T8J;u0+Nbme8*;mw*$}uLOt+6W(S%{ zK`S-HcAruznb$V^p{lHU1fsY@qBtzFvNjHbAFARMC#7Y3nKn2?nxX#sE<@A@N-ze# z>$`J>5OcLxwjx=|3Twyui^mC!qE?LIB8-2*EqP{q6w@+RWsF_1tTdA|!r@t?Wk>C| zkjF7vc5^pFGc@w3zT}v`=?EiOvacOd2w7qw|Jfk++aQMUIQomPGcu7`qB{YcI};he zmvcB10)X1Kev$MC0V+!gd5yfKZ7oucS)vFQ%zzboz`8^^_7$;Mf+dk4VLAc{m7sAf zw{hD9vO*bhF9~utYQhOtvM&0f9k-(;Ta!q#qZPQrJ&Yupa3noiqbCR?u`>zDlC{aQ zbSQ=_ON=Z~%RN2NP(APwV8*08Feu2ssW9*YmfVTgi@<=6GTB}FC`hKvYKvdA1g&l5uv(g z;mW+S6m7}3d>KUa<3nTETzr|U!<(zJC}PJH1&}_&jaKtY!KF0qE05(kp)fMP_IL>MYr%>$zZEGs z`gT0)QhjKS;$Ukvxp z6)K`3tRo|gIv<$AAk9rpVu2aAO*U$ykx&UktdcIff+hEYJiMc!prbV1!#k`AI(nly z401q{wON~ZWN^7|b|_sc#zy@oJz&O9sKthoc$doqS&PNZV{}z|5kY$rPw7u3HfN7` zwmCo%Y8(T zjyyb$8Ur&>h%x}Fk!#71X=Z7b120f!l`EJI5fLW=L}GyyGXYmKWfZ4Lnpa^~s7oqp zak^xIsypNsuX-tTn-W3f7Vy*A%_SN3VRoDTA%^QSr~aBZ@`EXc4M4} zrJTn(Z>`1`-Xa*>BAtSDoPqI-r=~O5dPt=~p3{-t-N=pI{7S|_u8H)Wk<=mVDsAQX zp8Cp8@qD59iY0{OI1{PQ1w6o=L(y3hCI72CS(4F$Fp=hn&`V>`4BRwJW1b3((1+6@ z0GtRHEF~6gz=^;k3Ya@ZqH!LV!UWb$7KmUh36nN?qmxhxCAZQ;oWngF(>F~CHm%|* zF4HNv!!{{9jG%NZCF5Z*eIka6DpCb!kcNmtDDD6t&!CdTQtq5 z_E&>|tbhUBxn~}z<`-3zjK&98(0!b*q@8?(t>S6U;qi}oW6pqNTS6mk?5eQW-Xe>W zB?->o1N^Z5tvmjG2;vUH1suSQz&j!w!n-5FA?nY%GtdJaz?Ac!sufB9>8}C~ZSyN_ zf+OLH;7%9Z;3FCc624v++5SriSK%**J0WN~DE!e8uW=t+vMO4FEW0ExzN0%`lQ2HB zH~oSvUeh8kvz^chK&;{;OQTD2ge3vhTB`$DYt~2|)rP`7T`JVcqSQbBrGSDgS_-v3 zeo#J;^FL@~K-fGT_Or@z=Yf*wa`MDVm=RiwcU?;nSgfUGDkzAO$3K>~UN?1Y`nPEw z5l+{Yh;q43$n$tECwDGSksJe%TG)=e=ZQUtfX88e2lY^`)LqUne0ox(&Y7M9hxF6t zZ7LsR*eMKyw|Of+dOc(*;bUV#WR)L8LdHI?DETxYiG45zpYMA@!K=T+j<_{hBc#Q9Eg-`)V_~} z&8Y{NlKwK%1p^oWXhK{DKVmiancSlVZxB% z5yYqtq(8{)*ujHnQyx)y^dO@nYSSG(vf?1Kv`5sXQMG3MDwQc#96(h76+=dhilsYl z$i0!)#||I)9KD_wtSLLdI`nw_u1Vj)(6j3BiMFdgPO+^YhM9o7EDJ0H1 z>l7rAJP+aX5JBn;gwH<@VWbg8`pmPDIvYXM&_E956Ou&hgd|T!{v32uP6JUS5J({5 zR1i|%-1O5(4`I|#MioI+O@|a=WKCIXZA4Z-BQi81i6T-c5`!oK_9BKVx#-x2c=fd) zV11&fqmDwt2_$Gc%4w#PL~7|KnqKNerfYG!38kJUc}oQ`diX2|9VTO!D#M6^7pk+6 zI!i0Cin_`vsR$#?Dx&-aim9Qu45K(R>NA#GUX6z^rV2vd9nw1MFNr0#O@g{56c&IRV4`)jJjkSeMNAV8xmJI;pa4m<3+Gtj*E*b`5?_O#xvYtASeO=bO1 zUK_v7#%9nw1ItiQjQbM2TZ{&7g>FE)-#t<$~1=*#E(J%IrB|6 z&m3OOH|K0qO)>=~)z3pNjXqH53oTU9=p{ll(MTGJM56B_!Tu2O33Vva{zNzJlT$-D zh1AbMYpu0WS!YFF&0AgFUVn!$b?AFWNJ0|dBi})Og%E>O!Xn3Fh(aKuEMZl|Sw5*2 zM;alLMoa<|nScpSY=RPUfh#5>lt~Fi;w=>3#3LJ_2zuP2uBb>RGFCx~Rf6!ZqEv+k z0CUO>`&F2LIi)aT(ScyHQka2}0S9nF*ACXQ9Jw$o20Yu#UKR$Jokh`Nhk=+>9%GfL zcuZs}Ynx^!vzU>ItTaYZ8Ee=`l$(L^Ui9kA#9||{86<}{GHAgJRB$vvvY=?hA&v}s zmK=!X06Nu?;kwvJ#{Fz9Kw>CQ+ad{?BWxy6JJ4zAeUKAr^0&{epo6zToM5I0t zNl4F(QR;BV2R~$vcCJf8l#=kfB(*M^y33N1b~hw+E&>p=E5st&G$cT{6HfQTlbhtE zJT+YpP<(1ro(LtVK1qap(yNpB2zaQ1iVsoZ<6fQ47m-8|bbX=HRP_u6y;(VAdxMHq z{Ww~`T17+=yV@1^02si9{Hj?M8Hh$A(UXHTP#_N339nRYk!Nw_6V?)!COXlGnye%z z;QAI!N=U+)oNy#Z9am1sRW6o|VGJ*mj8W93n4rvTW=8%2O1{QI79B!`FaZk~RHD-^ zj1BB!%z@%^l*5-9(1s~;ts=wD!k3Hr#fxW|j#w06W? z60;O2N^!0|i-945CbXcm2#}osEAcW>+@=+Pwva=trX-7L{ zq)ghtvl;uy&1IeGPHzeHTfX(yKld>W5&rhdS~93}V!)6YKIGiSafm`D>Lui06uHNx zXhkq;(TrO3B0b=tMt!J}=|<-z$qnhdx!cl`zN95Em6J=ht6eyu2V-zHQb*NQ6%AlAk zD5J#ZQ1rnneVJ0$`36ZQD6XJh@Pd>G0uHzugsF9?=3Cz;TIP|a z3C(CO$-25r5|63`2qxh-3F2gu?v@13FJ;q7XyOCJ)2UA3jp=u^1D+qyG^XG!SWRil z(?}0thT1X zq_!j`o%n>8m8w)Qf4LJeGs<)RUN%F^80F25mDyF9(ykoJ`6`L@i<`d^9jrvhFn^}Y z#c*%~894gTjqaJGE6eB@2%0ZsfOKrKk^{vsOuCiy1W!KT%jxXf9E zZpxC2vz;XwlRp3+03-gBl~V*mxTi6x6CIorMF|8_A*eRl6HtL3B_y#+VH6G%s6;WT z?r8)MYr+Cjh(!Uhq7w*5*`5}Y1f$EkOox{S#n1M@Q@Oa#8Jz=4`kB4gY<*l@*HQ$C;p8sBg}%|d}c0;J9&4i}IC(0YLv zxB=1BixLPmMNp* z@(=DHu5&vs4go;IAwa^p&s%yBUTT~SA)FFPoCJhO$T=pAL?#3cCuEAi4|I@<1i=R_ z5+zxZZmKvhp^_=dFBz;jKaj!NnUW4QNt5%E_yV4F@-G$r9a1TiLI?yypdL73IXij& z(SD*ip!BCf0VsXar;Ga0|1l`>Nrdh>x*|=eRpG*~O9-j#o*V<8B~_^;fU$@26C`_5 zM(8>)%pi*3Qa3zHJZvC4be5Z%ge0)bGbKZTSUa#2mY#S!o7hVb!XUv6%ubM&CgTX2 z=pe-mDk@Vd$9$QoQcoC)Kl~#+Uc{=#Pz-57L>U!6Qimhz!9C zeVr?r!4id%C!vzoagq{k9TNOFkVDbmS*IB(6G#9sL5NWoRTD_y$vg3lz`!l=}-CRM3CL?2&CLybBVuhUX36(BbhAfFgJ zmP%SUJUf99Sh!qMiAb^+TRRG}%O*o1otRU*d#b=}vQOA4ofxx5{5v#@5Yb>GY%Pt& ziW%BejCN$y%S+WSs+!nC3`)hAv``|35wt^dE9J-mPL-Hh{M6C|RsJbT3yaZ;4i&f=(KNovBHW(24dk#aZB&69XaUdS#$JU1r_n}k)V^QS z&Q}eB$5_^{_&!1%nZs(W@0*O3sjV>30q|s*-ZMW&l?-fat?t-1w=n~+*_&Hh5Cu^X z^nhMi`o9CLB@AIMc-sRs7#%byk#&V859z>) znaGrsuMZVLldI4Tb=WL%lKgTV{JOXgMHAXtCj|x`LpT#i@PiwiNkcFMm%EcdS)MqF zA3$ljo>UZ4ArwN9D6rfWo`YGSBhpKO1V&*LA}!LXRFx>5{=%7>LW=OZ80J|@IjJZ_ z!vfmUv4jX6Lt3Qmx&(>@V^Pbr#E7s{DVJi1s^wXRpa?iMM4S-Bx?7>A+S54AQ;!%k zJQXUOKr=BOW0(m~s-mjih1<)d8O>zWte8E=)eFQZ#j`+0Fvyuqt)kJJB1YrJ!F@I6 zFtjORiXMmoWprFrtFy~23r4;Qs%VO;aE+zl3ZURy*ib9Fk}F!Q4baQJ-7sAhu#MW# zMy6>2U;DKw^2XBg#K%y)N_0QL!VI>>jCvFb-n%Nr(A~-%zr%XR#afX3@W=Xaw{mSZ z0W4Q{LpR0|91a=44Czk{xsVKD<{cr|Xd;pfX}}o%dEaBwoPA4@?n;sehdN4C>A5Qm!l(rJ}SGBZ8XNJs(~TT>{GpbGLSJ0)AV<6Oww zz-zYJ+$EA4vkJw@ct_vm-Q68O-n|S_5}D5S4A!W}R}BjDt18^$nn_ZSA&B1pL)U?n z5Cj}Z-Hta3kuG*kz=Wi(1mwVutjK?ZzGz-BC33FbA3-li(n|90|RIyPL>yz0^w@F9{%@ z1O+OWw*x9@*(s@R38Mg^IX7}Y37 zN`CCw_+v&3Jy+}tSX&N@35>gFv^&GJ!r&LdW>vz53RS}j##1XHY7K+QW(Bom*$P4+Q6iGZH4Lk%7$|7Zlhe7;hQGuP^zX zeU?GkiLYkYxRIM3>#o?B{#2(m5jh+*xfTU5NC>d_ZXZGs_ZLkNI(jp=v-JK%^LEOlZ(jB8y93fKT z7$K>EC@InoYUF)|whiVhpF!I)crkiTvf~I^QR5E#~qm%W1p)P+gY8@Af?OZa~qreOQz$#!VFI zCGm^`-m7xb2Bfx*$of+J~m%=I`tk4nW2tLZWaqd#} zZ`GB!FUsFI&>ZX~nL@0n6?61ii0&C!cJ-ehKbt@8s!G^#j9F=s=}vK*DE-Gy=6<4P zS`493>_!YZEk^@cG#c}kOc#Fy`OuHB!Bm`<#Lim8>R8f0(`5bX!4K(v%9&jWW>0#{ z);WIm^ah<9JRoJ*zj})fn)xjulGb?g zgGq3Jb{HWPPDTGmrg9`pRjY|gOiSkqpE~8q(QxOb#hp(zfL;+()$b`gTm2#OXT13YpsvnzrI$weZnNGh1biI?#DC3MaI))9Some zWpUOWp5S)~*^*S1*O_+I?q?IKPG*+tjpu?3y5hgza!p>>>wO0;xb-ySwobIp`RT^z zW5oAN+P_y1!hZaiZ@qq{RQ>%=N&T-nA0FR|_)*YH?25I!*x zP>x5$v6&3xBT+26h&U?H2wq`gys8qay9;UPn(_ki z`U3X=PZ1h=d5=meK5?&WAigKH8lb1s^ja*jvfA2R3Fsk=EHyKYe$merP5p7-wrTBa z*#nZT%Dbp^tH3|f9L})^OcX9gd+))Aj|^O@W*+T$zMAR08}<`v;S3CBh2L}t|LB4! zyEbc$*)-*}MgO2Saa2adVO+ja20p)b`xj$2pf*3_XQX}n)9lJ<29?Q`M*klLx^25# zQ%sdOMQ(SEwEg=nWj?$GcX+?F%lc^Km07`Evs|_PTvt{Lxlo+6Z9#K4X@deLeoid~ z&-%o-g`I3R$Mc6p=={We>&rzv;W^p1odf*hY>)iWIlGE|vVW`-L31^4AmTrTyb!E7 z!Z@jPFaC~BX&2V`pk}N7DEX(0IED*i|j^T@A((PA_Dg;ZgN1UD(7;C}HXFywGnnvhFD4J@PNXN+ima z5XfR#E9`=-->CpgaW_NY z))o;h)3p)RAUY3TyQb9!fhV(YQXN)GqGAl&qtc}KZO0P~)z|K*x5%VF9j6nSw|MWV z*^-ejDNmld`<^nY9Fw_AlL|k+6j*B)D&!=)4UEhOXAKn*R?*PWzJ8{(t)iRKFtyMA zsUcmgSKHHdt~|?I2qoqy+&lx`JZb*$ba`l`EqEBU`{%>relWz$=y`BzvvB|heq%vP=TwT6Z;%pB2udarG`c7M9? z>g`j#x`@4dq0bVvYLt~K-7`+ZLlU!^MD8c(4?Jgi8BmJ0d^mM>hoR!Xi|dtKsttN8 z51z$88GfljLDS378ORQrlK>Pf9_hwwMJdop-uoS9EK{(xq-C^@wsmw*Y#lv6+1mmgS|_fTx*o+UQQ zH9NrJkImv%&#IMhyhaGMaNgS@FjX{|TZnIW_{bEN5qie>dGxgSa9Bqz3G?3jOY7A? zK|5%gmTL$zy;3STBUAl_Pb8(4pJA0Y=W61I7-)R^-zC|scYMaz)v^ToH($KwGGX4; zrKR9ep}BfuAgW|UbB9I*qv?IeX#vzDLryUbohbJV=_E1yp>1Hn0X2K}z>kT4O-8ye zlC(+~(bbO`LW8YNz6g}ps0J<9krpzyRiX-F_J4>+;>%Sik|R~ zlq>!wxh?Wxeq?UAwfN1rn~07kk3Z!!H=)^0emj5i-jFsSSKaMSA)IG&N3Kf3XP08& zr24U7S-wW?VE10M>P-1ng*pSDy$2PlpXzND8mtcX9`&n!ZYfcC;pOwqYF%}TRtgKdh^Zad-%HA*Yjniw|}Mfe0o;72dn#$7d}VP zLQ4y61kL8s)z}zK^);54!AJ%ph-K&>*cjp`av8O&I_$V6$geV@^9H6m&?htXFn zVIvK`!Qpm7j6tz6ms8#4}!MlZp9Ju6VLzzT?0T)=$(KJ*4%#6kBFnP=q)>N5;t#L$I(b~*l) zqNaHo*r6@EX1{4O0AvhORv+FaJh|r9d-&kb+h=3l*Q_5iR2tF?>LT+$rg#}@AuGL z^Uy^00Hv(KijvXqUVO=Lu;XJYKHmh96zsD`?DE3;Hl_HKHm*W_qxmX}6<3NCF*pkS zS2<;CKI;-i67bN*p7V)An?wJPO^}o_-`vf4r07>VTMOQvzANQ~3|N_>WO2rbJyh_k zoMiOL0U-lkazz|ID>jduM4#Q9mls{mJpgf<+VTdWhj(oe?Yx@RO{_f2L2y1Du{A62^-FmGOFl z|J<4X6+16F#HXknz0@bLCchcX}DTHvY5xW7TTrm(si`H0NZ| z=)9wxcxnG&-{usV$DL1Uz!5siCqYI}`v|d-(RbgLG_$$NOX^#_Yb#;2u4N+Jvb#p4cf8Ez)edmhd9tAs<*b{yp?k?} zE7;I0ch;}p&~I@z=-MzKI~$H}7*0FiUD&w0=4`aLVRYhr?|kDP$ihnAZU9WSM%lufe$mq$-FA4R#CCvKW&xL6cyT9mt3 z)^A$2xL9>M>nmfU2GOMZPr|D_cm=$Tm@1E#nIK(bIUcr)$PfaTa>GNKsA*lP*T{{kR0V+zvbED>eaR7 zMRxU$toA6WHbCOM*IX}Q4!$R@e!g2QGt~x))qX5){@mOCf^IJQC``L^fa-RjhFegJ zNMK84kmYu;qg%)?I2{xPLak@o^Z}qKG1~%8VH7Q(xYh+5S|AV{Wf?+tix_qD`;{+= zccV9)m&3bJV-^(f+km#3noaUEiX%2Wbs<8~Jxbi&-GrMON&;4HXNKWI$%JYjFbIEL zAZh`6$sxsWU*X=Vk=ns$xF=|fRQUtL^)0A_+;4kwOXZUHFj|-fWjmSt-3?j0Vq}+p5%$m58!sQ)Rf}|7uxny8Io9Z`xGm;oQz0pOS>|{(ap#{ zD(`BE4qS;wL!e~=_2s0hghu3iOyYJ`;&qyiS}+vFI~*0C==MTGpo6mAzQ9*=6_qIk zrhx)-t0ich%QA{RLBkp;NI+boo3Ig_1Ml|B!z*XOv((3xXw;{;vg_2Pp)lZ4R4wLm zLM9IFx{}GQ+FnB(HC4gDd{ubQRn+ZXU`7ccx}YHgC&7Wh0{6`q^XZJ(DR@M94G zz(}fizD{waj?Eq1>M1pQ8DAvXr!Z6J?0Dcr){tvEVEZ9X)q$36d%?COLS+mddhc^I zx>aT4@m*mz6lsyj2>2)tuQ`z#O}rj9vobD=(iP`pw52=NUVvvsLF2$3TR*AL#>^DS6VI~H$m>aw*eroV z07){_2Tar8SLISoWg2~9qB-dM;w6U!QWy-p?JA3ib`tTHW%8Gm?{8&n=J|o7;=+K` zj)QEuUkL(oq{OhY@UChvj=~#1Kuwt*O`#$v`n2tz98zJ9C86(eZwijyF`^iB@;UI^ zGRklgBY<;=xMS-|rHd|d(NyGx?^Yw7PriTCaFvCAV{`AvXjfC|0+F?KPFIgv4P>Kj zE#FnYRK$7!J4EBmQ1p4&>(K%9S(mHTi&M=)3cE{rW`@m0&j0k3z{}<@lX=fn;SAA; zx32yiN=JON7OvbamwZ;4o$sGDwphHu0=KI_scs>xssWlOzO(AA>37DpM0x&*9b+`K~|JR z-!{N8O9TkG9TXY$y(DYwuI*9@YZjkG9WJ!WYs$O0E@a;V^W_RY)+T1*zMUCa@P+Q2 zMPlfbEO^sext(_3v0!;_ThOW3$|3UqefFCQXi~3%UoM#@ZC5Hxv;+!OC=OQ?j+P+! zQf^gWRqDSS^XOu)Uhh}rI>HzM6|5;>d7Yq#YOd8k>o}F&wI$$stN=+(3#Di9$Mrvc zx_9W?5tX4auK?ZsH7#Fg5p4TkZMDymGO)=SDHt%VQKQdD99hqDfrc>5~F|Z%kEoqo=H&~MsGCvXF33Y#M4Z@<1Cw~ z&~}VlDcs%Qb(Cj;Tv~&5_@3HVpWD7aGCzUdUIie#e>r~pl_3RAs+Pz#!}8V0WzM%` z${c0+^j~hz)F`EbyN@Hmb=4Fb50%^Vvi7*Dt61th5-a(a-D4b$FbZf4pwAL?`hK;R zkyM)_?tia6y!%yVIw>abS7yqQ#L{cGZ@=+YZuomb2^FE)1)=gZL5bZRe%ho&opqMC zp()=*FrytJt}jwfJG`e|Q{C1a+e4qT{&CCLdJYcDkoc1!7nZ5^CsQ*l%ivGegRpF? zKiN)UIbMHq0>g5{|Kvu8e%M8me{F7hN*_n+3C0o+j11kM3=}doRULys*w%|ly zdA-1VJ1W&-X!U0crSEwAY!`s)OA5*;=bHRN@Ol2HPF@p~Grwa&(tN>lZaQaF-N>IT z0c>$=KIcYfh6Txxc9#M$_p04>^LQy*=VtjqqpQ5ag3kGGVAF&lBngD4;ot+w2sXMgn@j zLtPs8BoLce3>ZGrTYxGI`R#HD8_ahv0Y1)nJj zm=-?p8uMcD0wBsyLju;m41VP&UZ`Y2Kkh9%3H+jop&!1=D~#f^21^;E#vaPiNP?AO zP}Ep}(mtq34o=tMPc4n*_12uO_T=Lwi9K0{|L$JO@7}l5q@AO@-=$>W03dq-UaZNVsY1EC1#6! zh${~Sil~B{rpk}B_qBbvA!In|u0w2xe+=@+1w6=oszNVU@hNw(=cfjX*CGG7L%io( z4RUo?I#)-dY@wKMp)O_7NQQOO73G!{^uQy)4XWcZ1PvR*m0PEj4EqHQ3vjBKX}I<4 zlgbImRUU$s72S?MRQ&;$hNiPm&KJ&@XS`?I;0*R&xG_`hd-cWY9w2gRC>2;FR!c?2 zMg}Oe5F}~^#wGhD|X3Nok}{?}Odd^6wQ+=qGg`$|Pu#;<-z#qxWxiKE=wbcv!TU7%R;)W5o0F<5K0H<>PP7+0>S%lp zp)DRWD1=r^h~@%4!-k@$^wX*c2VGVH9jQSLP5^BRpG|A;55h-`0Fnlg*22;*S(^kT z0-4{K(M}SQs9zmlArZ6}}Mnw>|_`;A$p`pDob2Q(Fw@@3H?ZA$TcgM-%%De;O_n4i!0Vo zuL$QxeJC;T{Cp6>;$7%<>&UE3L!p&m^QWM3zh%5>B6B0-?k)Ic*2Boa53%QxwH&BY ztq%r3_O!W+mpZc#m#wrz!{gBj%(1{XIbQgn%3 zW_QqwRY`_VLBDEvZg+2`FezUpAx+YYv*#)4&Fm*~kPT#QwW&|@%!-T5@{oIF1I>%- zn$&-i%P!lW;*{{3;ESE~z)p>53{Ot5fQVRsr&gso!^Hv%_b+J7871Y+z{gpS1nl1R zrP2Q|Y1*nUATjK>xfJ}h`1kVDdoNsKxmnVb+|H5ssBQ>L&{P!nwWk_%?0ahf@i(aY zY3rO{=N{YlKDqAvCY<{1{k^5#a&cDh{%5NX6J64N!R?QPhQ#^zoxRTN+`2rhjb^nX zpP68E6s4Z%KO!M2mcu{mv&8q9R>V60BwsL`;FxxX0jWGNu!{p$`9c>0t0+mMF=HC# z{Scw5k@$=^lvCHn!R(bN_Fm|tk3fc>M)M2q?as z^z;-%p^$GUWMpU=wIt@A+lO#dwD?N7hzLMAC={&Jcue#{5U65g`T!c6ZIY4qc>Go3 z6q;!HI)XO2UU74}94d1@@=pva=^SDYXokU!sPw2k3g`pwC&dy*WF_>{US3pnl=70O zdgiEkHL$QxFs$4Mj5ck+r!*ExC(cLa2`qNUcUpvrq9I&DN>{64Q1=y+;4|#Kb}+3- z1_T%sN+V@R%(%1VC^%a|y~31+4##QdI7iI!u%xBAm>ycutc=MTl7tm*>LJH4*~SCI zDIP1a*En`YW#=w$EcYlcAJlb|0noI|SrDGW+}SbhdD?3#NgM0gTppX>#5V0I;K^5G|jr`?;KVVvT3414ZfPx!$RE z8@q{d6QXGrs^vmGHE~D<5>TE<^uewG*z*QuS%@7`a~j~w^F>rU@F8TNtS%blbwA)3 z2M%q6k9nnaA`@Y4j`o6!M;dpiPqV%m170q=GjUgxqnFNJy*O;<0!bT#ltm3V|^ zYd$*x`HR-uw_aP`jA_#NO6uKR*bVh3gV1ampx3<~oTJ@`p(9qdr_;M3+psl-j)r9w z7fvIQx@d(CU_99Uo{@-qS@@qtyGyG%2>tgv_7B8PxepC~Q7nanFWJ-H<{~ra9L0!P zJ3#EX`r)6C;#8I#t~}!!5a2k*>sdRpmU0b>nH?uuEID$%UcWJ?ygy2!dnlZ*zH_K--5emTBm$cR-E^$yzUmQvw>)Ll8wE^{n(a! zTs0r(g=}v|?KVP1C3E#$URh6+uR32<6}IG&saOoXjh)ndX6eFyq@g60mZWNmZN%3# zP$z!V^|7u%Gylur;~+v{Tm(?DMR3FI)`wtYEQ~=9&?kHfZ4+F9DVTECwliKjHO9#9|iIp|c;12nW?>C4qZ@-r%_kXVKQ|rhO2^{!eAs+x}Sa+1|Zd%?xHxM1G@R zak(w6S}7xm%W_G^YswK#@Z^KOjF_gk(4>WDJdXQorwsq&b5mCwnJE+HrTr*HM<=uXli-vC*r@ z6H(rA*Vv*!7h>^>Yc5nGlH?nv42WfYQV@RQZ|XNtr*LUZv~uz~!h49Ou>U_|@@-3D z$?-~EaqOKYNtq0-IdZvxbBwMo0^FmmKD_x%9N@J7jT<_wDZTDMTwU82da_ZidLFNC z+ee?fK^H4beWzcRDLS%Nb5hm`a)of_{8ASiZAvu&QKQh_;U+J;3*W`K`7u`h#?VbC zg2Nb6K>Wvdrww>qMM`eD{5pK6Tjao_Q5(jv7RjLjJ@&THcBTIBn!WxGi^;o~(U^xF zWhb}55N#cn+3O`UcbybnHMKl-83Z}tTJP2SABGOz^lb)-+H3A>yVH=C4P^r3gcux} z9Z;2WAq=TMqy*#DSqzeei+{S2oGzl=y41qK@ z;Fj?q4S>v2HG+r(9O(U4n-1%V*#q1umTS@4A)@Lw{f}?eSklt3+}clMFX6j*!78 z_m_ELRFQG#%j@UD_jxG73Dc9j`IU?p@gN3Nk0^CHBMNy1g{jxVwzLRP5k*ewSb1~> ze!22raT5Rq2r!^zN!s4BLv;*fF`j7|4^0yci@vi%97!L!?{=*TJI>&k!9ZR^L`Mb* zN|x&XM@6?_i2ttW7^8lwmS=2_xC^EeCD5$EA}ce5nFS>t2ykPy)GHY2>^(*07?&zI z#nw2{{ZGkJuQ}GhBEljrF=j``6oJq~_-iNSS2$J=8LG!Lsl(Kl#nhFgcu#xTn?o#* zy9fXlvZzh#ku{Z^5vx46GiNI9R;~a;y_-#~i@InHs?mV6zZFm&Cw;SDJVRz8BTyhm z4$XNY!;_^TjUolKNOU{Rp@xtw&H!^AWPq#ZrA&2HIHSl(!xIRUu4XG;1>8#HaP(7t zKTI{Fdz4WDfRY|91AvY6g=_B4!flf1G{)`~u5%ByXH^A}f;!DB08boaJB~ws>KFZQ-h={7n}ddUQWoTQ)b8a1%XS?I(g1)< zR=Gokp8WWpbOP4%pg8E+{NOHGbtjkS=>D+W!-5E{LL4muwoF-ciJEh5h;l?;TmqFa z{p7h@LJ96awsQ22J>0#_q#@?iRsSiS2amIWUU*VU)J$T|wd;nHXs1u|(pY*eW2LM( z#um?ptrc!*d*$1&z=|7Ci?y`?1!Q}o@M9pjBMQ<4xOP*d4NB63tc_`Q`d;#qATbP8rs7j20)48an_IA1 zs??>NhhR#`3_`Y1?s|u_$a*2qvO<=L&y-OrM~%;I&lLv)c4SG>mx;Qu zzMNT2&4z;I`o9^H+FvP}^SF|zZ2LeA_8ivta25MvS}8MzuunxZa?4mi1|^EB#YfZT zJ2(f)(A2kflnAZphg{`)ZSIijuXN|^CpeFk--g>s_6=KnJk(y+tX(&=F{_!g323wD zM1BamxJTK!;ik{Q7&QaP3F5>y5EL~T<8M?g*A9Qqky+r*YnU)9jkd& zD%V5GuX~GC8~dTXsm%Ol}oWjXf@}CR8Dv zCY7%|@ZOD|CY-$My5G!T;G8~g{U-uxm5MItc!&n$uU#(p)n}rAhI*(BpJ?OS(6Ayg z#a$*r8dTw32IYN{xA?lLA;3u*0Ir@?;fd;Xqy;}YmFrCug0La~RGIu@{!PfFI*Ha5 zMM2cpY1s7yQV$EPxKzCi1SC;Z44_2aDBW=HnRbG0I84p8{lHpX81b4(S6nd3qJbUai;+ zZReD?r@QE_yw2$eICJ9Bum_+}f6x4#a>66C-ihix;u(;}CJ}SmHqhEbp@To`>8xD8 z(>#;{K-lK}dbA^93|7*W9#r|B}7XFeM(b@X` zy8A1H=y7ICj<+~!egcV;!g#w%iR-L^!Y-$;iW zw=3lISb?&ta!+byIM=$OUl%*=7>HZ*$S&S9HKM2fsPwv8D%-8efGkWMe}3V_bc5S> z=kMBNoM0K%+u0T_2qLstfN^cFKDn9;wqmJFaSKmRZ!HxZWbT%%D$=cu5IE5ld+X9_ zGsOG4Zv=Ll|Lc4Lb2|9-xDs3q=*4P~MSvmbmWm3c&N6fH=2yWnI@q-w5A}D__C8}D zMMEO|cbtZvK0vdP;S7Y9igjP}LAjlaZZ?hHZj)oo_7zUSaI-fLCD(37&x-O?F)p84 zl1&n%2gY=JTG`ejcii%|Gl?nxxU(a)k;4mpqgGAbqU}Q=nsKn7yvT#Q|I$vM-olVS z{ce@GBdXi;3{*Yr^Kh2$OABLj=A5>_fb@Sa;T?MZ_6-SH` z!>-3;Uf{kxd=K#Y^uq)6CzA&*JZpO|DQAVK_7FG|qo4>&heMs@Zs^GC#q0YGih9`s zM8atoLDvnDck5N&(1WooS_l+-?}Mk7znbLBGEmd^N|rAW5D04}-5i?(zS8VE6M;=})V#KkfK`IobVkEB)p5^_L(2 z??AiX4@zNXZ+}1LKZ~|I!~OUB!EFko>wjl1xBtYK{we(W$L`r5hyS>%OV1m=p1%Xf1)L`D~`DOG~!|Y4@{zN{5_Qv^E_mA~9W9=^%Ut`vh2coZ=?sQ*yz51n4dZ4Me z6NO#**24WIevFYRr}JC%J;|V^r^;rOqx`s(ZW_NIC$7zI6@DmX9`~>yvQ4@{e_wBSxHTZ7TqWRd4=wM4SOxF$RFDDW9p|g z^rHT9YYHMW(=43);~XZMDK@8uNDWW>1%2z7l?wE?KM&L7u4kOtXe_xOLkWED9MEM5F-e*coT215c#nQn;JvMc`cgrX6F7k1+RHqNu|9mSlU% z2!b3vpU@!hR`%b(_}R%mDtdd8{~$Z}cp8MBA0u-YZx~@rwhO}aEwWdr^j|`)Y}a>k z5O4fr{euV?#~VcfQ(pp#o~*iicIFwE`UVnEpyyf|`OCWR5dfVmG3hcKHNCB7A3M7# z)4VLT)?Qv9n=&>Qm21BOppz9$Ls2jN7`Yov=Z+Xd@tC22jr=yic+^MxsbW5as0x!* zk?qtM&so&2p_4fw98Sj*fqF+PG@8q&doq6?gr<}57NUEuDgt5cjsYwp$E=Tm^l$Pu zk_ULMRPND=N>iBUE22ioDd9~~)&~k0)NdLkv~2$#dS@iLB(Oe3?C-ZXuZK*L*pBf4|O2lTa?$S zxiTnIce6RFfnSfI)Te9YV|T5$Eap(5Ffu;<>^Jl(P6uVoLdU6W%9}ak;6&F-vzX%m zVOko8yiXgXEmXK5BI~ZYvy%GJ1Zqx>1kDD>HWB?`K>`fruo86(@;VVK5 zZ%^(-Sg(_1Stg5Hf``I1R8cZIf{^Smv{Dp#SnwNDEKV{Stu8;E75Yp5(G=b7Ykg$i z9c6j&!vr3npjFx)CN>7X%WX>{%Qi9>W$Yvsfk%Y-gw`v)5NVIkw9=hARw}F~1?8+K zH6m=mJZwi~ZaaK*fTasaFa+iHUO9tBsX+Dil%s<7D781Cm0RC7nmrBI&qJnD`aGk7 z`WM64^?o?kolPr4*yb+p*e^~2hJo8v+`7iA`Mgn}69$nL)5?nA^sPEE=)%x2B^B%pL$#s-aF9=EA&H0uxL3F*bvm8%*PFeCS)DOksZbTiKnR-e;S z&{2vn>`H3ywdH+9*(MmeiGCDblBVHQ9OB6caA54%n9FXP=kb02&E=i(H{H3jTsVtp zR28ra)f=t;xOx;KfCBDmIl{?dIW^I5;9hMgTE&2D`SC@uMEj5LqfGU%xV>@qxKNlC zVFhe6DXNnj&WxI3e(;?vj#YF`al?{sb#_G2b)63Cz6uNg|3QTcsy&RBbON)_i;)e* zn5O-~&lO|A$cIWy6>;`jIOYx1&Y0{QDR7bempJ7|LVAyW<{->F`cb{&Iz}Ql44no+ zoXK)5H+WaboUXARcQKGkdaU`@MIr%@;|{|N+#`!bT^DbJ{W#5{76qb|tG z6>!tZIZAQ~{2t*kQt)nWkVZC>oA<`p-D2josD#ku;xCzmuo%=P6wyPDlt&_TD$g|Nxv@2kz$G>cX?%Uu-QFGDnE#~KcT{fzuGLu})nP%?3 z8R8)li6avj&i8qm-&Do>&-cxA*Qh^uuMNjonofB)e}wb8Q|;mMbf<-oV|4m2AjwbK9_C{gp6Ql_$t+t zM*P2B(&qO{6&Ir6oTY&kWb4^`eJxf2JTUkU|~(O04W9=I+{3iU^1Wu`cv% zv~`kxZHXNcW88e%4#&*#Y=34Js{Pb}gKnJOPM<9hS6eXZ`!-=jUpaTeKFl-fF_RFB zVEi2BTr1O3|M`y8V_|<}L%V>pWfsa*i~CTaXQ4}FYwhrwbX=sKrp)u}RKURvZ6wpZ zC^mb0U`G&&UX<|Sv5%jl<4Y_!&yoV7btgEmXp!&@>sW@i#4iezVYw2`Rx2(ZXG(@Z zjiVh|huUH#XUc%@NzAuTiO#W90>yH!XTaH@ciZo{p%V3BFx9Cwu6NWxa~(2Y)Dp&3 zp*#NynjlxA!T_ZU3wLvF+5=a7Lb%z|FsNc;4UHsKNBO!gZkoXAv$bxB_Wc(d8Iw*l zZ59AcF<)Y-q{_6zb(r?HSmKmeAZO2bnAIR$0=+IQ&Y7&tT)5L*9QPUf2ZY^?W(QjI zqu@TjN55GaJ8+_TQQ~LJ<|vS2E=yjJ{^t|*fU-N6DHzIm(W)KM>T}Vz&Z1FVF&fG- zTIMmjIGlD~jDANjE6b8-B4Kh^>biI|Iz9FBiu4|%4Hf9h@=6N-Fr!n2Ujw=BdNwc@!46za7S_^S7Q2E zGJY7JIE*hoOPDrKB;xV)9f_6h#!b4AtlH!i(Zu-Bl$o9Q&!P!Yc_|ZfsS)n@CehUO zoz%?Gz?agg@?yU%68M>OLyD^uL6%}}vMnb%`W9EahIu@?c;%C8P2w5d4{>1JR|o>XKqA( zZs%k!7M^#hjCm%O7b}+cG%OEWmxqVvC3ixecjiUF^Tjms6IAl^{^S){4CfNqF(JO7VI zrqbxV(wMu-gu7V9lZo}0{D(BCqYxL}Yv%hE+RvJycB++}0uYRS<&rUNkhB+LbNP%O zZLoAzfND*UWlhMFn$Xemhx$x|4$@RIno&6KWi%+tpL&8R0)YaB>tA!-Q}bY`S;EBo zcEJ=LfUeo|50}Bb>S6u4H6`bD1n&BB)%tr30!&lXOOKg?0GF?z?WJWdJEoDt*onI;}p$7G*rn{qjVW^74n}OXRu8{y+uZOiv!d(P4Pn=(J6*hBsH}f_W zzh9s}H)XmtTg5shy_>Gwwig(-!Tj8kwwpT>x>~(}1?86CSV)7|66#(8npGNL*2Ho@ zBBTvK%V*W59p0wf-S~c?Mq$fMXPvoB#;DTa1#3tHk0&u0L#utxBx`kdMWqSD&^%ZV z`yNM~OODqnZ1e1H_g-u_+G`vIg6?JBAI^NipAIj0C=yjx%P#;4BsF-5d%fu@TCFd# zS8H@yq#m0rdX?7h`?n*Fr!(U(0n=T9u9rZG^Hhb+`fkXV)UjHGPoilcJB^Z)o*G^@Q|V;fl7pd-oT6 zPq_(R0!0mVg{@+5e}%t2D|~z2{q|zc9051uoPNK3Vg`TZ#CzUz%Ss*(c~bi{y~dX|3AfZ@`B(Zzsqa^}0*v~2wI zeC3pvTzHG6v`5~9)FaRB#*-bB(^%$H&P7vQc#~e_rBQa$e0q}I$A^5?Q$3O|QwE+l z)eZ(hEe(b54@b-m`>Kz`FAbgG zN0OG3pYA7SdgF7JMx1yP3yacl?eIx(ry4Qwxj_6^)N3j}7;XzrPsQUm91f2TWQ|XvNzzxJ;;aTr*6Y0ONpf z4Y+3-CnlEwyEtmgG+6JQ>;b*^e$A9?!D&FM`;aS4&pt@MKkOO5%A`KaM9r`XHJk*% znS9#PfcFuTuSisoNnlnPFN_3SE&34CeC^{Apk12D()jK>CJ>AWYb59Wg*iuyzt4@CbUJ0BAgdei?^(t^hw6L$->hOMnuO5FT*p z8$5{nRtT3d5@6!p2TOvn=OhsQ4*=Gm(eHr-qydYnjk`(E9~qEN3~<&S)<^`p@zJi* zOo3N{qAxy$3q-e&fP=9>umc1n19AN~Q!ny~p$aG}1=1CsH8O+?5~(THurU%94-v>f zgtQ+4!0DgCK-yKxXF$Oh00ePAE`{?f3fM)Yp7(|b5`pl^SpnGGEOEZzV7~Otz_UIG z1OZ~W2C_jg!4Pe(Pfa|Z!UCj0OC;b&&P75olO=(M8#A>u2~d@tpG|`rPtY{pT^jl~ z(AY=KKrrSZ(2O(DGNeJ;Cl?zhf#IA>W^ADO3ToKXFBsq=l>-EBKRb(pL8ZIyqvjC@ z;_$Rf%mR?%6}2FfkR`VF@NUdA2QE04`V$JmO9Yrt0!ww5Jtts06CRvLfaQo4U*;hA z5j7Pv?G+5rCJi_b{16E90PbrSAee=<+%>zjm%+Z$r(H4ejJAiRJfI<^ovi+oYTYPXkvS~Y%vK_U&9ZlKB z+3w&eTk$13VZA%ak=xH1chZ-4GHth0ukPkjcJh%sxraOXy}Jp$yQRI`70BIcqurWY zyM)x82HTw%w{~8>-%YmNtF`@>W3<;%vKME&oA&-&4Dwr3$?l=rzU*GdK;-^#$^K|l z@lfynd&>T#(f-8MgBja{&x~(AMjkBm9xSD{%`YFUAYY0gXjgj=zSrWZ(FC%bTp0|e!N7TEPKZ@!B%kzL)0=38rbb+bSFCxL_SP*SaR(k@76MG7F zCs?c@VSWXYTRJE9B(|uz$Qt_!TVeG2A624h1g%wICCDTw zQ&Sq2-T>F=#I2L9l5VhdCq>YzD~!Q#TRVeTa<(tsa7Q;=;mH*~qtUKjKIubR)eNIO zgJPXmx5614uQr*=?p0a$XWTn5uGW9<4`DJsG_A8AGynC-_{h9TqP{bn$>i9w&8y;l zf2PSN>#irit{}{&C&->~((7s@bh;nA0~l^&n9a`YhLZ*F4`>HjIE=qicm`!@`}AO{ zNGC__mH8K!*($3y7?w<7=Y>Z1)qz(Q-#k8aK2a|IWpUy8u|Gf#enlJPz1rrwQW2hY}(TXj{Jq8RV5^MP# zlT5KzUd+g|i4azt%C2`HyY?}rf)rBx7Q?{9I)sb|d+n{E$vhRw=pe`-{&ct)LMWY= z)rm9Ht2S>JB5;_`H2NL4cXFvo@EqOOXimjWLSu0 zjkIr-wuU3{EPmFG3}-)HQ-W!Apg^v5HsCu}tkOK0Bo*nLcwjjvRI-~}D~b@pTu~g# zo0L?MLA)3oGcLO7!bv1dXMZm7#rP1F+PodO2A13%*1Z)kRwJGX7YYRK@Gv@|qQ38k zj${akY@X4}77U?M?Ql<-rji)1Al?a3KN%TvQ*uY7+Nn9ALNC(RUJ}AhPuVh^)xKcy z@av}@fXEJGd-2H9oEny>`x z>wRxKL8aRY)zpUyl%^|#tkd%Dt;36>LY8i9O^1gbE7=z_Qq-kwClkE1G46uKd-aF9 z%$%^Lo3Tc6GL@F4XJo)x0ScSf8d zDHi#f5uT<8{m{(1rkb8H=X1I)d1<(Y7Bqh7GaIPW(h7304w_Gnh2Qhj?1-r z4@(&?S0ugGP>LoJF@s0$lu<~WYQK5;{$R7d+r<}zsz@}VViCUzp6RVG^nm1dP#FYgS9nqRxkqqj>-Pv0G^@fKxaq)bd#Gh-3Ij=q9c?$VAT5mm@$MaPiOptcrb zakUdv1nq=ECAp$xwjY`-FxO?eWe+dj_ipT0jU2C9TYl9^6p6NLC`%c=(jfJ%maJWn zRm+G(iW)1rwZ0<2D(PYfPubuFQIg8=fWyg4)%K2lM&cS?E0~<=EkWAXEffNtjD|&n z#$Tmge2KHA=nZ>QW%DFx3fJsU51&ezZD_0Qhcd2#G+)g&J`C!0wU@J4oSl^{Y;^Z< zXE6Wn=}_AtSn0tOzH}OUE{3J?@ZNeyyA2P(Jxt=^TO?<-Z#>tgfIjwXY_~f0pKD)z zujbz^XML73*RkUs9XQo){k3VX^Ak7Wy*0V}mscO{=;%rxK5fkZfY!c=NAp${dX-6e zM>UW88!2JPmv&?%?*;ov0$wP0N8%x0B`vsiA5K;m$8W^f1A8m-3Tf0H-N%kchYdkg z=bZ0oN2Q>}7U*wjBr#gVwvq`c zrWh7XZuioFZRtV@s^)1gfZXjJ5A=?K^bozj)yIGfCxRQhV``&=dhVs4DWkqQ$WCyz z`?l4+5RetM3K=weNQkv2)-_CuYZDO6xSHz;=C^wF` z@PeK+5Ca#fv6UkUWFzzUif+V)xprKA1kX)k2%BWq!hM3sX5x1++ zoxO@77T925NDEiAs|BGg*Lo}_dqke+v3`fBud+pQw5rC-cw-KVPZ|kxlUpJ^2wl&8ES8_9PBEW5Z~Q{c@_n zM(#t*Mf7VRgtp0sfRwFOcXn6IU9Zi)217PqOR)z$Eh+=|I3j zx|4buB_G-mzyN!rPcJPZOE%wrWE(0NsO+vG7!n!s`k>4;7t)m2Q{-8rAdZIY@(W)H zL{2=GUT{Sj_cTIHF%%afL~aV2Od@tkcw}qMCod@szQ6AI(f{=_+F~9cEl_CYmvBe+ zkPcC~2Nt`TVn~o;*7RsnY0>5aIC?(Zo+`47BUvUg)Sas$F>~+{e%s>%l>1Qce)}zV z&w)^&{;8ZKNsGF}5>$q~kB%B=hVP^4o)9W{K#xXhVVCQ;d(|CFA-6K=%zK;gTA>Zo^;Ut9j}|IF z>ZL<)4{MD;Aqk=amD)+matRg~TP!p(Sk}4}Z=?faYNUzQlf!UHNA)Q%C=ZXSsCRO~ z;=lC2z*hD;#a(FDoIfCKUkts&me5NS0kkk%3kR)I(D*R?MJV#*Ai zJfLT@1^?pbaBFE1f~gLO-`=VW*OZttBbSO~YTInb__)e_r6zLe0moBHxJM=?D7^`B zYS4Hb4nawpyu*qWg+>}C-Esw}N1C(R4TZ^JUYoow#9AWU!l%)i;xicj-N@%H3RkVD zqr}iV`k^E!N#%QZwjFhqm$FGCG}yi}jqpZkMN!fbW$|!P(k09j@hQWqB>HL0CpUoP zqDJm8lGm_}L{AZY0@k95q`;yC6KYvG`4g_F5uo;a?jp5NK*l8f9$KR+>4%cwi5S`w z`d}SZZqZ7%reu&TeSg}>W#mQI@U$p5i((wnQ#atPopGm1W9SeBWdww9nhA9%juw`7-y7FBu(K|Lj-)Q||dt)%mbap&6rjjNg2OpHOJ{e3X53Cqihe zXa1og$!5!Z?3elHkOiSu=trW3my!#Ko!l=~7n1E4Qof|P=$WM^FQgZlokW_gmM>&& z#TGmusWym@nIg`4zu?ijupyg}r)oZoV!O^qlBdHBlo6NcEDGIR>_I*6WGgItzZlog zi3JPxyyR*f3Soa6Q?s%d436ctS-fhU3|SJbej&S9pS;u+2qA;rbhtb!2!+UxoP*`CMR)$pxA2I2A9bksg_6b^PVNYI(wh{j2s};Cm2pMqYx; z#R?-I`TepoxmL1jgB-HUE3iyR0tGVM{H6KtS7d2%G0q7|CF`_(kBN;D81puWDO-K` zN4AZr=bTZzV(l?zYq=4Nlw@D;=LnL+z4G8EsKs>1bIt)s%?+kY=12-ODZ&c-g!n+C zh<&eu!F!!_V0||*zF1$~Bb~nmHPE7m$Ixadk)B`kg@*7n=g!+4co{~h`s3mj-DA)T zkpFeO>IBFd6;_aZC^V0Uft|B7hE=#(2jTE=o+af? zD=RO5b7!}V=6cklSb>kGuUVYKuZYLQvbRhvhRyfVi=BUzEp~aXXUy)s8Ys=&CO%%< z%cb3)ijMVWb!nB{FZfE4(=^|u;ZpQeFgn>KB)6xmX}@A%zc69Ga(ln}>wfb5{%hKU zS{~OZ*g;{P+ChWy!HM}nlm9`pgKJC5LF?E3`r?E3frG%tgO2Tku9TkguLoFKx7y`{ zZunvE)7L#}hy4zRrv`@un8TrJA#BRwaMR)Oqr;Ke!!c;g==R|R)V=KFaFXZfom%u1 z{AkA5JsWZK-v4M0LpY2%S}1m(tv*^BaDU}=w7h-v!69(Lsm2O2nDhaWEs9d8&P zFX$d``Fp(gINounK>+~!cYFu|umT9!fxy5ufEEB?+kj>=<9tU;n5mePmw%3`u%xKw80Z+VkjaN5IgNAyGcZ^JlJfIy8b4ldZ$_fb} zhW)d20QU#N{R1$3pdEt$&j(u&9zYJz;69uIiVUAs%8i;sVO0K9MOAb+42r4w>{<@q zDTh*C6JtIZeb60C+z64L)Sp(5BEf^@t{uKBHYn%vcn3w;ho|IgtKy0fivcjBXLfW{ z%?$tqh6bxh)La81Q?iXIj%2<#qVc&}WGIXFjb=P<5iom-^ez@ z6p^k=YL!3r;s!WSgy!NyFjk3Sqr2ddEOx(?ftRJ>vW^_O(+ANg{!a4r%X>x^^-?zE zUO$4|uXX1yy$kD3a!Iw94KPrg33Q(5^(bqp^@a`@*u-TS#Bnr|>u0cps!-gPj+XJ! zPZ4paeE$=P_-7mbpZjwSV8rdu6`+~Ur&^xb9SUnt=JRv0=?N>=e&yn{zup~5p+$;2 zGx>v&6#^Mz+s^rF_sUu0_h(%b`wV%t%$EJ4rd13pw2QQ*kqg|Zyne^>^tnL2RvAY* zWwZziAQ%^LSJM14qX>ZUjXW_DyxJAeSCo_3qn(i=6FhSI6LdwXp*AU){4rafqxz+?0C;)a?XpU6j7%%*(N^*FVq&*; z>ZHbsT~ubJgJ>Z=GhNOqNv($O7x#5mB*Gm4LT1zE$B65)+sY^J}vA|;?`ncrk|p| zbD!G%R%O|A?pV@oIz9o7Vd|Sn2z^kpQ_==+NpaGxCyQe zjex0Mt{TgEgJu%1LB4zx{tL#qgDK0_RNSoaRAfqt&lYpEm(u zUWxWlhsi#LMF41Ci@nIfIK9NXk=t#?DDFs(dBKXpqOL6uG=?eqc*+4=U>GxCC@F!&sgc@050euzX$$^Rr+7Fu))je{>Ur}YNohp{0D>zq7$ zFgN=qNy*+)B#=n$DHMQ7gmKLbO>N#`4SuhoysMRy|Iko0F4(SdKKA1 zyuhTAL*C(lA8I{>4Eif!fJpLZZZFyR%;_lMUA3s*YqzQGlu5+10K9kzsbU{F6qJWY zDuK@)rU(N4a~teHEC3|{|LcboCv2Gc1C#`!nsw)qY&{Vxtcs6RPAOEvBE^gw0&9^2 zijgoyqJ>%}jf60A1rC+cja)?m+idpposX<3&m6KBzB^v)6My`*a3Kh)!yqAdJztU2 z;bw+}eSAK3?2U~7;R%IljdhozFYhcxnrHkVl;-z*1${&uvy_MY4BEDKr~lfws|$9sQ(G2! za!U5IP#F2UXm`t5vg9T$03GyQ^PMzfZ)nON#$_=EZDFeaYoQq)QK!3-`iTGtaSj~w*+>6iTMFgm) zG}0T_S?Ri^ashm&H4;W;Adox&0N79fUNDw4jFPr+Ed-w#=#g;x88I0_OR0wd^LUD5 zqM-Vw>*4IOdLiOAX{KGj zSp%>lVXVQ2>#+jkg>1WukJixY*CFhRY9#!VlQvS1d|eV)g96`1{Fg)E2)c!1OToVm z!Draa^j~a=&!-ZpcbAnb5MNB9R3yJIij0OVYe?N@Q2sHDgZV=JT25>bql?6M$2`Sb z+z^b)O|iV}Oihmv6|+g3q1!jU^ht1tZe*tfU2TS?M@*rCCZW%qfvll~q^}|D@9rOmf*NS6j2*8wvTQ z1cse9dbFoF%l!;At9DEzzRikcsNePgQOYlE35fR$g7BpSQz|5S2LS^U`mr%z#rZNq zC(jhu4EK2eA4Y$&ZeHzLjNZ8g^hlpAtpI(r2qXebIp?83aO)KgTzOkuS>^^5zSM=z z`Mcu@0V*iU);4UqvbusBc%+&zz<6h&@%N^mGFIGo5_j=THtV#!SLE^3pDgPI#0flK zDb`{6ibjYFW7R#*c_w&+HdI#Nx5>_i1$5JK0m~4 z6;%;bI%^AH?5|Zi4BEOFbbsAtYgt6EUcEMlev8nG=$cL zY5E+4@uasyj9{X+eNo&xE-Gc-g+nRe`+Sz)4eUpg1O;d1gTzbvqi&fDkX(vyO-2*m z&FC2V&yEP1bxE{IyUfV`i8(H;1b`OfosU9j0RK3|XDU6_!&yIA=HS$2QD!F@}#v@%GyPOe>%?5g}4Adl?i zIe5SlV&*eOD2PBngiJe_mo(5=bk)*12g$#pIeSnH00VC0Ry+VS`W^5j-FCRhBPo!Tv{H&G3U%LK z@bE1i=**luw(Rsiy|eOTPS_e7OhI>S98hf}i`VOK0bgS@ApihRCctqGX^u}8xUQ5j zLIYm0dv7LqU>=3~_m9~G5CZDBrS1O5(h%-2Dh@8`nzfz?=rz{Z`GobJXi5TeiBhkR z{c+b7Xz^tX?9^kp^~tpLYL}`(E0nx5Py!w;%!C z`&xa~92kJHe>G=G#RHLyN|r2%3$M(i?AFDs88>=X(;Swkkip}E+sF2XB|W5M!`dNg zB=aJ#s_+P$@N(ngp>Y#DCS9X`XEx(2SwT&eFG6-?7@*!-ay4@i9yTC009ac@dnh&T zy;ma+8xb<8acb@1rgL+g$d<8qD#-TF;>AFFfF>Y|Tl^K!PROiLgX3Ko73&O7f{m0r z8O@z6joOchNP1E8<)Ie^z0WD+qM6OeKlVoOXqIz1zsws*7LjXH!GYgE`c3zJqe$`1 z@f`feU>3%6JKcN)0k3MV4xT~&1l)A({rR4ryC<)_ugjO6#%nxH{=YQYhb3!m-Jep^ zbEqt4Kq#ewb?wYlyoa`3r)R|v?+a7s2r|i=p43@|6EYch9Hy`LCtT;ZUQQF+8XzKc z{Xi1*8QB-5aP1aPhR=SrDGS$ErNj5b zMl_g~NV00Yw~Ouz-}u=Ubh0$`+BHXT@xrNRAo9xfX^Q!et<#<6`%>0dCwo?Vx9OUW zpFDmty0AXt|1)>g(jl6p`@+t7$gXgy@w{>Ee@(*?RNJ~E>S66`cW3<^;*4M-=hmlTXOA6X`_XplHB zGA}Rc8Apn>Fqacn8wQicWsO{(ASI_O*g41E3u@_sLL}p|S6VW)CFOF?X9}p2a2$;$ zF%j~DK)oQMY!^}3PF+J%G_c$j3-uv^DiP9N7&FEe;6`!@wg)P9i~5OW~Ts*rdD4)W-6~8@TU4 zA8mY&I%cVwe@aNzW5Iu z*2O`udnU$+8*o~Ee&YY|YuQ!I`1>Xj!9}B7kd)C&9dcL2OuA)p`5U(ukSO9~?e(B4 z6=Ie}Ha#?a-cvb7pmEZmRl~+r9FYM*dg(}v%WuT47u@yL5n6ZoJG-^#sS;8NA?%&;iyrWqfiQ5wYbpJ`_zpF5nGXCG)e`;fQXe;Lb z?*9Agw$QAxH2g}dxq79YV|~9ds`>Tm8x-Ed6)ow)wcbc7ervZn5nT@x2CZ!ERCtxb$}E#sju{K4W5*NuH#%>rYNV zbOAuxi|A$Y3v|o}E5i_vouiQJ8gdS0TPePTsn&_@fm6(Cl`!WO_ZfGe74h~4=`~eD zYhMT*+e!b{tH5oUG<8#r9TFCHX=UQ4&yMMo1jjo-m?WRR?ZTM5)}cMj)0T!2P__qU zJH=W4j5F#JzNd-=yh)BXeNEWZi3z95h?HPg59M8~ll&qbKM*v|@9kB0d@X-zdq;*! z>50^(%VF)1EWUujcwb&&@mn>1_lA*eUkKrzqTEr_vcI`t%?gHJD0MYN?znZ=z_i+S zuj!!r;X&Px!gp` zqS&aPL@tpSdo|l5D#+@??EQxQ`OnvHS)Rq#4e>RPo(;1-V-_C~OB4_vl`hd1$I7f8 zicQ=mc;Y=K{%t8{LZw&RS4tMI(`QQg_KL)mp8Ao*`+G|QjkAUUV~y|4B3PQK}S!% zcgB1Gb~}lg8#c0!k9t-czlmErO+O~II`nvSx=VSj^K{M^4 zDyYC73-RYhlO-9yDW_AcMbv~0zMGevNs(-ROX}gtlunRnEZ*>f+=)|Qeav82vM9>k zUg!|HENv>rXfS45rr#epUN0-)DNjQIL>3%K6)o)Ma1~9U|l5W_W z&XakpHkKiY3!d&bXX{wh?k~3Jzu|F{!2yulL%i!k=>fR5uf&Hm)g7(Y8+EU=ay*h` zz8tvbr4EdDKGr$-(0g?k!(!DDbhWI|h)2=6*r=Jc!nLT^+Y_YuY zd=(}0MSTsOp#Vula7*s4g_5iB+9-8sJFSP)JCv?t$cQ4>B4n*NKSr{pIDL$&QWgIg z&D*s5F(&3-Jx7$ttkXuE>r{S$VAHNCaXbQg(@tkX;IsA%b;cmOms&h!g)g;kwT>p~ zaV&5pPDuD{CEqh=uurvkv|yKN=lz{4)ghjt_@y}}%09`tjc_N$sL5Mn=(BH$uy`g9Ec8XGy%x^4@RxUgI8}JNyvqIy!r-?K1>#kFFnKnixIkZ+w4P z`K}YXA~u9QtE`!&38!yy%^!ky#*S9mZr2A4!){neZugyg z@*%6<(K8e(-L5W`LQiepgs7aH-_L zt?SfyL#}28S5sOyh7QPn+LJ!_`|0HO#MPB|#tO5Md<@r0``_l1hda*aD!{Lt{Pj_t0D z`LXel$W=orpsFgo@be7AAWSJUN;G1`+?M_^fwFK&l`pc-gi(%raP${>WL8}$G=)8e zSX+}eA{Z;q>(qVI9z;Ac1hCX`g-APb(zVhf7rzcw%4dHHx2d;*y)S7?mQha14#N#_ zfJ(w|Ra>=bnetg_z>T9v!kFZWPL!IuZf6u5(ba_0)*$Lk!cce&B3#E^$dH>hpqHeP z4tUg)5cfk|)l2DWqp!NM@Hw3w+=!(qBAfM@MP3Eu9_hrVuw%T8a zNpxMgs8M$%piP*|Kx#>G;gjxOlE<6Mp4Gu&IDlIVL3f!3){dem^xY3p$v z?(2;D2QCL?Wb{XNnO@e5A(ZP z#O?G-n>BtfYshv`U^4Ago3)>LZ~GAcz0U34DiXImjWNAmJfWI!lEaXY33^35X|{Gp zW=RgtJ#VJuB+Ae zSf>&9cOwlPeifx(%hP9u1s>%2@dA7npi!6Eio#`<_q#iS0hhB#(@t9-Nrn}Dq15c1 zijvXtAInYPxaM@5t3=M;tzSdWImpI6Az2{-)lun+*`0`Vx|v?!3{!o<15Q(Cg}ja9Kba=cV9*xgy+ zNBmb0F_l+R--va?(&yVgzcPsQlYa1+)>N$Oy+eF5OwW12k*u@oH<<@vU&RYREG1IzZ8MR@=f(YQ&i4uy2Rw?$ndq01pINaW?iLK) z2Oz@q$K~9*jArN)HT)8G5PTpQC z>U3hG5=MQ~yu+ztT{(iT-F~J;Kd<)%loLKvCQAFmDJ?^jjC-S3E(Dvyw{}}$%KQ4~9*Jr*2PjfI7;kjfV=d#fmOv=gTB{+Al z6HvN+YYkA9oLCf!*nUiYTsIRcN(E4$RD|+9cLR-IC1(Pl@qK{}|0Bl33jFM;JU>++ z#d5IeX2+$#HzCI-uisvhm@vJPov7v$bhR#tUkaP^f9qXw#L%oY@lifhP|xCYqkXsU zEq7qhPDaOzm;KKkSORSNaXJj>_1ha|91%XKTm*R@M6MEfbkJJp#5tC%(IM1 zr4uEV(1}Q%zb3%E@E0Sge2-)_Dl_>r0S2De2yMo9iSl+&jes(FTQNP-A*E{6Zgmby zSa)8)<$$ZP?^M~KW^?eq)~2Ff%Y2u4>*<~%j|h9qRKgDevp059>!dB~{S=Wxu5%8_ z#UtCtcE!x5gw!@W;u>yEKE&G?#kmwguxk`f=j+~Rt8Nr=$7X$hu!3`Eo|US`K`rL8 ztV$&xNlwiMzP_?wyVcq8{WG4sJ^7O=Gbyu?eEO11KQkc`?e@sPChZZ?a{xMI2E9*0 zJ%~r=`vUY##j6+Jl2c?WmX=Ne3y}glq>+0t`tPnsu)#OYBDwyAV7$ep7w&QJ+ZODD$E7Qtw zV8C6Z&p;#L%l9FOM0dKNhbSpH&8*u@IE~xnk4LgFlC=WHXZuqaVo7&zoXZpFlPskQAQMbL z^+{Tc)!FmIat~+lsP6<9!J8~_uMCJ-%Q*+*y#ro4w6t><~IcGse zEpD;FZLEPITlwG)AkY+lPh7fXfmw(?i?q{!E2MkKciTZhwKR>4N&m8m}HZ3f9tKoY+7s43mTCy2rCstgxI%d#mNVX{;6N^CftmyVFyxa z9*heFP8Mr{86T7v?&&Y-{HX!)Y^q=%b5LvlB@cvT3OX*-`G*qaG65ibA~q4PG2{Bm zg;G_8J8P&=%4JWTk@BVrBhU!UrG22n9A3SGk8_WDXj7%UcSkcUQn6wl`1sQGK9vlT zE(>j)@6^^7nEwJm?GMIYMr}4HpB%qsQd}d;EsWReOtVzllmOT$? zUVFS%kxtx-CB=ml?pE<)@}ik$Zz*-_!xQd_IlM2$WB<3y@;}m-D@Y%AZvO1o+CS%} z$*P@)oV~|&#b3d7_XJsINkwQZAiZWL_)|VaIww(JVkpE^dc^qJ z#e^{I&!*7HcyEtp7?T5N`#F4=r-S2c3dszcKj(?tcosjXX4D0{ncLLEqwT3YTs1Bl z%G59x<%2eP#Sh+{Y_;@EsW#!6v7=hoP7#N9kXUPcjaKd^^;#IclG+_B-JU2VY>$BCf_GID<_kHjC$nkyH}LvhOEXiKea*fh|iLdK+ zNKacu5|-8Wso zeq4M$SejZzran0YXLmBOu7x_2q?C%yNsF`o)y)<^&D@t=H$FYv=9p{o< z4BvjKF30qpbX{r1{qo9;@6HuhH3NJ&J1r}^kV?gQUnxMt(xay;7>=*(*2F zq&&hWeSKnM`;9H9-BxwoG9T6~eRUOk=j@pI{lHl%n_tZE{&y9rYE!Zw$Hs%+V@+(Y zwRGT3Da3VMZ{S+rBo~U8qTy{WR1Z5kOeVHUy7;KHj*Mle#zZ}KJ$_y252>bd3R z+qR$h=~nt$eq|A2sB-G+$HmU;-&dL;BU?w{Jhh7pbu5^1((7u85`grO-dz5KB9G0* zzm|d!6ih(lW#&SqhPut}iD-S`j}&K~@1_Lv{mY8{AHnbc@j(p-5G;QIM8MX6QwE72 z6NYKj{sjLCpue5cj^=dctvm{2#CnGX6(gL+*rRNaSLaMAPTpTUH*zL&Ko z)d`$B4xiutGni1ow9E-zj2leoZc$FlU0kF(qGkNj=*&3JplH~*9)(BDs@;OC7vWmD z6<*O&yD`WTU=|)bWn3i`(D-GgJ(cL=#9sg*D}b{m;*=a>sRa+BJ@0EB*~g>^v~yM5 z+ZcKLY4sg86bBH&A}SP?WW%~G@{NCrhD;hDFKHcKE+l<9vMOr%_Gzv6D#kHLKB!yT zi_JPO`(QX6%ITz;^Q<;V?Z-K%Nilzn)@;=)cawhj2F)LUp!K(;gQ|A-DL059Mue>F z^t1$-o$@T7;sAo(<#5W+N;BB2$s`09mVS(ux`|H!B@GkN65@zW=*H9E6bk_pN8&6? z4qhyXzkTLiH<)3_W)MOmfgqrDBMA#9ye^B$Q8%(GGfrk**v`>3GRsHRB#upR4{gWhN2Jh|_9pL09{8pnF%`$q%b044 z#U6b+#%1qSW#+fVTSiHD{M_o8SS8XU)7_0#`D+8=`;GGsTJ%0HZVBCxvS=()$g1#? zkA+Agw;Jp>+0zGME>w@H!teG1@sAQEf9jgj#9V)t?lK!KhM!n=)D62adQZvMGPiS-5VZ_Ap`YP9x-G zWbs}eNxI{JuZ~o^eDCKa&w{c^7^xkKKTg|j5_^^HFHhd{oJGPDP1jyi*A1puAv^&4 zh;(mkM7pf7&;Kycr4~%JS5p;OHb1#0n48}&kW{t?oKha#&u<$wVto8E`OV>$ zfHlJ?vW+sW=g?j>F^JugaXP(2dt0PnPs{m^PkN6Vk7RwTj=jHO@?@+qD;A+AMF#et~{sJ9$7rHJ&C_j~g3B+JxQ zGYsM!d}xjU%3E{+eUG-g&+*;>U|EM2BFw-z0_+b%7>A8%?d z3qN|8E;_d@`>k~{?wIu8(S%3BOUCJ;YX1_&;X{9^ocWb%|0?`5`MB%v3hKs+PMVr41RO4`t!`B>-(42fi#CJmz4Bx_L4uL zn_XAW4V1A9{UYIn!K;Lyo4cVYBB47>>!(WdT|ZPk(YQI`Kyx7WCYe|09=|QoL|PxO z@xw6HVB24_CT$3^x`YUlyZR0VO#U=QR;i`E6$2{Zfvv%Az;jd-JklFRw-$orp=N86n27Gb| zbaoY}zE4K#Hst%X*mD`0M6m`!(v~LUoQ*;mv;(n_?c7WYw_GNsz;Kd15#rH?Z`o-M z4}coylI+`-4_{J0YrENt z%S|?4dDozItR(2<7Ion8`l;mu`}<1^{Z0NaO9Y)PZ(GdulASjt#<}_P%Ps9-nylz_ zK0R8Jn{tCckWvF01RV=HT#lhs<>+}^=Je^x_;rEtlI?vpb_PvadnvZ+PaYbwt6sa9 zma3yq@egHIo4-m84Jurn%Ff9wuue3;R2_Fu&it`4$d|$C6#2f+#|fnI4|m$(`rD*Nk5Y$cLLw0TA$_oUf$uk z-nw4#>uiAY$)?Qpw)L^9z*jfAP80)vj!WJR$|Yt!yE7NGMR))EJB#eoluz>ii@mr0 zYWvapd~pjYPJjSKgS%5oad&rjYj7>a-Q9}^cQ2(tad)T1iWUkKinL89{T_MFbDleM z@BLwB&0TZ<09F=(Pj>d}E&Fg8_&E41_$F1z3bEq+Gcoc%__Fig$y5CQKk+XS6EuX% zj>N8otngPb00~P0u`W|qB_a(V8mmdSGIxqYOfjDjhA_KlhZ3T*elb!AnGs+oA(dP5 zN$hWprEh0ki`23hPwy5DUTT^{mPn32q%(i=U+5W2Bu0PJCT?n%s{i||& zuvBSRNi{d?d_Ka9x@&u~^Xp=dobltY4NY#9@VBS@WK;M}3JA@cC<}K!Ydyk;G}&9F zyVbt7;Dz*aWmsq|2IXmS;<0es@oa-R_!Xs|o6Lu3;EHB(lwJm~#W66k(96R$Wgxvw ztTu;+1HCUMoW-_HJ(VCw)z4H#~QDH{Ct;Ts<<-|EkxtZ~~mr!4v89P#sS zwnxv}g4Tc07~agaX}*^7s;v0i`eQv4(G?79hjuK66h7}YX7gkPh(C1# zD>{pf&=#=>S(25iETfG+j=+`)C^82>on@TaCS%@FG27DEZLBoRP;Fda5IT~pD&(<( z0IpCsdL{vN9REa)>El@YqVPdO)smPA76$}ytzj;W7iD!UO#RsHP?2brmspwZBgzcR zk~(FsD%w!2sV;*k*;o5rufrtZueCgZwQVa-XZ2l&HD?XoVF9d-{U2F13TqEHrfP%t zq*R-xd>)-!=j7PV+m`f{U7Dd*fwi?`QjU`?D^YA0T?bjt7v1Ylwe|H|KKau;E5&SI zdw0c^zrH)fVL9qa)bp$=JjcnMBy}I+8D+oPtGyiT-P5uG2eaiKE$Q@{Q^jobQjX>@^^%@`zr;PCNU>i2(+2GRabbD{@`9YF=!r4-W>yCD z3ij4rZxKjD=0eHDhcdjWO1w;Iy<0#(JMSvgiuKUApvho;B(NP**s$pC<8rexqxFn` zQ;z=EO@V_w5juGL{@Ex8546+;IK8eye?SSzxny7)KL9eZ#chg=V*N&xvP}$qdJz7M*r3)pOj(UQPxCh{F*b;*Q zv~m${-fM^_=ZKOaBd{5d8kOK|GnliP2xC(XR`E-<Be*O{1hGe(|3e1E0h zn3%=z+HO8A`(+kK0AlUl(r+?<7mfX!gK~{)=Uo)}vn&;Vo`aFhUy!~!kyiVegr_fy zaC;D+NrhZ^)W68xKQ307S{F@=a(`KBao(S)i_&d%i~F;7_jXP=k&aYe9X^f8%n|jkfDQ|Kf7yTjGClIU0XB@GmZ>U=oV+hs*t>LjA+# zs>}jO{}^%>mPZk4P*hhjg3FPWMnDj2cf@!dLJh*?#X@ko876B4m-C~J68(N$=F1%1 ziDC9)PuM0-6L-bROwkQekgh2)&S0r{p=)cQ`Q*sPb&e`4Cw(lmo5Iq{!!h5(ZbI(R zMW1fP4r)%pWan_f>KIr z*kB1yYS8P0udAIJ>g`xr8zxSToSW(cHqgsQ!~PF>LjU#^nBe$G|724H zcE&mX>b^%pqDBBLzkes({Pze%9=2t}Vf=T1^>=UWU)Hw&1hBSk<}C$OEo4MnEdKqn z236sBnszj}Q`9Su?q4r!I1AZlL!~lL+y8l4!*$qEqs+teFK;cv+P1w3yrAq3`Uk)w z>5j}q*Ih9gN}>PDTdQGjUAU4fz0z{@2VlY30Z-N)vv&?C|03K?lY+f+(7ZbPX3w*a ze|c;F0$Bh4B)w7lPlWpqz(Nr2zdcF+a|!p3W7&Tu-2bMlV%rF5{b$1cH(m7);nviO z{fDmlH^S`*D|Gt!p9r`9q2lpB2{&_bl=xq|>L0>QP2u>LuKI^?*CgjBIh|G`bk)-u z+pLMxzX81;KL*>ECf!G+KZLvXOXCRV*%!o^-*jwMbn~1RSg@-_ zcxyWX7+v3rEuQ}w^KNNNbl7g3dqrm#Z!bOaFH+mQTXElBA-uJHgEP)H@3zHSk;oe!!PEseUhu!k)qhRIa-kp~O_;NhLKGgN`y}yR_s##UsC#=!UjV zk8_W*w`Q>by00bPT0HwUBX$>LGcEaXs%1tTF z24BrZ@E9gHUo$lbH-(-l80*X$6#5Ezx4pgpoU^~tC8)PLm?*d@_RWCuiM`w73AV8# z$NcJnlsu(%8Rw5LC2r#Fd^5c&2w$CfudDm0OS%i znJRJIxD*{g#15*+pae;26Ag#V4jTI2i@RZs5GH<0B(5=OLW+YZIH6PtrlX%U6;4Ww zf{O*7?)`hTiOX2UAS?JDOonA+E>c@79p$U9ES=U>obZ=Y(t(@-F7cFDBT9PGMF&Yv zDa}M;Ryv9@EJ#Tsf z3L3@}Mg1r4lFfDHTmlY5>VpdzjR*8>z9;fj9q!pLHuhNDMdZ0UQe<>^SJxM=71O zvP<|E_+CEHKLf@~egz7;4wY?cix?5tOzx%MQcrcGr1(F0}mlnY_+sM0p+u4hkTO4}8=FIq5e? zvcIvV6nGa4&%h3{-Q6k)3ri)XUMEi{t7ej~Gm0gEr|P*Z(_s|td1Qok>a#2=v7*Z_ z-UZd@yy#l0HoC1bn6A<5oX}15`l`3}qsCx#qr55Yw$6bn&2VvIrDI_U0kE9&4wP1V zvX&eCrky!&CszCD78^qn&x}x&4VHi%hz7qcOQK1G_Z%I~DFU0;^vZ^HsvRvk0von` zlk0`99j(RB5e_Y7qol-+wyMZANA1atu=?Iw`v2YKUl}pJl>cLV(GZEC|I3z& zh=%+_pyjb_0Z{#MX8&V+`CnTM7`4*5{b;Gr@8Be(mKh&u`TJq0F2KRu-w#7M$`N5< zyRFk{OHPYWRhpjRk5>QLQk8x(|IL|=(wtyIp`d(lQln1b+;Z>%upaQg;J_QL{xQDA2ez=NjXtu zUDioq(n9P;ZR_K6=XoeyXtHw;h`O%N*0`r8XI7hCfAkl=t;%lWf_Ey1SU*dL1$3R? zyTS##DkI1FT&Dw~pDMt@1y3&b0UWH*&434lK#S=N`+$MFCd)W#rtRGdK*i&~>_-`O zDMQBqw;dX*qBN7N1tXl<1g}s`H439>qL{4|dTe$QyaRKKO@&Oo^mq6Hz4A+f8@&r5 zf~(v}TTVR*2!R%@9+L919~m*eI86=BF} z%skg$W(BA~w@~nT1uey%Ye~hocZ>dgBf(TpC-^bSwsq>%l z75_zWLh!Hg^=C^J-BfMw(`rrOSh6@f@7($4mU>)Sd(rcy1I*rfG4kW0c{RB9T&k0w z{%ha$;N;gnI2`=49|eRMUsy~*(03m{zOC=Wk!Qa`_@@!JATqErbcB+A6575j+>4Ls zY+Ae;XNJ{XPq24>x$fH0)i8VTz1Oov>V4fL`m|j0$N)rW zv#n8O=;~%%!rH`pg5RFcyJ^|$x%Z+VAOiq$A46=%qIjD(E7G0kf=&AG3%_vSG5kVr zY3wZ(fTND{Pfnq`*e{}^;&7)RgV%@7+1Tjsjet5H@@CLweq+99%zz&oQ790;4@0Qr zQv+}r?I&V=9!>RSQ6F5wxOixHSP_cR{2yX0YS@KDH% zip%RM)KMFv00GZS2tJipmsI4-~5E+}I74d9~N?32RD z*9K9Aq47?2p<(M|gWtUtkW3jqaDnH*M0Tr#f=z>{>p4+?=X1y+Hv1HY4oIaf^B4rs z5bS*lv(Y65u{<7nux5l2_%ItN!Yu6!%f}#r4Im*R$k-6!R0#w31QfgdxWPQ>eZOT4 zNWq0ZdDk#H6E>${%~aaLUL=YMPa=mG4Y=!4gLmYfjlix8ee@FNAz_42i8~K9pTU`U&n1Eow{2xS#<&j)Ms*MQaJ*$yf=6=t&kOvr_)bq{IFr5CUSu?T|b3cVTJbxJ74Aw4) zAn0Biv|L{_P(s%vLAVY^#B#+jc9nY6wMh&<3bIi%+7_dnAedZiwx;P4M?6lk9C1fK zo50w$zM+A%K+-@$gFpzV&SsduYQCOBp^*YdqZodpmng>@j=ViFg6ent0r&cp|%jxS%bkY(?6)4#vjFZ0M}*nAC7yn)u=D6E2o*N z2h?*ZrZAPDbMr@@NQTT;ue*PtZH{^0KqXU!W0XSK5Wap&5nrDhh^z3y7tsOB`O{Cy0t)58AV> zEq4pj73cETv2x>n;=EvxLI1g1bi4@KZO{__ws!LF`ddObz*1AxbCT2E`U>~D?pX_87FpJE004cegu<|YkBzf9z9@;Lg8oZCoT7>#+MN zME02z_MnV$Y&E|9{Pd;dPc{O3YqTfrc7k>8$awbq9?NSexL+|>1PpR0423bgqxITw zeDX|OB8Qizk%~r2&_3rf63ydpat@grj}mpKo1Web+NYEozo=C53k1v zV3Sp5RYc_HOiJG35CSysHu?f|Op0RkDcf)$2N#8357j>J4+!`6XrfK$w$n==BCuY-iZfVHX*XraB=L{R_JJ7cr?jY}rh z|C;%ED4d-=-8>v&+7Q|-;+s1KB%LM4d%pHnEMP9)Ha;dWq^roRXXLxCSGDo;Q+7`|BK<{C4X|?XG9@*M;Bg+mClMcYQy8 zeGS12(jKF`A7o+xkzc-%T`Rp$8M5b<63fD`P zrk4hnOE{TuT)v$2w@jR%jm&S{iaun!QnIyGWZ^|6Kq&HaDSVF*bdVvU1o_eK;B%Bc4V2%-pxHOT1>{Sf(n)Aa5^WSa?+18+5r2&ADK#&x$4;1<5 zDidB4zEcyr5i+F_KFV7fdMFLbYQIwG0Ge?znCz0euAid&2wx=xed$VTy9pPBgekl5 zn!%F%_X=_ziUx%}1v)LJKY?N@FMwQwg$08QW&Q;oO(eD^3kofuW0wX(O!5}rU3USf z;zeuJ_0%(jqJH-oo&|-=E-4)9j_!gF4qw2HungLkh_P}YBA=t*u#mP(i!RY6O1cM) zf-n)M4nrLxv7yMVNO34UG}}!;KU(H(Zou*pRjD5>6%AmspRv|~r13t$D8zrGANgB0 z@)0PuWezwG1L8pGlu!cY5b4S6h;1l385u4qYYU*eME6QR=rewPEu@QE6m0fQhp@i8|n z98p4%M}_be(BTF$IRHE;aP<H-UUWWKwPV{!yiaZ}RIA(J7+_Q{e# zEP$yF5Lppg6ardqEvXc;%&%?axE0~#P@k7xj@*!6S5E^a8CRQd zqj(DFn~=S(Qe~PWp5U+mxlJi2z`Yvfjd$su4&XWleDnn%?sx_^57}3HB9Q27jrEAl z;<%5FG)f!b;82S2J>(K002vf$mlwp^g80kXRS6ehJmwknpv%zVx8LV#R3pJ>n@Um$ z(Rm)3<6szc611CS;9bKqGWQp^?&=0jF}U-E2%AbuXs%2t`o3^z(a8&`ZrmEr7jOH8 z(P@*@aGq*fJnLK}d27hRB$ZDPR$<_QygW)<#)5Ca9cvmJ{7DFZV-5*P@o5`N+^5k3 zlvJ1plu6o&W`-NURUPMrM2YDR99#v)UEzo;hNA8kp}VKghLkpr<9t$jTFm-tycpOe z9M-FW`3Xk`)=a0l2uz-+@5QIsTt%8GM$ciz_vWrcv%~~zFw7h2)jO3qdjQS3Q??br zc2f9=V>$f<=(s64=MM44_$VJvXg4iTaD^LyDS+vwEcVijEquk*Vz|?W1|WXS8Nl{5 zNHlCz9{`%e9p7Q_bw}TXO8D%6yLWK27t*-aTf&G_WY!d?`yITSfbEwU-%|mz8f{bl zxW@A&Q=%CLo5)8G=!w>mZ~HN16e!~T7&n`6$@&2tYyhr6CtwjW7 znOti9cpCUd_q z(W{7Q98paRu;&6&i)UWZSTxr|&%Ar>RzT232zul^x-Xm;*sl-zS|Zhwjx5lek8D_Y zJ0A~1O6=AMdOrAW+n1!*fXJ9F?)+`s?w$DcnI!ZcH^$yfr#gJu1>_pRt!h@nJ zOZ)axf*ijH4n~)w#Uz_UGNZwN#^&C<<^^hsenJ;~fGLZ_LKeJ>s1+uK6pIeAxPOBM z0OpXyMcx&Mi_SbFEWTylWX12|uKNzdN7&F<3L3e#K8Tz6u5hEQt>wNj8Is%bEn%dW z3HrxfD9T|iv0)vPVPL9{K^?DM_OQv;Fe$%}nHYyfkg27~2qoVLIC6xvY{a&A*iJ=Y zcyQz;rHBVEhj$RWFC~XRC1>3Ji098ycwftCCfXPS)0mOUn5F$#TDm4)G2kOF^j#Xd zuQdB?+?Y`oPjCXKuI;!MY|O`nGpfUL1ZBLJ5S?Czy?kn%d*3{lox@+7o#Q~ZW{S6{ zR}(`2GSr&4Axba5LrgDfqEwcnU2IYZ(=!Qma>`T7WYKD9Yl@K147lSWCE!MeFWEPU z8@!ABu&4kj9^6jDrKWLD(~*l9K-(@wH=h#_%rGtffHsie^PxbTA9Wfs$FVpUCl%!i z+R<2_^E%h&z34>-kD{?8$iZD^;=hah_^vV|#tj6r*WKWZwh_$_;TU{oi?IW`hRC`% zA#JrFccU((M#-Lf0hYK`bvxkves%x@0lUr=T=3OLFwm zE~=ty2`Xh2VM5oWgeT!HIBX%47$JwIwwJ-f|7#8u(a>9pYqn&vj9)yyDD8a+UgCxt z?&$Pd@P_*6we4Hd4OuN~=ovw^s@a9KIXFF4z51+xNx0_RI>~L3xTqs+ok)Pb3ISEn zB!rKo!CN5-!`;$$dN$JQr|zml_5@xCu92tsZh}5mDGhGeil8pAg9b$|jz8bWSnoSC z(rWq92L)c$<+`72&o{)SNQ|Q)lL=n>aYjJM$VO~E#+%_F1#1V$>4v+?*1i>{!H~sg zdaK?xY8y#u-8!JEr`V7(Ph^v^(5cl%j^H=oNTz0%Y z#klW-a7Vc@x6uU5bfgzhEgPB?CiiudpMRW5ur;W}MZ&U!(CBD#14*aVEA)UpO+@x2 zjz$*hr^Ef;5l%pea;1$Ue+BwzNR%Q3rT+XAbjrI=%K7q~;i(|XBJ@SC#oiJ=imL=r zI9JJKPU`cJt96CSa+C908$G!QIarjq2-7;BDGxr(RZ6x~_<>EP1F1w>jvH;)=0MUp z1$l7}gI;5XaZ$|h<|VQ{uUDWP`jd4}m4m0+K)(TG!#QCG&ft|T4Dw4PWo@ARN951@ zGXWkGTPwmI_?<{TGZq(%$Unbx86Wb=sEtL#IZ6w};v3kF7mt6Y9ZM2<8KR!>FR|5f z?7go%VX;0bxbUtwdH$|t^Nrc*GUG`lCwECO`!{dys$kAWHRxc$Xt|Bqwr zL{^9>p7Y=9SUPOiJ6-qx)UjM_b=sbvBAg+O=B0k)aH_vN&orpXvWQT`{%8V8exyow z%jZc8M&A#AnA@zJKsX{4(dO8hre13{-6J47-(Wpiq#C$$uz?6g{BJ24{g+V0zez@j zDBiz?BK|`%`Y)k~e?;;AB@{tAk!km@p@@GLmccSW-^!F!%2{CT^9 zO15i9mktcqdx_r|_k?RT4= zsKK{;eoUerE3dv4wofGCH9Q>7rzLi+`nT8LTV;MdzhBKjb>-hoOjheT>$IJD_|o?D zS?8QoH1*>_XZD5fsn3gs$K5xWFK;#nQO$o`PoZu;+w@=knX(7jOd!Z54RRh+?8DQV}}FlWG3yajF<84Rm0v%W#w zmR*-(bWBdJKGo}}RcBjEU{r9US<{=}nvyM3Ufc2_DI{Zyd{;qC?Hz&6GN7E!a8x0N z(IYj5c!~yZoc*Khf)4(ejUn&2qFP!-dT~U5DZl>!e^_#0I(E@rN2lOy#dWHBtqCnx zH;YCvZ$>F4fb)4ZvzAd)ax(qFQ+<|68R}Wb!H9~D26<)8b*=2~Asj9ta#gv9>+IV7 zjk{9?K^r~QG=L&x%Sv&~E=nnVRpm&a1bbRd$uoPge23+^pnB=DXBId7EsT3WWYS_} zrl9cpfM4-Mt@oZVdmHQXGl7`ru6Bz>%+DB}&&Kq`95$4Mqd8Qd1!zQ2@Moiv*oqMu zsk%sIWClc(Q8W23WWtFG4h~>)&g+^f7uRK9b&mNIC+6Z>7V_Dt4u#BFwO`k?rOFnb zRP1!m`RrwsGfuNemKjODxnA@nm;EebXt(@@qZO)DK`iDPpj@MtS@Y$qMW*S>E=$do7DGyQKhNRj)^-l$;UH0pe3uS%L_9N2^xy zJPi=GfarEu-kk64^ek9XFJ{`7JGkg#n{W^6RY^YQFfExho*!xJUYl<4>73lY2)KK@ z18npgRsMLLch`HY+8D4p`SE+t-Mh=g#-JnRPmddSefQIi!FLGe_IURm=}A+_@Agqu z4p8bZcBmx@E(=Ck^P3hx=BN8(i+ol7ev&NfD1icgrPvE?Ow#30p)HHg?-GXWD4k+X z;;pPqi3gL7n#*l)_dp+nwK=kyoL`E4Ix+h?+C{~l67RV837g#WHCoHbkmJFdC*AF; zb}on$qgt%y7Zb*3U(!Zn4>%jsCO0Dr?tJccnHL57#05R!S?@$d)9W9+6TLle^C)d0 zlh++y9}g*8dk#N7_)ic;ZAETQsXqUm>+T`Wze89!Dw3VlG$_lr)RCvoF-zhTf59%t z!6CS2>Ngjf1vd5WG?HSQErJ2sooG#um(3m*mZaOO^tMdS?CDmr%5&i%NB^0v4Uco-MdpdUr$BnY1^aF0|0TVPCSD z=NnA@Z9aAN<&LGPm+J4w878%>=f=I>SkW)%bQ3?TO}dTguk>t2Z(ka-foDa1dFRjF zd`?*(7hlcxl%4$i)`3a=WuoZpQ`##VPw=ZxU#OdCFi9Wm=gE>c`52NJzwS;?o?UOC zJh!KRWi=DO_U(;*_OZOV|3D7>wki4D7kl*|8=LU&U9;~ld}n`r`~kn~$AcfPan`Mj zX5EW&Hdj*T)vf%XewdMdHLLRE>%pkOHEb>f1U+FW} z=A-sa;nXKYPu`M9-OE0pZz!g$79(&hY`d?Js&Lx0PMjeGlu-yuGb{eHB0Sr|m{WP4qVM8HWy9_=Du z$U{wXgtd>{hbu#-+W9_an|dBazJi4DjL)&Cw2dwrdCj8l^|CUI zUdUa)%I5wq(x<~ZpE*)jSZx^;vnCJ|qUY3j7%GV65qGby&J^xu0OfZuAb>>H#u&>m zzl=e5zk+#=>v=qjwr7#I`%UW7Q4!ZA9A6$~Ugh9Q6%%_s>K<4X;;U!p#^hi#>bkM^ zIzY%$%HC5A6PgV3I~Sx`g2iPDgQ8 zMdu{m43^4Niz+z<@~Dp0j6N%Cxmf?2v{apSy%bkt}vhThp~H;*%`@dz&NS*W~xsk7L1~t9;4s!~Bl2E``*sqIAP!J#+<= z9UYy#F&!jVLkB;HaozhaEr$i(Wk-zqr7Omu9EazwCz5n!q+!S0V5UUH=6+oZejX)e zROR}<%k8s|v)W1YZwx1mDzCw{Jh{&NE|J*us0fjq=mGLLmYB>w=6wFOWRs)N=f|;f z3jA@{;qRmJUX8_{X8ZhBD6kqkGMc{kSX78I8-`x^x`luuXZ@26L7p7hG4Uq_E95r0 z%<*Y{o)S!je6YNB#mG((k5)qm_Y>pHhgdIRXQHu}Bib(?vRpw&Z*~u3${bR)bKdmd z+b*uh9Cef3uD|i`bbra5hanu%CXzZ5<1(I}WDhDqkMsFhEd(JedYYFIFjj)M;X-ig zqLVI?_MZ;XYBvz|q?w(P}iG06VE&p}5DORFES6{e2Fg+BjLU;&{w; zwkm6vKlH-iZv+3ov(WHi6IUr}SB9bU>CzcMX$KTBflj3vb36UYilK~rK4LLxmP29r5_W0WL=3RpTw(7$v=&%pk%T0ipUm@E<<0YF!zM}|EX)&Y}Pg5g+7X=qx66KRan9%71D425Qzky0cp zT+BLU2FY~jal|a^jgFsIwka}QwVGW=Id21nYIKKPf2hI1{_cAn%8K&dH9fA{3yQge zW^q5xwU=t)*@Ul^9$8P07qdm*Pb{%se;rPKQBl+L>PPKqwpNAHyPBUcq_IyS51)m0 zxPJcfnBYR3PNfOMe}a|3iqDIM8$QS#Qxf&wWUM4=5NY2snl$cDw@4gw0`~yY2fFxv zH2S|%-erk3#AOqSd>6;o+|Nds=@Ne;EAFr=G$4MJlAu8{hEhosZ0k(Q4u&+;3ubGc zVf=kY{-F@;X++9f#E##gk{l@L)X@#ebFm_VAyVGei3OV$o-n~c#GXH<8Bx^lAq?buezR}`80y@!lhM()3A^mK-6ZgrUayKjljU+cb6KZf6ZC^NlMsAxSDPrW9F;=0Y>lBF+ zXWo{$2V$)|N2eU@1pdr>**z?n?>5D8XY4vmyISNrlRN0{wdpNH?Y&eIO~nF@~|JS~G{eINGpxRtXmA=XCK#D>*(%Vk-u?+@hq?3@N;iRkj;q6om}%G-a*5 zgLEy7nKjEmkRpAyX#lxhj$P4-U!HrPq5XkZgGhIQuT_%&o7}J6(?ta@rK==jS~BL2 zlkLK=YuQZ?=8jw9+8oXZmXqczVS%R$<*ehUjdX znaoRLg(^uTn)qCj5a-28p|%T?4NQXEr>TrDHM7*_pc!x4uA}J9?I3w`3Pq4`tl`W?&JiOGsQSUZb}IeJCC+b7|WDF@GVJm z4tmh`cd2-M6xT(C<));g5N{Ze)^Xm(@rrwpySYe1N8Av{Ncab=iOL8B5s1V7>( z{E{4%4$7aZk?>xtl_bJHOaQ%=hDE9b96m52)Evs}%sx_(Bo%GBZaDjLYrpx-&&8Ud z?g?whKJRUnR|Q9_x}OQ82wpHMCRDK=eAtR(-V3uA*~be>Q+WDF<`AnUW;CC3NCQ+Q zh}S49Wi9P!C)CVL(5EtvGbyJdDd9I4&hUxq`ZF5mGIS(i1JuC;5W$x2^cg zI%g7-m7KIPQM&cn&Vh>2`)PYaLu2wiu^ySd1y(0RA`(~C%{gd=)(g^D~;@`3gxg>FPKJ8>S5W!xK^|*03+_wB(lXsdA4_Y|B9pKQRI-ha zs=>^gAl-MSRKt2BR^}!-wmFtm$^~D3P#YrclWHbJ7EMXXcJ-vqJd7^}1rW%(NJ!4E z#Dob35<~{Hex~SGu{9`kkYVEHiIwBu!;jODaky;9LJh+RO^TIqJQyU>$#}^{$d@DI zstpcj85PF+UhJb#RrtAXN%Y*m1i;76m*(`vPUu^1OszEzs1N##MF4Mh?nBYHpDsho znscv$EZ;naki|%3loG#;qItw+c2u@D(n`iCVN-LcQ4r*ft=edR;;1WYwOO+^DEhgz zo!KRz%{h5bkb&B=sy_cL>E+{T8jmj zZEKSP-=i1}PI6(r~G0aYc2~L?MZrv$zBBxq$kaH*8vDKRtT+G^raauK?$u|W=Jzw}>4K=02s80bL zFA3I+aC@_E-K2>3_9g^7>9`$oZH*gqKZ#+=&AR;_-Yg37@o#6$idYgNj15V!L8*y` zKZ5wfQdB~yq_Nc}D}p|s?+l5a=ErBXl~EUzuyCs2n7bSalh5$Dt5j0qy*#pGjN(aD z4B87Py0SeQPxFex*w?~ zq?&`Y=2lHn*So{QH#Ai(X%q^QdFD7Hf?mvaI=D% z)r>)uX&N|u`(BESsbhh(MeLbLh`(T4nQ69HFp<$Z3faX8x!X1A3-*1;%g%9Gj+(7! zZ*JWp7?wD4eT`py49)SC`UPjqW;BHg{wP}O3nePRN`76=kTDZm9d1Qj)b$Mecd`#J z`%;YWmN-f0?@6EDt6v$g7mWH;{NLTF^+RxYLx|*`R$4%5viQYEtQj$BY47-X)qUusL0(qva$cEULVI3zKEf{hUM7Uz=2`N3_TCT=J&#*` zCk!96J3ELCjcJtd5Dm~y-@qZwCq-J(Kg}RyA)=s7M3u}TlrGX=KN8>Fu$)KkYm`2`mA!%abO4=R^nIs89$HIsFabdQku1U(f^G-!E`u2F9F*q8gZ3} z^F%q@+iRjj1pP`gfvahf?Szw6A(1GD2I@7FToZBLv|(?^n$APibFnzGk2NM>W;X|* zkCkSSG}z1`^m|)X4jXhPC zdHRITMtg}y2$m#m7ivgoL*x<4VXtc(Nr$*G9e_s9TIJ;}OAjTY|8S|N0h3qgkoOSK zwb8S4ZS@Ya20O2KM@z<8Vn*1>+M8)bBzi?4&TIwBF}Y#-X7fj;-x@HA_-<%K`U~Of z#~31xGv4UJ@i}v!MM#;mBRi&rVApz5V9idIKGL`357IJO<&gLflq z7*s@3qXO=UhHs+#1c(jR#U!r849AT>9|rX9v1JKoO=NOtTDmxhWUbhmj)92w1l{K3 z*sWu6TohjfT3UFq;5@O7Ev^due4BkDYgR^McGyZv!(;B`VE#Q)A{1Za5(NvH1naTF zd>v$wHphJTouUli2PvC!4@QY81g*MsC*qT4l%`~rweGu5z|WS#^VJdZ(ZRC9d2jLh zel&PVArTqzZ}*)VJ0=8c%^kQ0Al|cX%ca31l;v900S?k!+*`AQXempRX8dK@k^KW9jFl zXIG1+6-TAZn>*!T!!rCT=6w@9DAKt7Q(;kYE%?~(es9~9qV@6Pd|7CBoqWljb=?xl z6RhM&PG$L1W=Q+{S~450M>{R-HKsy?Ch5k1C*?$mNG`UBf5`%jlY6}JhNP5XqZBD0yI#vY9LAa1k4>0Y z0wZV^(livENCSnkWR)7XX%qL5I9JjWbCqQFMw*Q5#qiK(wcE141(8@a!?To?vM^S% zKeh$>W5?AagPf~P17d>?mb0+R0(qWQ7CUf6PgzPAUQ2X+QfeWf9l7{_5)QD=UaFVJXX%Azl+D>(NTjOx=u=lIWt3 zNmoEMf*d*(tK={hmMfmcr7?>behY9&OKH5zOHcgX2&ze+j>nFL)0E7{8^#%yOatD< ztviw{`C*(jCG0hY3n}SX`Ndp|mMx3sjVh6!`4@H)Da@ zA86}yH~G0?-Q)^#rhKEtfT9s2`kUC%qxB-OG&*@+ez;%N-9xx7GV=;nFM31z{9bR6)zmwMC>B`L9l_nfg$wP51v*m&(~7j@&qq30X*7wG zg*bFXw|iVE`QPR<=KQEN*BO-o79lD2e2%4){H`x%+NmZjN5$*?yP0Qu!&`i8%z)Pp znbp7wW&E?~_}~wpLeKH9qLE|yX+>LDztFJDw|bL!D9#opQfXdoPEN#~q)BeOm3pDsdd+5g*RjWxmD>d$51|^pqWl{Wr=Bi4?UnlurkQZNs3%lxdH4!D>=C<` zrYh1Vbw)JByhY*QaoK4Uk~tKSJH<3qJN~t%cQOzwWoJK>Z#9&UA~pqUGw=X`E7gBA z&Q4DFs4^#3mgJ%n0BEth6R@91PLzkgivX62R;N?cu5qOjHTsBl+$y`|Ow|ameb7;f zHZt_X2aGtfSCTwzoIqlb{;k$C`0?#V}2?3Lmy^EYXyKzj53gF3U* z;P#Y(K_-_i<|(X%6s+`;SDF1K_e0`DjN8t6lu@jCjHJ|dh^yB_S{g3-2e0`0Lv#us zs}ydx{hvYaG~G9!Kc#>5)HK1`LS+QQVYou+6TSTx*aWS@PwJU&ety!Lw}v$0b~K8F z=PIT&=zB8g7bCbcvSc2D2IoFPm;c0aore3SNlfHiV&T2L7OD0FOs>^@TSFa)dZrl#Vmq`-Y93(7 zdaV!>HYP6ddRprBk(b*M+vbL8&C!hAM$r1vgm2thLbg z{vn0u3SN=4VqcMdAux}zI$Y{xz0(zvq8<|I-1VBROD@O?8z%j#_TiM4?{~8_FDl<_ z+nq@5$B+UVPQnX|aDhG=-1_%)CTKlE$ACA+g61T){W}1VM-2sxk{9)BmkC%!?aqCWUrh2PZUem(~1c)oe*4Zt|Xp}-XfS1f62VeyO0k6b5rKW~pQ z$jx4Cea}2efF6*WUloh3R{ZQ`GXL#b&$;G|&GNeOk*g%x0vm}|Q46AygY{&Hf1Cyq znup_|G{%xXq(Z{xG!da`DHos4?&0O9Nd!}45O7_{@F0SzGHd!ck!v4L<&-*fv>7g@ z!&ILcU;iBUT!Ls;36hiOW>ig=t7eO;D&070*XYU%+@j$u!LrEsG$;5KwaOJ@3DI%9 zjx{#gBZ;ZBZUi~YqmqAE@YqtVo2{5jcN=i~d8#j{M9OwE`*^<6YjdW4v-7L= z`*_eN*B|$8GkprSS<|B5YgTHNGUH5?_*isStg{DIopkTcJ~x)JCbJTIe?Q-D)EX9t zpXiS4fop%CWpn@f`MPjKox~#K%C;X_7aL;$`4u*V%#u2STgo!%AyVp>8Xx2Hi!NhE z!Rv;zre|j#%Pxq|X274h-uuvfPJaUXk;JEa^Mf(R(uKi}%*yA0Adk8NnODKFG!#NX zmq@vwz}Os%0cI-P!T`ILmwQaGRuu3~vNtbjfw|uC52Z-a5R|LDz+o9mk;Wllz*pGO zVjyTgEQf2Wi7qQeq_#fc0T3!s@BrrI?lbsO<>(w`2wTULBG?rPU!iG*q6NsLDY0cD zE^hFU#FQwJr*%>R8|Qp+E!b*Phi(LlubJkX&6l~SE#V2qNT~BT1F7G&1o^zcYQRf<&-`r z#A=N5`ennWCDT2TUQ77ljz=@dKjG5|o%YnHUAuS{{sp-oxR&98Z1)*+WIpG-nx)}u zE%VS17eW*Fs!5-=po_wtV`$*&I+Xw}F^xI;&vv=)`-Vdr zD{$lFp5pcJZsLck_*vpdyS66BAB6n%65Aoz+yfRkKhep41?4tL{ifcckY1em#@#!l z#1eS;M4d8O^qjw7ovYsGk)c{wruv!Sw92GWjwO7?J;y3;w;4(sjYdc@97~x-#+c~U zG7ia2xK^C3*cwr0dcXBknWK7$P?eXKoQO~w1zTHXVK^wBgMT?LUVUvJM@G%-gujqO z%vZ=+tp!atd%K0Ogm_Vzo&YONp0oW%TSIraoFGPqLqPW!TZtqvUS`;V=3ITEJY7+E zAhTSRcO4Cq7VKIbWVy-p{gq{MVzqvC+ABereMQZOk-Fknb*9=F|fgmW)>5f;B0JUm$?ndNX8t_Jg0{IiVbs4 zR-7KbOgtSzThZXA!ocwCW})aAczQO*pOtYHW(=CzKvW*~fW|cN=~!ndWSXZ5$Ttfz z#ez(eAR7I~HvG{|g?6Kg7otZ+Df&x_5ScTr#3nG}8Jc+52%f=<eg3Eni>q8E{7 zM!xhH8ObJ*}hnF#(N z2tW9NbcG0nAPgZ0HZhZ#VM0@y08t1)03s0QQUo%sc`mpp(@n7}mz*F`DA*lhoa)L` zpw!8yMvQZLiFkza9zncTlUhY6BAR9F*bpG@$_Ed7b1Su#24Oi~doX6;WVDe+1rwgri@ zjH^uzEYr9ocso8JDp7dx$|r=1$);STs3Ihc3X3wt#4JXIkoh5HU~>xDe2ic*tk`8r zI6{C;Y+|ut%m_VX!oryChe`BU3Z?3=edR_m5hKlOq#Bvn)Ub(Fe3=Somi{)}nDC3E z_?cEL*+nptQ9bG@V`71J#-GtFi!W=m(&sKP!`vrr#xBii8* z^0A1-SZz($7R6M=y(j9`Wy@Nj2EbQ5abG?|nQO|>(i6o!rdCb&};H8nKk6lCHs|>E6=1+rTcCu2jr2LE;a^1h1LO#HMOiGn#-{1e&IK-e{i5EyxsI zo0y3vH5r1ZX-bP(^ea0;iBook8lE^4p*-a^uT%ZB#6T5n7f~4$69%nIsGNsTf_S7N z3B?Plu=kKwVN0Uqd+3KD8c~XdxO^X0-$`yo(e|0oqN!jXj8oc@{uyI5CNRG7O*jEQ zjCf^I1p-ZFqTQU{bSUpxxDva~q_jLZ<)U1ruOn<~Sfg5*5=us@EURk_ zB^#O6T!t`U9n4oJ##K=~rZIZu4rCT%n-x~3&XaMfRNwk9fN|zz1rrPp2?L76JhhR3 ziOtPWajivL5)_u~L}I2-3#H#fMy?Qgu^ru0>72Mx~F*sw8(r|?{+~HVR zBx=J_NsD_NEp2(-%3bcee)vNYAQ4_cd{+>J_``Px0SNH^f_IwLVpnI%&CwM$(rVF`Xxyb%<)M5O}tJWEN! z!HLWN>m{#k7&!u=MjjU59Qg4XozJlQ5Z+GSu6H=u1*@# zm7EmE6#0myDuIczAY4suK?@+58B8-lg2=KvRH6_9E1@!FT2+}!RgOxSq>w$VAp9X$ z>v|XGGCQss!@Gq=7ktv zcO|F<>@g)Op^4pBBDanFgkW%UGvaq5w=hjfMDZr<6wRzT7Lw4ijd5&IjuMrGyM8gG zfy(2ql1QwS1}nc$?IUFya(qFGHa?4FnWJNrdQ`IEk|a z;+YiWxs*O(9){4TO7W-YQ5AzCAAcfj1XYQ-nS&mXlBiW#2&9v!hbjqw zf;pJ8sG!?9PUty_YM-P-AbBDaYEnR{EB?3f8aHav!q;J^j`N<1=((ie1g{eXpRhZz zqZhmrp|R64w|kkE3ADj0jKg~wdI5zryqBafg)*cH$GE$DF*HR|j1c;lyu(AXQnRzu zI}ce5xe0|)*ohS(Gy%e-PS6BPv_zQF3%&S+NaKn~LkVq32}&TvQY6Knp#;kNnb#_# ztvIy5_$n6ps=698uYyC_V5`(Snc$nLcoIkm0hv!AUk}gEnZB24YYgA8E(LX~$`! zwj_ZrB|#i+2)FkOH}?xSKJWuQ{@{Z@(1SmSH$K>dJIl@3Q3S$lY~1slbn--GYdpWgb{=|4;(>sf+zhNu(DVl z0@JSmdz5}6s6^4Hf=IAY86Qq)!5-8>j^e1HL`tM2m5P7}rz8lHYqQLjNzFLn#2UJN(0Y(Yw04J1T36 zYw=4D8jMXCj638Bpil|^TN9vL6RPt;AFt4sNzgq~6sl3o7ELfkV>Crm`~=Mts#MH1 zT*I|8%!*7hs;W3lui~v-8DJWa4L>+!fuxJG2WMrcX1=RgjwnWF>&2B=Ym2N{OoQnu;45$hYSQcA}G-H~-H zQ03YY@Dq|~KpgN>9Ocr-BuF<2%>zBqgCxj<4AoEz)dLOf1BFD$gY1Jm2+@M9P=Sm$ z6dlNcq(5-0zlQ{`LRdF?TQ@@(1ZV;Tvc$-a9Fqu4oe4w)M2M3_aKJ=hNwZJ{L}-LK zk%S|a9Y{a~KbiigM~G5)iYGsb9xqgsk>e-md9W|_QiAf*tc0GT^iqbi9>H1x)IR zVxh}V;0U3j2-kEKWaQOKAd6n@RrD-INtlF4@DoV*PGB9+WlY72sMYpdzT9%W0P+M- zrBvNY3T$0fORb8Nvel2cQYi&8LpWAHAy+@~9+kL6`_Yzzc!apjxJ;P1hnT2Tpw#F{ zHA1ovVE!N^Q==Mh6b56s&tP~Oa4d#pBiLpuu2N#yg>^QDO-BPQoKiA^Gr&+Iz*r-| zgFH9`B&gUt_=7Xx*gX*0JV1jF)zCcXgAvWpjor`=&4YmKP!0u1e((YO%OASKc`QPL5N6FebOIypGgiKj%Uz;U9OfIk@hHttF|K2aXJok5>#!VhDXpKB?-Y>8``)xgvV z{?fIHC_7!p`%5#lL(3~ppxBib(;xET3QpJwpnD%FdkUghsr}I{p}I!8G}YAwVto1=ejrM(%Y__q0`(2;NV)#^6l_SjC(2 zy+)H-KK2FGq_9?#B44gpiHfiU>rJ>cfhJ}ergQ7jLs*`o90{0mK}?vZnT)6Id{>~9 zs7y-CxdIJ;F*V;9E$G`L>ce2_yAk1PkYX4v4jv`qA|-OCD9B`H;@Hc6x{<7-PwXP%N%|FQH{jN zKWN;LJd+3L4_C!SMu3h@@ctiL?0WwiKsYVlfop@4T@29yqF+e zY@KDENM(p3Urw-u4;x^a;0uu2A2K|O{~0!`RAm3nCWn`Q~-Gl)Rc^?1WQoX?zKy1u}1f##!vom&s6Ze?^R!X zCf`bEDNeA)_e5p(+!j40F#Syv9o0xP3CM9XopLTHk|PL=iziV=gfKB3L&#!8n5Uo& z=4URu!+ec4s|;N<1<&9YV^}q+K@jTmECp#0szF#A$>8b>$N${V;R?s;JJ?{r*dxeT zi)~>TR^b#j0-<(cjBQ~&Km#6T;iuMNG{}P=P68I@*bEio3`K)KNa74VNQMN+gXDvs zU4(CQ+;*!-DF(MFUflj8ODT2)MHqxZP=rOWYaMOCxX?G)i6(zj+Jws{f#W7ViHojP z1mKBUzNK3@cH`t1LxOHJ{Kyk@GF5D1o1pY_h+rs^0xeb&NJW?ao zT7T=moE^v{ILP;l&F9=)s66FWZXX<*2|67yRilSil}Exkk@Q>??cGw#Vw}(V^K{mw~9;?3uKo>0o&Dvru-uWLm-5VKH5l2SR_YoqgCQ7#GBATO0CTARVU4X}GeCkfNZ~VR>Xyv|9Nq&w5YdBF**#cUgiJ_) ztl5BE$jO~t@T%(%-#@xu1iH3sLr3(wrlxgMulE{Bkc6hch70|DI=7G$mIPz{3evj3 zbU`r-(N?D+t&7A4+x|-JuU%tLkGKt#V~MK^PzQC0vkOOPT{}$N)^Y095#e7dP2n>$UE0aq})e`2*~|gK`bZk*_%DfE{=;O)$|91YmGd}bhBAPsOyeyN$#9D zi2&a6-L0hX)T(Gsswgu;nvJNTk^dla0^!%K7Z85kj|(}Cus;GI0D>OCawr#qAs~Ao zAc80d0w1vQD9`dCNc$lWYA^3{Az)!HKLRsAgB3PejQ#tkMgtwT^EgL*#3xZ;A7Za2 z(R4$|OwME&rO2|zYqQ4uyT)rnUj+UqCQB#YLi#$B939CpVZdk-(zr1FBW(nyZT*#G z$x%=2HFktVm}{tA$t30IEH+Z!@8}>+Qbm~S+pm4w4{hN`{@EW=Lve)WAN7uQ1Y5^+ z-{;~WmC?E;ggp?*3^f9f)q@qj>WsbWs;2oPmQg7tOQWCuthipUE)8&!Z1&&JV znVvKql_31~gHCtp&Sq-_2uqbRrLu&Hlcj_S6Fqul$)UoC5*0Q|*r-vei;Xa0N{A_= z!Hyk6a(t-p5~50#FjZ{W@S#hGkRT;$^a#<-nUDJX03lKY$Pl7J zd9u@kwyAJ2vCeICfZO#L3w0Qr73bH zmRUBUWh0I_qKG4je5uGqU*e^RT!qM`$Zm)vQWzpa6@sRfrHu)unOU0YC75{j=}4D! z?)D~}a3Y!HAT+l4Vug8B_^69XG{w}ToaNCKVyx-rrkjb-*(sq3?Ue|r9u1aBV3LT0 ziK-s0x~f8fnFJ9;CS{V4L&8PWWv7qa5z|aA=|xy+pw?7VMFwq@N=6t>6j7;bRWeg0 zVII~HTRLSsQ(=WL*_D(>HVMdLe*9r>S4&~l)VdW)>MjuMy89?3QF)b=y=RqmhFg%_ z(Gy)|rF9nmT?5Ni*I91;t1Z8U)wLU9nffszXLvmNC=n7~j06&vF46}dKQ7|g9kMtnmaFwvYs;(r=t(vNIi`e))y{HGX8l3{@rP%unf&A+i=7+# zlY974B$0_30_Kpcjp@j4e||YgmYyndN0y2V#b%Lm5t%7#h!6?pZnWolCGA&!i942e z-bwyEnr*6CJnB#mQr2hYzLtmJD?WDNHqS1O@SemqNM4 z5pKx~oC2nnJB-j@x5f{}`ai!6(zBXzM2VM2vdpP`2GeW zD#43NQX-e2MduUIxukTY~Pk%y zbhp{CXfi9}9*Q_a8;TWfdo_wpA7bO0lD&p1UNbS*OzcLB_ygs9w1?D{t6V|( zLm|><2t+06NbL(A^-^~uL{%?!2z4k#i3B@>4gxR3yWYbRD$yaOrVs%2mPfl{I@shP zWf;2K5v*xVBjOQ4E_&$_{~(sI&?R7WLCmJES5rqIawao%%S{Nu6T8^&{(y-f4E*F0 zDqzJ$FL8;>Uh*O=LgfWDL8+2ZRj5_e#7Tuh%}Y%cXo*XNbuGX8)mjwTFM?%}U^DT_ zuP|E0Ev`_cHnP-XFmy4ERVZWoAp(Dj0E8kG!3KmqY!TuEAs^JoK8vLrAs7$`k!X)3wM?xeA!XME;By<0rG)>5|ki4iXoaq zXpk5tRNOP()(l3lqskgQNDK{9Hisi*`A9?}KBRM;jBDp>)~UF~z~-KmW89F^*%frP z@JZ=o4N_1Cq#!}Gbu_--YEpMXP31G9K4uN>ZVWrQp5@0FW#Qs3Mbg<+D5XYl&=DRK z>tJ?ills-6 zSu%yF8^!o~Nis`W(z?I~nuURmXlq-O0!7O{k&un9tYr(wB9)op2!l+i9W~}3AqrM( zU@I)xYET2i5|-3q1KZiob{`)cfz@PykYs(ppfgA|(zgCK!ixSj$Xs)CvMPfO9?~#Q z+U%M#XlM{?4=e-#H5R}=2*MBevups#Z3to@LV>Ye-~#Wlmp6z34bEHM80?^r^MQ4( z&yWUNuUp-XhQzw5xWyHPW!?ZS%TNR%-CzMrfYkCCwGLK4V9h|hG9a&c#eLrO-e3n{ zxWNrvK;H90yy6xI!w$rIOETPm48q(24^};&FaTI?0Se^a@EC*xqx3-4eEDp^XWW?= z*QCZRE{4GeIPUd9M1b3ahxS05K8#aD<{S|oQY#|c+y*zWDIAHCBBK>^?s8=b0M)1#8w_4>>Q#%}3uGWe8PLC=G(cz!EL+g> z-_Np@DZdP10MUo`AO;SJ!Tk&p@Y^^@59ok0l}&sQpbkWvG6})q1qcz`-vb7L7$_5d z<-oVaz`?zM28vrRAM6BM z>5H0-##;r#!OegM&HxL)3X(m5QKqmxlM!sScFKKl&zFCRnv+D9)t+a-#pldh{4ZM2GJ4S zuK^BiAWq7FlhFN~{te=iX_y`)&SPj!Y~aQ{HDYcI0^?xLhZ$XLFx}*Q7;KbXiEUzR zd`vrKPTRdk8bFSYoL%Gm!Hgu4iac1&K^Phgo)9e45L8(i{FZ%`+qs3{g5X+~p`fk3 z24^Kv?{&o!9plwuUFsysjX0G4zK9kUnHJ`KmKR0i*P$IYq8(_}hHY%4?BQNm{DBie zTD^5v@TFk!MOGS&!OE~BgFqh35Z*IQS>ny&9|ZyVO&_X>8V$Sv3~b*&{v-DFBMcN| zLHgrChF|z?pe)5lx!K40`M~*|Uk|+6f>fE~HC`F`9~rnG`kkNsDM(1_(HQjKG>yR? zHQsy#2p|0s*^o`Ny(HP-KoRJ`E|JYH{Xi}SfezpR1#R+zYfoLM~(r$N;&y zlDW+X`W*ooG~P$z(SnTN@oCfXS=tet!B*m$q$B}mxC^DY%T^u%-<=OwvJfGd%;vn% z3;-bu%s>mUKvKfMT)zG#49tKGsDOASVG<^#3@l*_U|$HTvOiQA^zD>hfu~K z;=whc%-`&x(4~wKR7@FRhK4cDHfo|KS|ZR1UCce5ZA_kQEN2>|lhDD2bE-^-JsoW{ zTBALk9DUe|j0o|0j~%L%{mG(lAya$+Q!lN}aN&{vLFHDSApGT;uA$@PK^ka@9W`Fh zl8}}pvYl)+nhHjmg)N%8ZD(>S=hBhLHx*a;1Zc1Qr+h-1WDQ@skwN?!-$pKo`+1~D zLWufR17(``|hGBq^!O7{75F8Vd`aot5VA+JBWy<6(0}Z7 zzzw`WKgxg$l<5r2=#0{+T`u8JPGJ<1+b#LPkM=-*fm~FgCy<6<2s$3!oKX<*Rrm}6 z9t=^0Tm_*Pk%nkUABcex^Z^Sz>Z8KtTf*g|zJL_Yz!4&$5(?&C`lY8Pp%gwLnMUEn ziI;ihKreNmEBz5=?!bHiQ=T#&tF7cpir_8Qs>?;lt3?Qd@D?8K4gT>K3BH`o9b4f2 zO=_9}{yYB7WIULIVc5+H-ToP!ZKPAR(#HN7A|B?AN%^6!-Jz}>A05U99>$!m5uLe? z(>eOu-R>aiWzfwb0$x((aNazrure@Af>^DF5j^wZ1)gf zZD?m~?Ajg#pRfTVtqq?xSr!^BE3X;b@KxFgS`!I6E0;x{31X$oN!H2UV*2f>l{sGe zwVG4LEZeXedU{Pux@s}K*B)Ij&yzzxvA^#$bi!KjR`K#eZ#(mtwDYM`as zKn%nOdHH}W;a9nVoG!|#lRhM6Iw@sZ<_;v_l>Weeg&}|8qt`6k0m39q;z0NHBo5gA zKrW3NY>uB8nA-Ji;&rfTZZ zg6NU5qGU-jh=DLj#)8Ng;MV>k>#`y%H7&^S9a~9)D>Y%6M4l|KCTW!QKrR0ABrf9Q zV^Xj7Iwg^EZ-!>5`gP>W9)ZEWn!(zi0n;BW!Xo#i%&vXN-=u39yzu(TpZc-PG+`No zm_hiK=KIFt$-$a&H5p84Q~k-Gm9-6(d5yn1WDkJgZ}ET!%4wXg+AZzuOzINRx)R*F zk`2&6^_?*q6D{`rEYh|p3@9zr%5gy=?u`22(`sP&-KYkt*Qu79E7br~0&;!~6ENAN z4gk|l{=iFGseI@x_2Ea@s7(SgvX;_qd*ML#U0?S7Z4wfe3X~}gxIp11;o&CknuZ@K zhnsjYZo-*rP!=H?H!cm_K-ieE+CZkN#m8U`7GMDuVtpxMZ7vZo7XB|6b9{g1o@X+$<_-Aca8?4C(z1NM|n7Eh?#l+#i8~NEpx! zRffTBL5ORH?}xl0`zq`He((1Vv_J<^|J~n$1V~8(YXWmy_-@-Y8MH%(f%_>4Ao*WO zn&cRafkn~>m2EH}&Embf4NmH;P15ZRpsH}08$||bQcolgoL_!LB~(i#84M{P-C_ze zhyX({X<8cs!?emwC4+pVkS=x1LN!IcP5F)}LN}8cGT<}S{@Oea*BE4ESevH;ZjqXyPM zW@q**#ekMBZ(vhyUx#+}?JO9R&Df;z&tCGVsWuMCE!fcH-mWO!&bHoyat-_~K+*vB zjq;3Ywm%xBLaM+hOJS*=SMQ1&#L2Rjf?M}hpDpiFs^w#RfQ{Jvz?T9+v=IS57IQKC z5P0j5=>C9t{{ZWzZtSi%3XniH$L{N{fb4EFHB&S0ssN^}fUhmTq&4=m|RmS<-EhAs3WX&4f88IEWW9Mb|a(;5Cz z;til?7BqPla7Mn39zmb~nxX&x??bEfKucztMM9;JN-^9t zUI|B8hX(Y5Of^i)`9xM^MG|SN-qM(JG1t=b+Bm6^rmc~(0Qbsgvf1 z5KQjal8qm6E#-P)E`c1`U@c_6QVq~omIf}+ikc>4-`pa$ih7^+J$7Wnaf~u8W(#*` zzW^W0dM(2(8tXtmdYZ25BqG~n(4OerUNZjJ=)lj$_NRRsO=@y5FS}tEmfn(DE_w3a z25#UIB|<{&LYC>`7GVrD;dyy`#9bh`saGwZv6cd@mR`6H6oE1UfrJME4lpuLGV*!< zKoEegc~4;KelB1gfxV|LZKbz*lR)eedMniLw|(n3f72-K8ZM0rro{2C z5R#h<2xU+drt+nAR;^g?eU|CVnR zNcN8BkQTHaH8tDJbI2+3U%P6?eR15Qd88-ue(@xJ7&AT+pnw>&T0e*^DqfI|e9QCF zAs=#Z?Eno>-`huiE?aGv+W=2$GA4&Q(T=*PYjWUv->HS#il%yG1LS5aq(N5d4{CNn za(1kX=^A%m&|-J)x0iXNnkE0hu+Q!Dj}5ZdWMPR-r!RXED8b(%mUa^smlgqS?`;tf z!4YDiD~#1;ZP#~3*f-SaUXgGWyZNFdwttrci+(dIzZ&0QN)H1AV7))38KSC z!yi9_;Ls7|A(4j)gNO-2{=_20A4Gs0Au?o0kRd{bj1;3pj2ILzQ@})#f@aMWEKsO; zxl=_?o+xCnfB}O=(H1RO94%TDhK!>zV#s&_HO7q?E~ehFdILueuV2A>)iIW=jvP1? z>e!)!2adHo#_o{lwMP#gY1xjgD+D8ty9(j54H9^4j)_9X`p|(xqQV|*c?jtt1Z5B( zlrh^C5{7SN&SAm~R!D@*Ny7r`_h9CJ+B(_Dux^$RO~*1vZE*mARc3=2pxVH z3y6gdLP?0rj=<0$$%<>JL9*^EOEDdO>rO=#bFiVqu^3ZmM*V!i;RnWQu%QMVYUq## zAZr*hNgad?61;*4`p(HDsiYEyBw=uYh8kXY0fsKU1hdN-!W8q%FT3P!%kIYdK}CX! zFzBHlHnh>A#cU{o&N-Qk6V4(4{b4*Hl&AsFLzQ?4P$h^^0#G6jJ#^1KGl~cY8*n%h zwij%m!P8G^cwx&MJcSC(^mw3rn@EI+QF|KFoJ9$?JOh92j*ZaZ@2W=@>t~zCt|HO0CRXuIgfW+?!X?JD^51J zV&l=i8}bwDEE+IfjK%Tn4KKUa!m6>l^n7qP-l?IAj|TJ}tgc4;m~612wEnAWK)gUM zkUX{S^V&gyZs_ts8s4^!EQ&b1aYKe~C95D@aW%+?IA>h(#j>9En?@cPrf5cn8j|Qo ze*RajeAOb*0<VT({9qBdJ&s79n)%{0lP?bDC;90FT4?1oxv z5=tO;uayWR);Gd^BS<1bj9!QMC?#WA5@Xn9l@MwDWJw|hJhViA&LRP{u5~S(u)rrd znE|$D(376X00rLy*HFUMr4~#@1|4)kRIXALQ)xv#R(U~nY)~GpB#uRiV~kuVw;1u| zhe?+r7+gA3GPCeYX(MCU(`r;Bv)o89X*pY8E;f<0{H2HsNg~4}(v_uoFJ)}={tC+; zgO`!n1uz&w&B>mV80RzuKH2FPcZl{q+R2cJh^tOeHLl_yPI2ienLLf3ShycMbYJyi9MRh%-kt$7q>=6%R#vxe^j(ot|9gNTyxP}0M zd?{)W3yaf(gYdvjaYCOaIaiiMdSp%%xyXPdB1?iv1U#BM)ZmK1JV+gCm;_;xkUW(s z#(e4qX!1eo@+hWFaVktcpc{@TGMZv>q*fbA$qy2B5Q~iSd5YrQFnw19{yFM+%W4#( zjMXSe&C_?IG2~#{sGspLiF#y#i*10T5WWCN2uCmoDGdV3iqvWd>>~pH5{v-Rh~i;c zEnyT%kj1~1$giS(c;EY!MNvHTfubZ$;7KcplNLNs1s(KB1X~)@pb%wT;j$@Gj?xsV zT;+p%Tp_7Y$ey5n$2vh=qb3_;Dauix z#91O_l(h;b5sq+O?M4bvHp0_)_$4(Opb46+15>C{x49SiEg>AjNJ`Kb&-w*xBq=G$ zHsNcmJy~R({q65{IpR)t#*-r}u_Z?Uluc6ZRzG_i|MBFEi-2UWLKx(Byfq}Du`VydQ-sJ~& zp;#)FGaMB;5kE3w5ER8nGJ++N&5DGajAYck?n&0NnVqAjju}gn{Rc6N1Q0|9D!+T; z&14MXoLL~;-e>qyz7WGsjds%#_Ns-e_w?zM@{ z&G;E>+q@D3Ut&EPz}*TYL>L@_jrAx<&9;p{>JF)F&?RV2MVejm0X4bv0v-S%sle(L zJnf57c>VyR$_jY0IKsaBRdiY4^lyKSl>{Y7{Nc+A0z7-Qk%x?1*&z}UW`+=kA&8+2 zBq@U!8cGr)p!^7E$gmMgWULQ=cv(phaT4^P{)Z&sxaK4!F^W^%hn28|=O8u$iA!u_ zk}84cI#xjmelmjx202_rF0zy*I0Kiepao8mN|&50HxN?gL05TXmo)Y66-GyniEA`j zh`6c_=nDp@mRPpaV@XV3c@7#fQv?HIgD(=7Q!|&B%)> zN=pw)f=B?BUY|skx!ZH?J&%Zd#mPJ#s>y%2xg8z z(2!uaxgwc%N|HSi^{d~Z?Q_5U;E!_t&Zoqp$C@@s(w~HQ>YpR}52pS(jCVZ3Z)l)% z3c}t@1vV_JAzq+!#^&#SpxEjQfJAIrXpY5bE?G1%S&%?kkl>*zZk={+;(%`AOpxM` zj(^@s34&mNTEd%{Yb1yd!>|j(uFri`uH?!956qwkeb9Y&Pz*lm2#f#?NG!yVz~+z+ z>2xmVaIO#Z0LPw?>2eImnvMzkAPG*)3aOCDb}WKgV1llW>pqCBQpN(Y(m43Zp4S8DwhZbo6ZI;&Sov-46MNI+c3kx6ygPk z1rtB<6VE0%$O19MszU-p^AKrwE<*@1(HAKL_z+6B#^;j4jCNw^HL{A`KuP=JCnFMt zB`)sbJ_1+(1xC`xH4cYi{EV*_iQ{Yqy8x`d*6)3=YuZ9$xvp)wi0vdu0wrKhQT!_(A*#MwVhB30q1=ZE@9`jsK>rv;#2$(xK9G4Zj(K+LAS8r3Br7`H zBD~^__Gkk@j-a`8#Uet=1Cg*Fvryt{%mPP>e_)J%B#z=La0z1)#bS~sY4QXoj^db( z;S3Vy;0JzE4k0J%CH`H~3Xw7olrj%&Z0B%N5O8kibS?=ZAreT;Dv=H=vvLWcG76Cr z3cV5vVJs}QB@Lxc1+?z!WMBraqNSoD4Lb-2rpyK0&<#li2UI{SRKP8y2M+VlZHz`b z?4$WirW9l56j3qpGAWaQPl-??ktmP*zDS@B&+rmV`!rD)UvM)CO3?@?78mb{yv(2G z?gtPin<^y}zb%nKk0eB{C4^Bk3FtF3LL-Pz@iK8TD8ep2B#}5o(I}HLK14W;B{WEa zv5F18;sliVYFFBbt-z7iHVkltQwUaWelCup{%s&Rk0Sb}IxRyXe5*tD3HVN=Y%Gl2 zuuFZ`(SO<}{vX#T;W&Z_4Uz~8QsqLzebz_BLTn!+3L+&634adhYU~3m(8f}XB=A6@ z@&F(s$_SGHAq#XuXU+oS$p%B>Ah=IOAnS@`2z-)pW{^`PE3Dv@VCJ+i3qNe<`atPc zOr)srB~x+oMj(_lgASZM|@t{9zEDyfY3UkgY`G5~{t`G7c z3YRbt1OX~>P7w6KN|A07u97ORl1mvO3Br_0t29Ryp%1V$3YT2EaxX8KTp6z!t$OG@S^M^h_b- zQ8)qqB$YC?KNN4|!0qEM3?*E#B<3d*L4$yLWivHmSY!?M*a9^q<17p-7Z(FGQ*(aq zA~r#9Hc{@|TEYc+RSbmg1%)vTcvXJHV8SeHGB5+yz^(8|0)0?otWgAr1$)xqwvGA<3fpewpkU2INNGAnvfQSP2t`6d z+m9q9D&Z7zKk*Tw3epUUbbTU}AT99b3iL!9cEos8MQyIeYP16v6bYDe#`v?wj&8fMu1w36 z#3q4Dy|gN65-Y8e5z4d=sIbaOHA6 z;!rDIXfJOdP+zD}zXGlbQ>?fz@W#UMEHl9}S5h%o(Sos%LK6Jwj501&I5iBzPJ)0m zvo=dLSW~k$YZJmQ##{MmhuS0(8Ers@5jHb|GR$D)f^{|r2v~zRSdrCOi2yW|wIqg5 z^T3THB2YvENL}NnJImF$GLNx{tz#x42kdMjE@GhAVmf0m<NH*o05`%5Me{5+U0sA|pyj^KpLfktjb(<@j@eZ7d0&_Mwsx#u!)$ zkM4n=kVOep#HN-=HTGj~HY!0jXGivF3siy+I3y{|KO_V72&4A8uN^6)L(&WV5)Y8nE2B~@-IPt^7HjiX z4*)?Cyp|8P7!a^jOoNzA%QO&@0ByO{jFW&8A^~l~luHFc5xkge@wQ48K@qT&Zs)j4 z7eUJ2(kx{Fgrw|mr3?(>@^9Y|E%p)z=I{**Mmk_V?b>2Q!yf0 z6@oJ)qcej5Rs&@C;#D5YfOIiR54s0YOWVSiPCjQQkT5LktIHi5U~Fbb9Bkrhn>C4^5`MlMJNNdDvx zB{FPRM$QOEdASJ07dsU|%8kGf*nLopq6FwgS!`Jd@;wohpW8LQnXY3w5)(DCe3C>`EBZ}#CHYGJf&OEhPE1zA!k&#DHUuj)O56546yU zji5n~5D6d@4T@9?LAIkqR%geyqzZawtb&Z=i_xn|%;r%|Ph3UE2}%x{rCJgjwi#(AbmN z2ZL0|nf$iF*))WbUxiWw@jzXfC7emwxm%YnIxjXmv-3=X*xn}~@v$FQEX7J{fY9K- zR}9AT;C&;iVudn*2ndGh6Qt00qJk8I&0rxvc7j9pMU%h`jNqsB;0%)RMLT*yXRM=@ zGQ&|iCHue+oHVHQpsQz|4}^B-{*-tTba>{o)Jb`q5B$Inm@o-$+{;%yXv0!0q0&h& zcqxVbeU#k$Uby>2Z|aG#2O%0sQ(i|=T;`8HO>;Dfan8!K^h(oQ%kf&NiI~gj7)`?( zP4&D>BcV#0v`n!Yuk~8d51q{mU9AJ5tN~xGBf+dA0j7eDCYH5e|Lx72yyR!O^YaPRH&BT!2q^zyRP$ zW3F%}VD5uBGG?@e0A#BD6IC-=F_Cq9S9L*SGvtvoLRXawilAw`9*v+3T-gV!x!hx0 z*v;VNta~K}Na8n>1zWZL_`Q=d_-1eT&@h^@hj4QvgqN9z;Mcc3nU6W)lNrXW`4N%BSwxKQOMAU5rIO)SQLZD;t(-?j36B1aR{D|2!kjz zc@m|NAS_Lm1W5#lM2QHIARLJYj~P6D`taP-lc&#|Mv~-2Vgw12oEY&aQ9^_W(vL(y z8d2g=A`y&AobohNG*6N}NtEj8lLtwXJYv_LNdi|a9=L1S>d|9Y&)Ty|^enpDM^F+$ zdiWR?gcz|N!Gib%eyn&ApCo+l7Ovcwk6=HX|Mab_$1`2ffd!SmJ2`HiK6KeGC8|^6 z!!l+ZmT~CN2X6j`XwFF72{O;!)jd)FO?;Oy;laukGfvDLarDH911T!4h`OTe)e&#^ zE<;+k=`@<58xz>Bq1Pxj7%~~B#}^( zL?n|?@?Rqc{-+2d66R;!dyO0tUm#%I03r?`-XH^sH^i`m3@DDMLKrHLp#lyt;()^r zJDi9^3^am4qlhEYs6!4r=&(Z%J@{~B4@Da3LrXziM21O0gh3@sD?J&87(|e9kr)bv zvC%~eeJR9k0-Y%kSVBxBQ5iJ3Igl9yAta|x1c^b=L}-8|=S~-igprpO4N*}=7PTZs z5I-!c!~PFI0Fi?YLF(Yc4npdH0}Vjp@Pnc{1o5epT`Cc1Mmg^ zUwE};uw?}+Tv#8A{Pi%z5o@-HAQe{(NW~UA2iU`?AqMeXl2xYJA0nHEa$tR#7MNar zB|O}^Vl4%iunjeYn`{w9B+(gis*4A@fH6$5$BSu3SaRnz=iJik)%PBFP-keLAlA{B z{-4!R3nm_O+5!X zejE<2hzc=~xPuBR+^FJ;Dvl__j4A}7f*6bMP$T9b+DM~~Ad-=J4nq1sx{^X3S!9u8 zw4`{VQMQz2mQxBEhDl+JS&*R79%N=23*GKRLBQs$E1PBr6eqDZ=b5HXvNClLvLp#X zD6~!n0Ys3JN~(hnHRMpK=%V+SX%5}v5bBlIOM8(|Wvoi;PPF0C>K>}v@4Tw00%rss zZi2-SGl2zXxDf&oZl){b(FRYWiXNYYb-axrYj?Y|7DBv)2Zz|qTX*tSPJ$x-lyQkL zCwdXf8P;{H53WiK^)eL;M^nn`G^wuB$Y|TCx zs?dfK@t;F1GHskx8z=8Jw*ZMicE3=@4MA^<+kxs9i33Hb5q`7KDh+f9NI>EciJ0Xf zZlD4ZP3}c8I2?$2>4IE-4hNKjk&9qdxgFK0bD3M_4uY@)AMn5jqKjswmZYTq9=L0?qCO`PfhFD3fej>;0~*i(MB>W*M0SP{kKn0Ks~QOde`c!`a%6WCjFzD`G82jLL<~0Z zAinY?!aktXFjAE&Thz6#4-Um%J|R_3Xy~n`G{q?*lp|Y!G8~10hG7<~jBpAg7(V#n zGLk`!$eKt+9U`%hRm7MgkeD&288L}be3&5okVMWfMu$dJj$fL%F)#jR=8k+2Vqe&T z26QEXjS~5T5j^|GJiH~2>$1!r>Z&m+ZcMdEYpfqZ76?EDGLTyHj(y6OpGE@6J@u&& zZ|TE`?dWGfq@<*O7KxwWRwxo1VkLk?iMIaWN0bhtZgmyJAOsyKZ>oe)BfQJX@cu26 z3Yz7H4gm%->;Md4V9Di1bb}j-=tGyoLFHaVBg=t-bD6Ud=Jt!Z%w2PI)HD*4o*4vU z#tsKng55c#bSP`FlUL*;VDl2 z)71do#E=5=>ShvINJKOozb?rs4|v+3u`JR}7E`cGKFx@)1_BbV25l(c5@D>gTG_nd z#Z`P*6;QhAlTtCogCC^ftWG7cv>r^Yp{b0?3}YC}WHHir^b5(7wg^BZM-Nnl*m;on zSHLPFwT8Wz68ng;y&e`lO*@aqj3`;c2~#1$K@C8(;~2=`1&5QUjzxzl97GCzK^P}F5vAf3!cRyp!y@jV@@MU>E0d}(!6L0O6p$yh?7snsx_IgDQB3Rg>e(Tapo#9%WW z*r$OE)O;=d#t;KoD(>~vM&!;SzNVh+oSF~*o#sp)FSD@F>}9hONx~cTP?TE>v9gWU z17aVB*w|+W5QX^8dAMU8*4?C_(A&yuF*<@{TuxAQW$uARz!~Z;j9i>L(z61Ao`WA&g)-Fajf%17AYoU$}$= zJmMlx(j$qZULtaE&xAQUpk9_kO*aremXdL#Q#$JPOyM*oRbq7yCWBI95=RmQ+GIQ4 zL@2!SD-CfQ_f#f8m^%t25MeM+Vo(NQpc4YIQ%E>=v|$ho)Fmcy5%aVXgfdRV)L&9Z zBS?1xE}&yN2249PQDw-5nu0zxvOfM=GAa_p6hQ@4Xh2YSGIw&q5G^$m_rxne;AMQM zL9%gS3(*h?5n|M0CnhB)sIn*Tg9h%TFL}3S76Cy6#7|ze6LKaJJ)tis6;)CN6o@x4 z>mpS>r9z})FZl98V?}2m6f|dnc|H_rphq;EVQF&Z2Z!Ycl`%4XRYlYHSb*?Xhm;+| zNQ}U!d+X7Qp{7JgbZUY`ABnMSiokoY2MC;zi<-d)e_%36Fjpp{7uxs-qos{|aTnSM zX~X7xiUn$aC0xLG2-Z;@w6z|T)LWGFZP8YK>!*+V$dCUq34e2Lp(F|TSAXtSe-5%Y z38{bbc1r#Sfbqr%l+X#^#r{eM=r?tvO8-F#^d~omKnBMYfk7}LH$svj2s$=$l3kcM zFUrQ)5CAWg-N( zay+rK5>nWNO;}4E7VhTh7+p-FI+V)Bs4UY zMrpRV7`bQ}Yc*H4{%9DAMID_6dzmH(vA2%Kcs0+M9>i#Sqc(f8C!E(OHQ0AGu2!6? zwQ44#9Evq;)(DO!BNp0dT88ll+-Z%4u#SIqHSSn7(CBU3cU-qsA-QEB;uj(HIbMGw zZu({*NpPPQQXqiCN)9rR1u6*xxsV8nkf7v$3o3693U3cmHyDX;oiLFUd4L*;fSpio z?-w_a5P=K_BL+u-7R6tk6M`m5O#YRkHo_9*6F$@gKGzgYIiMsi`l3PbkqJhZA4h{U zXmwvH1U5J(C)WcyaB>!=5ZZJbKClqJlRLblgHpCr$kUj{0wx+!P8TsdwexWbp#$TD zq%3+PC*lJBU)Ta|sdO!HmSCu3Co)kNWj-vKqvWI|$0B8R;&zK@Gjixozrjzx!Vm{F zCP45IB^LyR=@5nKgCs$vD;Ak)G7*xwLL4y$7cmCNlBte35N`4tNFZpO={$WPcvV$Z z<&qYNrw2KqnxsgIN7hp`M0u1~9BiROZ?&6|1{%8Qo48pRy6Jp}g++W|Y9NDp#<*AX zAzO{atjx)*##x-hsBBLoHddotg;ZNsQwUbWt%w1fxR{Qd!Hs`F7~<)Ts4-WPp^ofm zjLoVa@@ZVW#eL!SZRiIet@NMaH6i&qUIB6-fAf#@_K%GqAd_&Q279mwf}jiPpm&oX z6Wac;5ZZrrbAS_Su?A>w6dI8MxRC}6N|Df#2S+3!$OAS~vdxq^K_aq4!Z{^7BqeJ; zJTN-oV^Nn8 zbW%5TF^Evq^K!t^C&|JmeB!BOmxn*N8;bf6hWaQ#XQ+p(hrL1@8?m*wVh~O_6B=f7 zKl4Az(oLp_sgNfIm&#_Qf)j++nrKmYr)sMB6EOAzF8IR}OU5eU0#%}Fs;Vj+2L59( znE`2V0cn$Qn~3qd#QInklQGhFd!fd9?P?tyQhkR|2-W8vh_S8PDhSq_81kq!+6N!+ z!69qoz1@pj@X8&scfC{;ebwllw@9vsb&i}-jY#AN-_fp$Kuhs4pOSPnu|+i*f?S%U zNo%8S=tjT=Om2a*e+=ue^0pxU7qAP7u>VJq68e7@Yrz+6u@qcy4h(<{nSPU?UgE^E zDT_Wk;Ibp@mHs8O-Gj3kHL{(<13lofH(Mjuq=Gh*17K;wL;`Uix1wGNO-ieR+B9S= z0hTv-q)1wWNQxv*n@%-#s7ne?J9tn9(X}Y&I~<0!PkKAPb30!gl~>}U{y@CKmVzP! zr*w>?B51jEF95f03k{iOO~bQG`|*EyxoFNyRgldn;qm2VChoDai}#JnB#+pOymAI}>#had%}25f)SRj?UpkoU*H`bP;1QVH@Ve-eBC!3S8u6B-H_EYXyp z33d~J8k~Rhrjz6Jvo=B`L;{vyX|gFC!zo;KVd~K@d$UFIl|e!}7H7jctZ`xhBU2KS zR%e&!BRZtRQ8_SUA|0k*8N))bv^@X>24*El^0XJ$Bng2#S*j2eakXEmFW&4V*bxkWbEY`^c=%+-NJ<1D}1 z3Y@{38Hkm>(%Y_yKucpIT<}UY-}|2V@s0q(HsmHZl@JN~jcy5|YpOJUHE}&EDK0$Be$$D+ujtzSl{8hp>*=e82HbA%TEN@>xjNjDGZu z&+s8i35{<2=Fg_&j|H;85W1iiT{j7y{@@eqkn#3i5{bbWZow07!3Wq0|0vNDX*UJ` zkujN*937Tq5Y!^g;x}r-Gi>Blm%=5_vNCLOHhbbfDH}deqa}DL z)g-ey%);GMDKU&BHfUf+zvWbd)k>O9z7t{TGB5*O z|F&-nw`{4FH}KVmb2$DLrb*1lN{N+W0Cq41_s2uY@^q+28FNk@5>j4a-eh6rbUTs@ zCWDW)NGSM`38{G3O=H0LEtV0P9WFK`E=b0z^n(X{z=^pm)tOgI(%hkAhYue+di3xyR;3OfLR1Q2 z+k>r;AYtUb9Wvx?5F!3zG71S3H;fQ69s70+tg`D+GFh?Cq)PQg3>lGIw2<6_1&bCi zSiqS4!Uc@SH>}c#fdfZZlsBx;>o8*EnkjRnjNKte!`zNN`j9fZo5z-m{pfe_;roagFJ{b9JYw*SNpGeP9yDnBm_f4# z{vP{z@Zjmw$9{kN_~>I1J@(L}Kt1xvV-G(5P_REd{@dfd{@&xSLjN*cWJCWF)T56B z^+r9!84F7uhdb`BcZes$~6ONq!C5VB$5$D)Kr8~Mi))=Nj2Ymlg%*0j3kmZ z;oS7oPbrCHl20l6v=UG^H8s^pC}G6aPCXrU){1H^=+=TJp+u8Tdd-BPhzjZzB7#Cu z$k<*5atI+{hrQKVU@4&pDHX&R3$JT~2+6RaJUZ#H!@Lr!Br-Pg2qu$WnyI9hQj%$= z#KI`IsB%wRcZVBr5X~p5cBo1;#R9%J2OYe6sbI9g8Y_&k+QRECzTgsKh`HG6Yp{zu zx{C~X{s|kaT*K_Ww=u{fn;|k4W|(Y-%P!;Wu^3#S%7w>VxFLq7Op8g|f_wNuW4k5} zgJPn?04}$oldElM+Uh!DIJW9adg!;r$V)f5{;F;|zKW(8Yog0A!@2La4iCNZ_IvL< zGuoC3@1DgL_KcgdqY1C zWbwuWH;kNb6)%5r#uklSWJe+W7?MaMjZ~6I)>kh?$|eJOy-FRcWYQ2VbN_PAGkJ%^ z(>8-26HYRzL~~9r3uV*JE)VI`&p`CFbk9K>C6rM_A7$No><mWCZMBW}UVU;+~uj<9A2FPw?N-sP{RGzBs* zc#O$ppt2UApkg;-5J`@2l|zw?M#Rg}jc7%D@Iw-QiObC4XhW1U zT_FS^qzx%z5z<`U_MF5eB|RxoP_iA9v{XB9BF~$Kf&`hw1SK(LN>gYupYR~@sdBC< z5tw2`IeAwlNMwmlUn*3a9@VKfValJEf|T|m6)W*!ic_*mRr=yrD_XTmRMG-f{5};c zSTXc{qtex_KB2!}O`?Ge1YogBI*?@xO*1#n#Ch3?- zJkpVIk;E`5`Cz+HGL)5|i(NIj7rfTxlq^E^W-;(rzZl~$Ff>&t>;9tZ4R>&wms~M5 zWibm!jtDV`wFWL_APii>zyrB#FbJoaD-P1t)EqRXs*RCB$xb!{z_M(NW7G_1`Z}{L zzLAXtE6Yvo(wgo}j5@E08r6h`)~EUL2vS2UA&JNx>)6pbZ28XBq5~f4geN(YR8M%| zGafXwjXz>=&)CM32L43IldM$GC;h|9Q?l|vfct}i40NGBv{G@4BT?f9lpwwt2y&d; z5a&{4q5~x%MJ*yxh)m>%Wmb0);8hVI_+VZi`9Tmr@`LPNx4I~??ht@z5|f-{2rfC# zo3%63m&$2RD#eLRW8##W%n2tpZOVC|64UPvg{I>vm`Vu_{;>Eg#i)R4X?gQ=QzW9K zJcBNjOr7%HJlE6+^MNYG_Jd#jHdT;{mg=GtVQ3&&#Xk3SRDaCk6|a6JECdRWSt&Is zOO0GChbSbbo%o*vncN8~cEqL=%;~}`D425@wOv(os+M7*7?&_rt2fB%4azmL8^|E5 z{@N?a^a=*5#(7;h+zAiCk^{2(L@gf-0&7yz=am@JEE(ZQTyE+uEN@VRImBU`9rM?d zeX(VL9qeW<1DVM<24|vyBf<)ngP*B&Vohw0Y^Wv%G}w`1!|`L#jMGHRMvRCO(@RG5 zVz#Pz_Bo_A?b*f_Nlf~>5Ak{1A|SM%1$|OK`uU{(;uhqf0ySt4s|*o>{4*itUb{a9 zx@CXHn;`x~4sv}U5s6YX%ta(2ax%J6jw(00%CSguo&&@p%qyhQsaL%}`U4==TL?hR z4t3kqCXSjK@Q0Bm93aWB@Dw086;Lj zA?&1r#&HYA2=l}emMaXW(E%7>qIiB$HLsANipu&b>6VFcj4N0H87boenKfmx1S43_ zgiw}Mf3{+N^ke7O>K5c2r;ugf;gNLH*nUpPw9((!{>9Lb?GLtG1 zurz6tND!0YNw61e1dCI!LwTo<`~DO(DKG-tlpXY*SWy%30l64tIXJnn`SCC-0jLuL zpGi=Ym@}W6bEt^2AD<(Lo}t+bGYmo(ggFd!0U>qaGI6=09J(1B z+5s8JqBs*glG%Z*%EYSj7rDy;l)#~~_zIp#iLW54j&PT@-~qrmqCW(Tj_HdjnybM$ z4U&i<#&EM4po+-otG~Jo8EBanP=QL*jA3+v$asMp;G(8zBTrKcvTBW2WFpGLyrD^= zSv!s%D1%pQi?f=es)37J{<|3Hu$s|2M|1SPqu{>p*bnqT54oX_coaYRKsCIHrR36& z_4ANCNCMsxuD_|a6Je#~`VirA9B;duJm8zn;T&Q*H@?{}&RMq-nVbSV5f>?uciRyc zF&#kIw|#RaA=yCuY7*4RFCuX!F4-Mys*)+`65P2bGXc2`W0OO1!575ANO&i6N)t{Q zur^VXo&3QWd$|HYz39LYib+Nntrn);A!Xl?IJjpCPs(`b-YD}iUv#{7qmCy;6XuBbjBegJzwwQ?x zDwq=T2%w-Mk$Ft3y2M@N#gdVMGhzYSOp41$0nOM9G(yH0P{uX-#5Ph59ncom_`}Jw zi|3=7Kg^m$Vk_&DHMGLUi5Vil+>5ID3*FF-UQ@^Kh>z}TE%FOLxuLD{Q=8b5oAJmH z^6;eH`p17f5Z>|-{D{vFsSx=Lzz-1+0XdKvu@M;QrH-r-5?Q7fA+H&EH}X2S9#OB* zsjdsGz>f^LKTxkfDFoapgeZ}n`I;2j`KBm2gxZ-B{))-`N`^ppR6q$=kNr=Kmpoo~eN>#~1p5v&h9J=vI z0#WHOq5HyF;W<)~79E2+yPQi0VwNt=pJ-7CB6EoUSxabf7Aeb1yoe&cyraKtyN@BO z4{{05{4#MN&PFtt#*8W$lB%g%jH-}Ks@l8PRJ^@wG?}OgAbKH@=reNJGqUK+4{Fra zkcoOR&3XZ=rXqu`a~~2&?LO+k7V1A zeg0%6f1JO_0UT5-2Vgg;m>NVq^9$xsk{5`dF0+ToH((Gfq&!kIyVVFQ zk?_p0=nA`oi4rn1ph%3Znkp@dflV~L!@p-)fsz?n)5`#ifjN5%%pI ze_fG$jmYsL*pDp0kHk<6`~zq5146h!LztZ>vDhPV5)QNyi`^1%vK>4Lr{XEW4HHTN zv&n|r!9*yY7>xvrgD@IpC<@lm@{zgm@wpy-lZB$6FY!T&8{sg?S*kq#(j{#lRH=xo zyG>jiQ%#u)zt@zZh0tyxQs>Bmiz?00prQ9~Y#4K98y$g%KjXaLH)Uk+( zq@uf>As5sLjCXC6*w&l`&=Fg7~P2V7jd{7Cscjo7&6iG$L&AM!>jLvZ~bMTn&Ul z372S05>kQFu*J>gs=%vELv^!GWW71gMXHht$b-DiETNs)2v7_w!^H`oO6%1m7n2y= z7?Rtq+B7=WBD(EGeYu&us>G-=jifM*9uf&Yc8ekUi~i`;n&woCiUEx0vl!%5A}IO` zj+wk%RU*8I4n~TOwiyD}Vl8Mr-d?_CPePAgrdIcKPyI`-{6K&6o&d0;9zL49UmJUNmOB@#oRl0^AuDv2j7i7<;Ro(Ohtj^$WLfH0oS;7!qJ z21|sRtLPNYlp*9478aA51L-vp+9c(uC`@n@&O%oCxO^&;owSlZd8d`GpIX76T_Ffw zL1LLM2u;ugEWGfJ;yEtW6%!|Mf@m@g-xZfK{+3Ya2=nj@jX2ha`9mMzLGfDDuKAD{y4&Z=?>iiW*s)?99_u zLyIfFP~5IbBdIHsIj{0Mz#d703e+w=C|G=Z zogxVYeIo=N@n0QrrgwgxK#0lcIRxDP`6dzl?k3UD4HQ9xOK;whFcyqpk$u7Q$>@Ow zl^7lHs5E!;NuM38XrK$i7nBv4GarsV(u|g2oR#4seRnEk@Dsig0J~T`xv`Pf6_!TU}z0XNbj-FH+` zaG-55poN5@#X&xa+1&A_2nXWiw{TloahuK`5~uhJr%ElwpNf!px|BK>pRyT;aUqyR z%u6DG@X@13kRStl2oYjXAwq*Xf+Q3&uwW1!I(YcN$YaNj9veC4xIqTTj*($haNKbt z21}MZVyJ8>ljRJVGX7fTco}0QOq@A>{!E!N$3PxLJr2ys!y}KTO?Lz#!$a!SFi<%< z{kVfikd#_grX-W{hDwq`gKo8mb?Qi|TZ;_z2>IT)FY%i-;?C?#B|8aXm?nCrQGb zIiT^s1qr(DXOST4<&~EQ)V{qQ^!CvMDgOhzMq*-#hcpsNi;PGTNh60u zG9e_1Oc>*hj5N}SBr(cZWQ&j(nIt1i&L|0!J{FlsjVjhC$t06FNuwlN%7`VGkxZf@ zm|Z3r2_#__Ng#n{eh6Y9ku)fwj7S=JWtf!MNr|3&PO_(;dv>D9pOlPwC75=CX{DW5 zGTJ94jo_JQCzFO!X((buB}Nzp(KXOuiV-AGLp}Il*F+ExP9i#&e zWQ0NflD6D>wU)VCZA6w(wWcK$x+QH3E*Yy16h;p^czRf23MC{MV|?iq*j@rlmX~Jz z(iM?poQk2TWP2sluoz`zX2cMjafXK(VszFSX&S2r+iI@ifyQd4m4CL5wYtbexHT13Ii*wfQkW1BBN;Q4K~n_NOTz?{7fng5VSfQw z7zWm`f<>f=O&rX|@G`L>R0a=X&?0C`1~SlutPdRHLmpTrve>ZfG_lFU8UATTGv&aB zJ9r3=a2C;>pgpHJ=82AS)>E~dJ*Q~CSp;>4L!Id0><@nU4tk2VpY13okLY>Mey;PI z>C^{5=@G=(_M->{o$YLZ8)bhWG0HZP=n%i7P@EE%Aq-{6aD^M3;652A5VdKQs8nT+ zZe$}I1>#3HLS2%O#G@c>DMlt*lJ}-mBu0pinT=p(nS{hSD#-|Tu=5h_xOqD?)y|os zdy|81Ng{K$s7RH_=9Z?HJUuxo6U~d>o}MSYJHazdlUQe>Tt_A=)hJKggP!=p7X&nn zK@4QzS3&4UuOT>uAV-i0zTzhkg+QcKiFuW&=%s@nY@~%t@xlL|{`Ef`0L6b`>B&!I z5UgRzBnJ=VNKcd%6d&XyCnVj7RwzOc4WbYt7okX0=JJqDRSQ)-Nb05>gB1_*w1i11 zp|%=2mx6#$A^vI%WD*iY#2jG=Wr*0ox+)opA(3NZ&`S`*V1_)PAq-{kp=J_>slfCr zVnE#M#a=G)h9>8VQ=PYmqZmCnX~x(@C&XU6r&cCga7fc4cy=B#@*a1u-so6H4cp)_Eld zKbTL=`&0BjVNX42!oqrr1fIHyCn{N~5&`wnB)r!rNr|tZmFfnITkPT&!#Kt=o-vJU zY~vf_ILA8PF^_xf;~xV#$U+`6k&A5PBO^J{6Z09@UInR3DGoSnH=RX5F z(1IQ`p$l#3LnAuTie5CM8|~;vLpsuuo;0N^ZR!3?V>;8C-ZZB>?dea0I@F>bHK|K& z>QkdS)v8`Kt6S~rSHn8ivYs`q&HM~F=y@A&xJIpe?dx9;nhv?pGcD)<>|-N4*>k=| z7q*e*UA#dXNPfdIlx^*6V|&c9kj0I?;B9XwBgV3*c8{;I3nVY&4LN=W7tYOMT6EzJ zUU&m7T6_&JbfF7e$afuTObd5sJKzHUwKlxqj2nM}+%v8Nz_%>y9M^#kacFTajNOfU zKjX#F#&I1KKJbr&+||3lb+@uz=00e zsKv^q9rClIz0@(salGH~hFY+g4*$0J+PguAb8BPQ(B}2FyWs_Hbi>&^??$naeu-|- zT-q_`MzjM@V_EbY$B-wy#dFaGuy>*1EZ(@?@xJRi40{`j*L>PbZ~9C>!wY7&aThY~ zVyW+%#c-&$Hn>3Zmpfw@7S}!E;gAc#tNZROc7_@dzjV-VBMx_8J&X%}@Ebe-;$7f% zv9}%v&1-!dUbsCRo{@{UV}16gA3ymkT?e@1*bA<=x7%G0hhwmK`Yz^uH|YNiSzP_r zogcmz5I=0=cY`+Qp2fx3$mQ5B{>&TDbpf|kABz#4#;Kd%VV%8^UB=a2`C(uNCR)_x zSnIi%HpJX6#KGvH9Jh7DGHe6=Rouk|;I3(1;dR60ft~}>Ld#uT1jg6}+L-UPSn6%V z3f>_0Mc@OTV9TN0xy2mwY2XqvVV^Z1+kKlC>;f%B-UA}w9Bjij)B(^59sTK^Hbk7# zouIn;TpYxK(D|PO=E5}?o%qFA>M7xj!J&)A9K6XJy}=j)Ze1P3LC|RfEx^GX=v@7I z0UbD76Cz?F)|ocM!TR0V3@TzIO5!9^VkKJQC1PSGYT_nxVkdgyCxT)qisC4eVkw&9 zDWYO3s^TiLVk^4hE5c&_EXv|6(qb*z;w|E0F6!bg@?tOg;x7VYFbd-^5@RtM<1r#* zGAiRTGGjA3<1<2IG)m(%Qe!n*<27PqHfrNGa$`4o<2Qn1IEv#ql4CiV<2j;ZI;!J3 zvST~C<2%A*Jj&xd(qlc^<2~YIKI-E>@?$^x<39pqKnmnQ5@bOdA@Hl48Q=O-;;R(0(8IxLKzoSL*GMUnuZ zbwNd%;EP=WUbbalve-iofJAnK2@C)MTx6f!6ug)#Xv1Y{ zgEhE;1h|+Rh#qEQCS~H-5==obs^nG1*fku$0Knw@T|osHz(~dcPqLT;7(l)40tg_0 z0epa4asg}}00wYDSaw4R2qlbR0~~|^0u%rOguom;9UTrp2z0|VXh8)WKmvR~Udq^6 z#u!BoK)69>lR>~WOhYn2fQz}pHB7(~EP)hk11z}M1WdscECDklgMCiH1mu`2xIr*# zg9QjBNB*+d8x&?uwwO^?Va0AK)dI=}$Lfh-unM{2<4A;3q@9w& z0|w-zgE}G{K;$JXr2{O$M|Ne5-DKps*fV742#lwbNkKMD!8Vj;i@8EIbfz{$K#V1W zeDWAB{AVyG=8Lizi4J9p8R!6%0PV>_P-?*HMJN_9CkR{t2y_99X}|zDfGlhSHsnGS zP(WB3X9B#QfF3{>9K$sn0~f5O1xz4{R%8gc+b(=%0XX7|-DF;FL%LYRYI(iYfLnC5^4Ai|yqAa3|tbBo?$7o?Zd-(ZW&ASYnna0>A-nA}W$Cfi{$; z6leo~veE#C1WbW7EI~K8K{8B$1W3WSb^|peX)pq2!x^VV zw%ASnX>ksKt9C<#%B#lJ>WqPCi*3Vdf{aApw%BRXT}6^!m^v+t@+FK#sTOFhkx2nIXhSw+ zLzz13v~uJGU_-XLSTb;{+M4Z)p(`#-CX3PSFe<3Ug{c9oWjADK1(bjZ>~8PwE>h;} zz0Mddgy~0Cz+nz;H?ToPnxu>6LPbX4(!N;J&X{*LUyD*e<`P*16sg#Ps-%jpi!H%6 zm~M+HDvOP5`Mww|7-=%NEHFC5O!nq3sOt0%z`)vAgyQdvr5lNID8T+DFP<*li=8IP zNw15M=;Xp!jLKNS4uG<1Z;$P0kG2@Jj;kwdgL}RwGc?2Mp01IStB$rOEIjKJK)?hf zgJ|*sFOcRi0_HM+C*wXO&C;0ua$Ku^XyV3T(Z(3k4uH}^E{r8`^;RTW##r%2u#mw5 zdcIf$P=hOUgJoi7GPr`YMk=>y&=)nf@iw&`iCFZUU-)iz~5;J6gvZmg?UZE1G7Gmigr=}1O z028#B)rM$`;99%*x))S9XvzsaY2TD zX>AUGjpktiXn^5fL#~=66X@{81!V%j=`}1v7gVU7{^}kh@Bk<EmQ*KTs zKn0M%AnNl+gLFuX^hlF*Nt^UZqjXBE^h&dIOS|+-!*oo`^i0!qP22QM<8)5z^iK11 zPy6&w19eaf^-vRaQ5*G9BXv?AS_vQkmW2QY#Nm{M{y;OsSORcCk#zwAkel#1nHOZU zm3g&|akZ2kK>t|-1#EJVJ#|zE83KIiHK28ZW-U;c0GnP}2w-!TfdEy%*uxfCMv_|v z7%`K%WIkV+OU|{6Re+2}Sy^`jH^jA&;Wb+WnN@d#GekCGA9YKmw2ef$lYnRX`kY!2%=z2)LdXSinf4rAh)^0`#9t%0hB4cXL0tYFDKL zlt2dn_cbuJau0B9yZ3J9^6&+wZv*${g+K!SSilwV8qy+OgG%Wr|0XV@Umc981sp&L zxOW1?b~o@s2OI!e^BNXx=l~e)=1F)0m_WuA9Bx~{Zd)V?B!B~4HH+Q0a+{zt)O7-c zK(&EqMb1GtU_%#BfQlm?I1ji6;HwuH0E=CL39y0%+@_0ZH;#8hE`Wf8`!|a{LkJvz z1$cpDF*le10x0*nfdHyr0SVAS1&rjL?zh9fSRKG-ahsiG zS9v#JfsW6(ZfA7@e6>xZfC9i+2{=F*WOl|{(JHrN4K*Pa7 ztvh-I#&&F@=vr1l1x$2n>;5zo)b{2L00B-pHynFf=PU5W0Zh829mHK=0y!I#0IFgD z3bfeZrd|SAgNvyo{IS?;AG^g3uDe|U2BazhdUhNf!201ei}^K*uHOe-s7qo!i)jPL zQ~Sf8;KH|4=|T5zKk|Ji>W~+U%>%*9Ez*HYPZ^twZrx-J*fp@*0WaOS4WKiy&VXkFxgbE; zjgCZwAVPF2QQ=~j0o%GE;NlS^LXp@SKD;=v3jqyYoDF<4VBoR=bzZ)_=CE7DiQTSP zK=^?~w+aa=G_8Q(;G6+9dk(Bn;w2o718EJQ)e@lw6WyL!NKisxw>&2ScG~VIJG-4!O4=cwO&J5;YwBAyLtEa{Tq02;lqg+H+~#>a^=gJH@{fI z;zg}PyLGMpCD|p}$rrIh+*+~o2CGpJcBR$rS{)Jvuohf~FhLiFlZ8@ra4{p*ZXrIK z5Zp~9ZD#9+g#WlGKpV;I&~2A4%sMCzz?LC^C!gW~p#lm9c#AHouwbkk4U)1Tx1MU- z%>t~Xo2tA4uG?;dzS1LM0IWW$DkIx4V(}%NDhyGzZoD`EG~KpQuf~CPxk3X95>V_$ z_~h#*HuSdZ;s61Uq|rvsE&`#0A_pqyAX%PFQOAMw`tc&j283&XG%--oxjOB<6VE*L z+>_5f^(-NQmv-4}ATZ!Y+zKd?7&y<=0p}*1;(tvJq86cO&7D}|fZoorm z(^~$H`YetEs0%>@*iMR2KUS^GuBvSiSSUg@3B5=I4RYCLn++YMswjjyNXkEwUdnN| z5h@T6p+SH2qJV1@d3E51MBULObdErA}$o`A^{z> zIbcczu;h~>O$i>tle|-f3b+7mjAv)ri#+icOK)|+T zn$+$JXPzkm-^WgpOoOt&=829sxUr_-Yb@~fnKxu6!Qb5xs>L9p1~?#r1whabqXN2E zhL@O~pP!%&Q?G!$fl82>kbtw) zfk^A>i5yXgo)92lPIKUlEHGez4AA0*18N8sG;n|=Dyf7gq{=fufW8nM#4}>5-^(me zi)SoD77as)EGl4t3LfN5Zqi06L)VQApa3Bm$k_miSxjRd6Pd{b4hEQ@OlJZo4rzO) z##ELkEPy~~L8{X(=rm1leiNMG6sK=+GtO^b0|d}AXT|tsCq&l%;Tqh4LjpQ=CpxYZ zpZV11J~M_tekN0$|HRX^@-&A|A|M3yyi-60+E9l+6rvH8=tL=6QHx#_qZ!rcMmgG1 zkA4)SArPI=l>pZ*l6K^5vy ziCR>n9u=wWePvJ_>eA*I3?5tun8DpGK*%7$NpJ{(L4&&mB4ls~5*&iNy9Y=z5Q0N+ zhfHwy5MrF2oO{mRb8BnA{jpWMKX!+oQ&UBCzwh&OKizM)hS8q>t3|^y1)55R&{#>$ zrjHI?C3my*9gQ+gVj${G_we}R#q0O7qpZ$3Ep%rUoV+s1-?o${yjC^(!mB9flqyYV zN+4*O-e5=@Nk(69KmeY-RSf`H<##r|5hS+5lCEtW2 ziRlomHm9$F*OIi?ExpxfI)G8I`Ls1JBZiYn5Tj&JPzDLVg%#SXN1OXLf@oxI@bL44 zJ3gy{@g4J5Rxe`7Gfqf_IjEG}TS%b-#$eOIJG>O0c+53vfi(Geu2k@)-0=cX?uacj ztUl)MkOLh;9X;vl(+4IKFgwHJP;rh{bF4@RXS-ROnd=ga=$^X+9{k|b>B2NP!1kKa zQ&~Qg8l@M{1N`T|q)iO~_S2G+*DdPdowS6tkEYfUxHjW0)R?hC{5t z#00vuv}B%`#!PtA%KXX7M$7x=i`jtDeNRe7R}~J%n<2Y?Xj)(DPvC>EZ2skFO6DsP z2#j3ix`M{%4-qultUeJ$Z;CIi0AG-XJfefEBX!U}V_QhEl#BVpQ2%aKuy!x8XPQrY ziZ5FFU`^OMIc}Z@TV-L@?es-1qD;jP?YYncU)&M$Acbd%4TrQktCAjX5#BQskY=OD z_Q2AttN2eY0gb#shpG@Dwa|OwS+o{N`^~aqD)u4bWa!S;-cFsMo$hu64XxF)q=@oY zOo)?wNq%2+djkYxja&5$=&-*vd`8+U+9`vCG5Sia za0p6=4#gW1JUi^LP+nr`#t5}5MuK1*?3MN{YUH}me52i)H1i)McRbPM^w{y1>+;NA z-7Atz^~kxo`VZcn)`y~bPi^V*Q~Vv}t2=qMxIwSH?h=XY>RO+;k53ZvqZ*B&C&$)x zV%4_h*$HI~XwvWJb7mR|!NEWEO*rBQO5DQ}WC*0#T+ONovsX#s$6Khz0-@MWFF(*N zt?raN1TZ2bv$BV1cdoK}1XU`9IVyN|C1VP?6-er2Gz9S8U5=ul1o<95gEPERClupn z+$xO*l^sSKdM;G8^jC04MZO3AjCTWVa_vtn(zHxU)UGFCi84#)OYI6sqrUb6iJyep z3Bm(YKCRkBc?`h%Bmf?W4Q>x$o_K2_JusZl^kFKKX;XP-zVH@XeBgEXF_Snal_`>G znFo-Ce};%o<`DE1P3$3Y$7jmb(H*!htvrcYTCqm1 ziVa%i>aoXURMZdGEw+(y0s7F>$LsX%R=oa-!o`E2Gw2ciwJR|dsC%!EBu7Owe@pKv z8<4E#X|vL8kx|1&2Y|ZkZ8DF|O56GB@fA;Ak}47Cg0i&K2f#Qt@UixtwL)${JOxF( zT4Oa0#mS<(i`qQ&$f|w{e~s+iQ*z*rp6f^G^V7S&9%2LA@WtJ)YwYU|9q6GXjsWa3OrFw+dMKjR~JHP6c^2{*>doE+Qt2 zO%F0mh$9e!k5ovqcghV`9S~w8LGwkF1xTPpMc~h4DM>nmh0O8om4+do@=>_07@fxe z$6&I0V6x09Bg&0wc}rew?U}zKv0Vfi0f6l1QEclT1CbSe`)7ts`~^yF!7r0MsrldYdb%{XdXTDS}9v*CaP|sx&Vshw| z^29|~C7H)`n*rsZs6EcEe!hfSBQov3_OpTtk&OCP+Dp~tyOAMjnM;V{3xt}~TUR}K zhNon>?BP9iPjTz9D$8Hg0>L8_ehL5%rA0R_3iQ;Kn;iD4qovA&_O7hmkFUvVl9(u7;W?dO6DK?m2_w0C6)0sE8cX2AO4k zR=#b;1|R{$N#6cUCawkW>u!kvw!TV%ezXRs;|s*lB6C{1-@A`@nv)^o)s+R{Wb}Jc z0stL9NGlPx!?j3-7n59kP1vd(MM&5KOo=EMN!I^y}Q^kvdbTk)@ zH|g@H>gEA0vp+N11#bvN=sjX53UNg{TU#p(!;5kKQwo<=ek- z(v{l-8W@>8N;P>hWAdEFG}Op6GSxI@#`Go4{bZy2>8ba#X7162ut*7L*u4Lc&Ge2%Xc2^#*p>r~iasOVH?r-@fAhUx_i9G|7fne!(lAclYJUU# zbi$^?#(H0csr9()n!pMrz9-B>TdU3kBpYDsGkTDDOq#?4!L2`q0fs_)fJv?h{R?yw z2#m!LoB4$~^5}3D(Q0FddgBoq=v$AXl!UFrOGz5wBgtJzt&Nf{vd|Y~`36l&Q z9r#;=$NM4Kz)3PkGJ==O93Pxb`18>c4(t0GNzg>H@+_$n&f-fz&|S4!3yV$#lj+-F_cJR|6tm7dw8|y+71Rl79wOwN|vU$x&vR~bHt5S0aH7~ znDsHFAt3b?2=rHVe>_DB(o5tMyw>w5hNNb7(FpWN zafzj!srOJ&sxB9acBr)itmcc5V%&eZvaMo3d+;$LPv||ZkX;>)W#>l(WMs0ObhL>f z`)#z%h>ML;2r7b@tO@+l@3sQi;1kURXy^&?*xJR>tiqUi8mG|G_H(eQcyScz4(50{ zk}2&oc)W6COdFLSr*Mm0q!t6H&38m1`%|t`r_oGsI%ypepxtN29Vu{HH$(CDD3};a zYNc`arpS6gSzx3lw>8mjMH_oH6WhATx6#1IS?L*~drL9c@?c~TL0 zwjSPMhh(+}_^!IH)<>GX+e-U4Lj-h!YF$6hrf6xW3IJ*Z2=4*NS}(Gry-f+ z%3Y~I!ZcM3d51ulND=e1Z**f1+weT8dW77l5B54?&}ooTNlfiQpQ(tdzh)QLjg_nG>@RoklqNpqp)Q=-^myUu zJg`_!9l_cZXcLUPuNa2D@Ouv?7*!2jm!h}7nX1?5#iy<6RX$1Ha;AT6HVM{7IiNf@gdHVhD%HAe$#Xlnj}lE6&Y3 z;)seV`MMfWo491mHP6Q#L^RYK6X|R@8Rx0mSgK@^XngTBVUPF3SkCNh(47INj^Ju)6CAM zK2e1qs{aLH6K(sHro973jEzj8w5HYi{PJo9Pi16=V>taYDM^3&v=?Ki4XP|N)Gf!Q z(MOKG0c1!uT?3j=)|(V!lYQAhrkQn20w+r=pyh+Z0RY4|2p$x$0uUet&;lkvW&i-e zHQ<*o)4UG6I}`#WRU#;VfCz&!NpYB#ul0sT!jw21qpVb;A}QTC2%I<+wV4nakEeC? zrPBl`Iu0TxHwLu1y!@xa63TRRUdebhYBZNm6sbBWd^g=$Q!cs(9aFiA&gV`p?07iy zqIs#j)=Djiv)#|tG^1;h* zYWLY+9qnv8{)7o7X1&|he!4!8%x^i?_4<5!yhtnW?wgJ;`}0jMpT^#Fem%y%zm8$O z_qOZX`OZ|8<;S;getbPyAI`h??(MH1SLgemKE8YR#~%QE9xNI{>xc~|;3~(4kVx%d zL&;TnK8H~oJAMwQw=e%30rTI%wa!KGtVMC8Ij%+Xl;Q?23bgL5#fbFrtj9{sIdiEUnnebZy<;%JS^v-Ol!%b=uAe*s9pheR{dOorlKf+sO~3b>1n6;;P&! zjFsBkDN0b~+bvEpcHS+?u&>-L&Gz5hEz6JK+bb_lbKa{cFRk3ejnmlMtGcf5gr(z+Zp3~*H)G>%B^A2f}t@*g%&8@n8~%-dHTwl4ec zAGTp5_>bB*(p-*S?|iR_lj3qa@00vgGkm1(dyrIHx1Xs(%#7WXn-oWlJQ1#WJ+DP9 zxH8}}$u#`4^hC9@3*l*U?QRI|0e|cuW%dj}#L#(g$crj<_KGHS-JnuYqeC?d46AM|YBIE;_U*|33W9KG84y0Y6u9aw!YnZag}Hxx{nD#^vmWytyvX ze$Hl0_Z=9ovWl9JLY4|t(10o>W~8U&R)XM9%9O@;i_?mcgW#J}c-Ngvm|#)G;V^Tl zH>8#;+!()+)8-YFjQE+N9KYb1B9V)a)X-zgA{`+=l>ta z3=qmZCjjp6=)_P!nd9g5i2HHdy<_$rZ{H<SjP^62WWllq}>-X~S~PWuneeg7a* zy5$|!LjmZmpLn%QV*%+>Y{qS?-Jj`~6HKKg9FiVlYg~Pk=>+H7x`PQu{VaBvhv?NY zesB2c{M9oivAXTYo4o!g9?Ne??we7LMZ{w~EJ~|%@`?Ayh_4HZ@~*c;B4y{@=#~lQ zA)Rof@VJr>bUDAP0~05kP{Ty`KH_Ko_8a%?nr8~x*;42LSs{#;$H8O-vvy=7rY441 zReM=HgFD871VU!)gUG$^#}nQtq`jMrRd-fq|>VXC?oTb-4{^sd$u(*J%)gy)v z)iN=g)6ob^k@Iu8GWbR((tZ$e_6BdU6v9q(4HvoHm&5mSdRxwxg-gYeEF(OZcB(P134->p1e{I zUL50TkE^ar=(9^_JKxkak@av~DXgmI^~p$+=VI^F)3xMgIeZ0k{tk z_Xi+=06Os?|Ay!P%U^5)?g454bn3eI$WEygpGV@Sw$@TzHVq*l+!+by8xL7SB0RHemmYt2%d=lW2NR$J{# zXTbU1%5a;og_Qfx*__arrTg7cRQ#xs_J;NT1ooov7M;e;;S6z?)sb(tFozuF7#5w5 z=G~UzyH%*sj+Xt`A5p_NZjjoVZ~e^;O#Z*QVd}2nZ*ItrX2)^EO`GL095)=%iumr$ zH_5%Lebe~}yZo*`hV{0bJdPW*vhIC+`^yc-4G&Pl@7_Lpb8)=?>4x-krklBM@T|+PyzC;+M!Y5ZbUo1=63r1SeMAtEB=fzUgHHd9XERoo z@#e$$2V&91X>wA7HmM>>8tVy$*N$89E^9nnF+kuy56~vyF)-v$AJ!4V)a&!RLkXz( z%m;8=?;^?BHM7+l3O~fqi?b}x))e)>ge$**X`H1ECi56nnh!RX45y3O4rOaJm5ye~ z`0OnYHkD~wjMgT?G@BjO^3;?0EQXpZrpj~^w`}?*%4e$dnw(dLTB_#iBm)WGXr}SZ zH~r~Dsm=LDPvz3l8Crpr4%G45%6F;SPwr2^#4OqY9-nZ}Cja!|JrvgscCP*Q-#*mS z%ypG)+Wwfo@4jU&(Y!lVUKG!DOQL0euHLKG{+8HpewUmo&3;g_y81S_>E|>5OZSAH zWd3)qKim5^$BU@kk6`r|`|~sUk8*K7tX+C}{hak)uG7`Z=2X>OV6MO6THyAO8u2@q zXDz-@T_k7Dv1wQwpA}Z7+quzG|K6Ld zUR!8PLc1Xg$Z;~#!|2gv4*=<&kn|y9Vz~euCRlBu<>G4ZR|3RHH2(62Slo#COF}dN zL~_zCDRPb@7RejB00I@LU-K&tDqRea!`zlwazS3leiXuiR3tREt!c@SJT4qvSTrxv zlC+J~01yB(tcdBn21NS>rka0oJ_Gif+ct^&z?5~wr|N5@(;@QWkpkibo*x98$`chO z@3X!wS@ZE7j~!jM!h~&FIE(kgoMSPX>;CiI#5)iCJxTc6>^x%$#s=0%m^$ifHwC73 zsqQc>xRUq7T<4QwkM7%uj0(Vfr-A%~90sJ40}g}3+bq0*r&808sLB8HI{po*ZGT-y zPy+&Y9TD9w1PHN|`Ghs47^tX?Z~KJxhnEbu-1m9JYzAZ4?|?qJTUgK?F%Iq z_rq^s`urPGr8@kYwzH>md4Fl+Aa$x-*g)&>+k*3Ky|CO9<{!S>ixpLEO?z#nK{l6rN~W8S4g7goB-!nEH#7DG3Aq9v+;zmpjwbGM zxtP$>Ik-Bl`P)00fYM=lHk3{*x*k0m4|4Pi{T=cLAKvFoo~9;ZEKFZ8#b!fgW!E1r znQzV-WP0O%=Uc&Lfig6X%DJbgaet;@hs{PxKt7_=}M-B=8lS5km3l3pB=xg|k zLn<1I|K^a*!9*N~Z0t1t#UVGnN~BcgZaXv|KyM&S#wRA;^`{WCg%_L zTlhz7tlqz1mYZ=gYQ%BKjPvEeA}$U%bJa5X9S2xW8fCWCeR@aw^$btD{lu~xNy?9c z(VgDwOJI-jJ21d;h#v39-jUbhdZS{uVp!a$e6=Ub4fZ7k^>G|ReLp!2mm?}I)VS@h z){s6Lkp3@n#DQo3KS%t}9MNo5YR-HrtA=~8BSyUQOmj_g1(rN4-c2O81{mG|RX1|r zdt$`LxQJ^afdp2-mXD|(2*83dR=Z{OF+nW=;wa+Yw&_lfYz05{)Dh~yJ!g0gfdN3$ z7iKOV;R}?yND!B~G7jH=#vlNo{3ifBP8{6#5IqPu5`tLZ@SVm)9f$8J2{?T3{f+Ow z@(AFt`Bc5QFHD(Ka*Cr`EKK0N;xG-!%6eFI#*Y1Blw3hP66P-xdL-%NtLPiHI4-It z#x&j?2Jp?W>Y2L1hZ*J{8R!oC4ezmxU7m+lP;v2O;0#xjsFs+sw4;{Pj7s+e4Zgp~ zdu_?->6LuR)_{u_;|q{6J>uPZ?V z+lLq69P8zb)51IPWRgnMgpI|64_O#0pJvpTC?#>*_H4-b+#k)L_h8A6I^P`3l%=?< zy_>}OA%gc2nGwp6@R-z3~??$G5isy0weY9>l^o-XBmEd=AI;h1fF z*eUkmK6RNeAL6&|$71NW zENb%Ef4nhGuOICzl73%2ASL;*xAYtS?8v#Jm_sN0L6FXgllp3dj*a{G_Vd9Q>CZ`A zcbB&9le=@ogRbQtFO_t5ASCF$(p$~n&|Zr*DLj4Uv9}4WI(~KX)Mk(KS!d+E=N>!X zn-J%<&%eL@PN8>Y-wh)>os2VIJyg;X3ORJ3HI0lmO!wM-!<}vSJdxVeoRpLf;c!E0 zColZ+VX1L=;Y|rOd`QD2QcV3;@`~~*Y5hnD{fmLsa6}pb>kF=2i9Elc93JC%d4HqN zI+P9gHpaZDbZu}W3H2T7;rU5LFw+2Ak-vqAHHz9afDQxvINl96GEZz}dhRJjx8O~7 z*C98D!svoJk%2L=_y+z}Q-*CLnVODy`$o2J3Cu7F_F+7*(YFzP4CB=Z z9T!0u9&MK=9~wX_!>;A-ZJhDRaCh{AA=)Wb!a%t-sHNxu3oy_nVFg7mgcD@Gq6N*Y zM92Br>=L^1EKz*+;&zXrNC1dDKUln3w~9JXgVb!fdRt7|q$jd_#>(=kbMavma?6at zSFU8~)8l>}?j12b-$cdKVR_wt@kaTR*x=FfM1es)*!$ggAnFsKbo$0tYBtj#-)S|S zbhTMdofB*!S9k4umhZhF5=l$iM76?~`so}oZ@>>t`UUkQ5{_YX8~R%KW>+!dKqwlc zBiMPs)$G^}#Pc2?nJZ<#p5d|J&*-7Lb~0J5W;}FT^?R7VgLIWY!O+sfY&dqJtNlPH-*Lv)Z=Yny%=_D7}15 z$fN+kmk0vnQ534h;tAtka1HM0I65npQQtc8h+wjKeKbq6ANWx*_w!5L@~`r=jJm!& zh=Nd#`FHmzxFW8#L=k)L{RxDD#g71Rj9B}rfCHw$s#Apik67>e?O(1Q{1Sr(Z%eqD z{9Q%v+^;5PAeGdC<{gX~6eZoJK-g^;FAhti}JR&%WkYL~4xKFrH@&XqSqm502Q& zp1&H8E#i8|5=U!cplqq~2yyxe<&NywpDe0{kDZOFC^h>*3iEd~-Le@8XyUIcO`hK~a`b7c# zXt}EN&_w*>iy}B_g}R#NWK!luF^^7#rpeG`TE|6+NL+=sgXUD`;zg;`uClti-f*Z=#DxW!{SQtde7aDOy&wzVTQ+vVZ@gKbR{`#z&uztJjG=&QK5-@nv7U#YT!{?NiOO4Wzy zxZ2zpHvJ%LTpxSuz?S>L+yG-*V}@VV!ylOoW34oF&)nEE7EpBduP6Sbf2Zm={`hyQPW#X)*(j2BuW<+M(Cp7!@@K04JIvazcSM6t)4kaLmRsT+jRANL>TP2|1VyCZI*g(PU zrnvoRk=}cI!PjZp03gKBQ?)E!V4#9O9kpV1<+{)veZ#W0D{v26E6MaO+q}hZYcA(q z>8QoU>A{TLbu!uQoF|vN-E%A>n6%6C{wHBN5vBV-@vd^80tnz3 z04u;>CkQ0912>Xl#tmbmZoOmWj*BKjLP(5pQoOv5X+jh{O=c$if;0h5IJeQG6Uo)b zW2?~uS6Pdd!M}rj3Wn7Kh~}{A>j^4%eT$H4v=tj#a$Bv$igPgVdXk~>E4w%&`vuBi z2$~!~_&Puo%Iv#Qn2uBTGwhO_T6YtlLdqZJi~D){j)u5zIjvb~bxym-+!Au3LwtHwO0{cX3>-EWAm z>RNF&sZ#2rHhYY?NAdSP+{)dd{nEwdL8}Hd<{85VAZz*0#BbK&zwS5Pds;gWxZ)gi82b!=rZqP(#J2h^`c=$BmYGZzP9{*Js zvnfiO+qF$9t<^Js%YM}!dfWW|8?b?i*ZV<2Z4u8&4d}|-8xGaN?-{|`UOk*s#NIA5 z2yGGP*VX*zBNB72=c6*))#qdKUk}bdBB3i&IVyB+7Zd8-H5ZfG(uWsQx@v-7rVUNp zzRZ|9)O?vWcM+6$U=b;JId7NlcDdkKR&%*%ncQ-@Iyb=h~$O?ukxrQg@<)u&$% zahcy#PMc{ko%_}2C~o&FthLS?ZRrJg#Y+?QIvDbM0L2a zTe8*MvrKTRy-(mLovf6A@U!f7%yumTBsYB=OdgA!IZ~r3y1x$Un(v`KQX!^rg@fs^ zs%K>7Z(Ypufjs7D7{@>lZO4ft^@#aIJqDj_eC_Uy@(+@p6g}f_g2URfhmnHSr#)

3! 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 diff --git a/Documentation/Images/page_settings.png b/Documentation/Images/page_settings.png deleted file mode 100644 index 04cd438f3fbebd54f838b3dd6d41148c9251847c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21187 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. - - - **Table of Contents** - -.. toctree:: - :maxdepth: 3 - :titlesonly: - :glob: - - Introduction/Index - AdministratorManual/Index diff --git a/Documentation/Introduction/About/Index.rst b/Documentation/Introduction/About/Index.rst deleted file mode 100644 index 4b35ffc..0000000 --- a/Documentation/Introduction/About/Index.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. ================================================== -.. FOR YOUR INFORMATION -.. -------------------------------------------------- -.. -*- coding: utf-8 -*- with BOM. - -.. include:: ../../Includes.txt - -.. _about: - -What does it do? -================ - -This extension supports TYPO3 administrators in performing A/B tests. This is useful when a site owner want to measure whether a new version improves or reduces user interaction compared to the current version. - - -**Features of the extension** - -- Caching of each page version -- A real 50/50% chance. That means: No selection by random, because of the unreliable random method. So the versions are always taken alternately. -- Complete different content with same page id. So only one URL for two versions. The displayed version is determined by the cookie value. - - -|img-demo| \ No newline at end of file diff --git a/Documentation/Introduction/Index.rst b/Documentation/Introduction/Index.rst deleted file mode 100644 index 22a39ef..0000000 --- a/Documentation/Introduction/Index.rst +++ /dev/null @@ -1,21 +0,0 @@ -.. ================================================== -.. FOR YOUR INFORMATION -.. -------------------------------------------------- -.. -*- coding: utf-8 -*- with BOM. - -.. _introduction: - -Introduction -============ - -.. only:: html - - This chapter gives you a basic introduction about the TYPO3 CMS extension "*abtest2*". - -.. toctree:: - :maxdepth: 5 - :titlesonly: - - About/Index - Support/Index - Thanks/Index diff --git a/Documentation/Introduction/Support/Index.rst b/Documentation/Introduction/Support/Index.rst deleted file mode 100644 index e6c81af..0000000 --- a/Documentation/Introduction/Support/Index.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. ================================================== -.. FOR YOUR INFORMATION -.. -------------------------------------------------- -.. -*- coding: utf-8 -*- with BOM. - -.. include:: ../../Includes.txt - -.. _support: - -Need Support? -============= -There are various ways to get support for this extension! - -Stackoverflow -------------- -Please use https://stackoverflow.com to get best support. Tags you should use are `typo3` and `abtest2`. - -Sponsoring ----------- -If you need a feature which is not yet implemented, feel free to contact me anytime! - -Private/Personal support ------------------------- -If you need private or personal support, ask one of the developers for it. - -**Be aware that this support might not be free!** diff --git a/Documentation/Introduction/Thanks/Index.rst b/Documentation/Introduction/Thanks/Index.rst deleted file mode 100644 index e4a601c..0000000 --- a/Documentation/Introduction/Thanks/Index.rst +++ /dev/null @@ -1,32 +0,0 @@ -.. ================================================== -.. FOR YOUR INFORMATION -.. -------------------------------------------------- -.. -*- coding: utf-8 -*- with BOM. - -.. include:: ../../Includes.txt - -.. _thanks: - -Say thanks! -=========== -This extension and manual has been created in hours, mostly by a single person. -It is actively maintained to fit all supported TYPO3 versions, user interface concepts and best practice approaches. - -If this extension helps you in anyway to achieve your requirements, please think about giving something back. -Or you want to sponsor a feature to extend something that's already there? Then find some ideas to make me happy below - I'm looking forward to get in contact with you! - -Nice mails -^^^^^^^^^^ -Some nice words fit every time - just drop me some kind words by mail to make me happy :-) - -Talk about it -^^^^^^^^^^^^^ -If you like what I've built, then share it wherever it fits. Maybe this will help to spread the word and find others that can profit from it, too. - -Let's have a tee -^^^^^^^^^^^^^^^^^^^ -If you're in the region of Aachen, just let me know. I'm always looking forward to meeting new and known faces and to exchange on several topics. - -Money -^^^^^ -If you have too much of it and want to share parts of your money with me, just let me know and we'll find a way to organize that :-) diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg deleted file mode 100644 index 940c6a0..0000000 --- a/Documentation/Settings.cfg +++ /dev/null @@ -1,48 +0,0 @@ -[html_theme_options] -project_contact = -use_opensearch = -project_home = -project_issues = -github_revision_msg = -github_branch = -github_repository = -project_repository = -project_discussions = -github_sphinx_locale = -github_commit_hash = - -[intersphinx_mapping] -t3tsref = http://docs.typo3.org/typo3cms/TyposcriptReference/ -t3editors = http://docs.typo3.org/typo3cms/EditorsTutorial/ -t3start = http://docs.typo3.org/typo3cms/GettingStartedTutorial/ - -[latex_elements] -papersize = a4paper -preamble = \usepackage{typo3} -pointsize = 10pt - -[general] -project = abtest2 -release = 1.0.0 -version = 1.0 -copyright = 2017 - -[notify] -about_new_build = no -# have one or more receivers notified -# about_new_build = email-1 [, email2, ...] - - -# About Settings.cfg - -# normal: -# https://github.com/marble/typo3-docs-typo3-org-resources/blob/master/TemplatesForCopying/ExampleFiles/Settings-minimal.cfg - -# extensive: -# https://github.com/marble/typo3-docs-typo3-org-resources/blob/master/TemplatesForCopying/ExampleFiles/Settings-extensive.cfg - -# Example files: -# https://github.com/marble/typo3-docs-typo3-org-resources/tree/master/TemplatesForCopying/ExampleFiles - -# More: -# http://mbless.de/blog/2015/10/24/a-new-task-for-an-old-server.html#ini-files diff --git a/Documentation/Settings.yml b/Documentation/Settings.yml deleted file mode 100644 index 7304ad2..0000000 --- a/Documentation/Settings.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This is the project specific Settings.yml file. -# Place Sphinx specific build information here. -# Settings given here will replace the settings of 'conf.py'. - ---- -conf.py: - copyright: 2017 - project: abtest2 - version: 1.0 - release: 1.0.0 - latex_documents: - - - Index - html_theme_options: - github_repository: svewap/abtest2 - github_branch: master - latex_elements: - papersize: a4paper - pointsize: 10pt - preamble: \usepackage{typo3} - intersphinx_mapping: - t3tsref: - - http://docs.typo3.org/typo3cms/TyposcriptReference/ - - null - t3start: - - http://docs.typo3.org/typo3cms/GettingStartedTutorial/ - - null - t3editors: - - http://docs.typo3.org/typo3cms/EditorsTutorial/ - - null -... diff --git a/README.md b/README.md index 11a9c62..7489cf0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# abtest2 TYPO3 Extension +# abtest TYPO3 Extension Extension for A/B-Tests @@ -19,60 +19,5 @@ Additional header information may be specified both for the original version as #### Demo -![Demo](https://raw.githubusercontent.com/svewap/abtest2/master/Documentation/Images/demo.gif) - -#### Example for Google Tag Manager: - -You have two options to define the parameter: By page settings or by TypoScript: - -##### Additional Header Information at page settings - -On original page (version A): - -```javascript - -``` - -On version B: - -```javascript - -``` - - -##### TypoScript - -```typo3_typoscript -[globalVar = GP:abtest = a] - page.headerData.129 = TEXT - page.headerData.129.value ( - - ) -[global] -[globalVar = GP:abtest = b] - page.headerData.129 = TEXT - page.headerData.129.value ( - - ) -[global] - -page.headerData.130 = TEXT -page.headerData.130.value ( - -.... - -) -``` +![Demo](https://raw.githubusercontent.com/werkraum-media/abtest/master/Documentation/Images/demo.gif) diff --git a/Resources/Private/Language/de.locallang_db.xlf b/Resources/Private/Language/de.locallang_db.xlf index d10644d..fe87edf 100644 --- a/Resources/Private/Language/de.locallang_db.xlf +++ b/Resources/Private/Language/de.locallang_db.xlf @@ -1,57 +1,48 @@ - - - -

- - - Site B - Seite B - - - Cookie Lifetime - Cookie Lebenszeit - - - Additional Header Information - Zusätzliche Header Informationen - - - Additional footer information - Zusätzliche Footer Informationen - - - Counter - Zähler - - - AB Test Settings - AB Test Einstellungen - - - 1 month - 1 Monat - - - 1 week - 1 Woche - - - 1 day - 1 Tag - - - 1/2 day - 1/2 Tag - - - 1 hour - 1 Stunde - - - 1 minute - 1 Minute - - - - \ No newline at end of file + + + +
+ + + A/B Testing + A/B Testing + + + Variant + Variante + + + Cookie Lifetime + Cookie Lebenszeit + + + 1 month + 1 Monat + + + 1 week + 1 Woche + + + 1 day + 1 Tag + + + 1/2 day + 1/2 Tag + + + 1 hour + 1 Stunde + + + 1 minute + 1 Minute + + + Counter + Zähler + + + + diff --git a/Resources/Private/Language/locallang_db.xlf b/Resources/Private/Language/locallang_db.xlf index 4f941b9..d5ed083 100644 --- a/Resources/Private/Language/locallang_db.xlf +++ b/Resources/Private/Language/locallang_db.xlf @@ -1,45 +1,38 @@ - - - -
- - - Site B - - - Cookie Lifetime - - - Additional header information - - - Additional footer information - - - Counter - - - AB Test Settings - - - 1 month - - - 1 week - - - 1 day - - - 1/2 day - - - 1 hour - - - 1 minute - - - - \ No newline at end of file + + + +
+ + + A/B Testing + + + Variant + + + Cookie Lifetime + + + 1 month + + + 1 week + + + 1 day + + + 1/2 day + + + 1 hour + + + 1 minute + + + Counter + + + + diff --git a/Tests/Fixtures/BasicDatabase.csv b/Tests/Fixtures/BasicDatabase.csv new file mode 100644 index 0000000..eaad2eb --- /dev/null +++ b/Tests/Fixtures/BasicDatabase.csv @@ -0,0 +1,10 @@ +"pages",,,,,,,,,,,,,, +,"uid","pid","slug","title",tx_abtest_variant,hidden,tx_abtest_cookie_time,,,,,,, +,1,0,"/","Page 1 Title (No Variant)",0,0,604800,,,,,,, +,2,1,"/page-2","Page 2 Title (Variant A)",3,0,604800,,,,,,, +,3,1,"/page-3","Page 3 Title (Variant B)",0,0,604800,,,,,,, +,4,1,"/page-4","Page 4 Title (Variant A)",5,0,2419200,,,,,,, +,5,1,"/page-5","Page 5 Title (Variant B)",0,1,604800,,,,,,, +"sys_template",,,,,,,,,,,,,, +,"uid","pid","root","clear","constants","config",,,,,,,, +,1,1,1,3,"databasePlatform = mysql","",,,,,,,, diff --git a/Tests/Fixtures/FrontendRendering.typoscript b/Tests/Fixtures/FrontendRendering.typoscript new file mode 100644 index 0000000..d9ff642 --- /dev/null +++ b/Tests/Fixtures/FrontendRendering.typoscript @@ -0,0 +1,16 @@ +config { + debug = 1 +} + +page = PAGE +page { + config { + debug = 1 + } + + 5 = TEXT + 5.field = title + + 10 = TEXT + 10.value = Example content from TypoScript +} diff --git a/Tests/Fixtures/Sites/default/config.yaml b/Tests/Fixtures/Sites/default/config.yaml new file mode 100644 index 0000000..a127453 --- /dev/null +++ b/Tests/Fixtures/Sites/default/config.yaml @@ -0,0 +1,32 @@ +base: / +languages: + - + title: English + enabled: true + base: / + typo3Language: default + locale: en_GB.UTF-8 + iso-639-1: en + websiteTitle: '' + navigationTitle: English + hreflang: en-GB + direction: '' + flag: gb + languageId: 0 + fallbackType: strict + fallbacks: '0' + - + title: Deutsch + enabled: true + base: /de + typo3Language: de + locale: de_DE.UTF-8 + iso-639-1: de + navigationTitle: Deutsch + hreflang: de-DE + direction: '' + flag: de + websiteTitle: '' + languageId: 1 +rootPageId: 1 +websiteTitle: 'Example Website' diff --git a/Tests/Functional/FrontendRenderingTest.php b/Tests/Functional/FrontendRenderingTest.php new file mode 100644 index 0000000..617d33b --- /dev/null +++ b/Tests/Functional/FrontendRenderingTest.php @@ -0,0 +1,309 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace WerkraumMedia\ABTest\Tests\Functional; + +use Symfony\Component\HttpFoundation\Cookie; +use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest; +use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalResponse; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class FrontendRenderingTest extends FunctionalTestCase +{ + protected $testExtensionsToLoad = [ + 'typo3conf/ext/abtest', + ]; + + protected $pathsToLinkInTestInstance = [ + 'typo3conf/ext/abtest/Tests/Fixtures/Sites' => 'typo3conf/sites', + ]; + + protected function setUp(): void + { + parent::setUp(); + + $this->setUpBackendUserFromFixture(1); + + $this->importCSVDataSet(__DIR__ . '/../Fixtures/BasicDatabase.csv'); + } + + /** + * @test + */ + public function opensDefaultPageIfNothingIsConfigured(): void + { + $request = new InternalRequest(); + $request = $request->withPageId(1); + $result = $this->executeFrontendRequest($request); + + self::assertSame(200, $result->getStatusCode()); + self::assertSame('', $result->getHeaderLine('Set-Cookie')); + self::assertStringContainsString('Page 1 Title (No Variant)', $result->getBody()->__toString()); + $this->assertPageIsNotCached($result); + $this->assertCounterOfPage(1, 0); + } + + /** + * @test + */ + public function opensVariantAForFirstVisitor(): void + { + $request = new InternalRequest(); + $request = $request->withPageId(2); + $result = $this->executeFrontendRequest($request); + + self::assertSame(200, $result->getStatusCode()); + self::assertStringContainsString('Page 2 Title (Variant A)', $result->getBody()->__toString()); + $this->assertPageIsNotCached($result); + $this->assertCookie($result, 'ab-2', '2'); + $this->assertCounterOfPage(2, 1); + } + + /** + * @test + */ + public function opensVariantBForSecondVisitor(): void + { + $this->opensVariantAForFirstVisitor(); + + $request = new InternalRequest(); + $request = $request->withPageId(2); + $result = $this->executeFrontendRequest($request); + + self::assertSame(200, $result->getStatusCode()); + self::assertStringContainsString('Page 3 Title (Variant B)', $result->getBody()->__toString()); + $this->assertCookie($result, 'ab-2', '3'); + $this->assertPageIsNotCached($result); + $this->assertCounterOfPage(2, 1); + $this->assertCounterOfPage(3, 1); + } + + /** + * @test + */ + public function opensVariantStoredInCookie(): void + { + $this->opensVariantAForFirstVisitor(); + + $request = new InternalRequest(); + $request = $request->withPageId(2); + $request = $request->withAddedHeader('Cookie', 'ab-2=2'); + $result = $this->executeFrontendRequest($request); + + self::assertSame(200, $result->getStatusCode()); + self::assertStringContainsString('Page 2 Title (Variant A)', $result->getBody()->__toString()); + $this->assertPageIsCached($result); + $this->assertCookie($result, 'ab-2', '2'); + // 1 from first visit, but not 2 as 2nd visit is via cookie. + $this->assertCounterOfPage(2, 1, 'Opening from cookie should not increase counter.'); + $this->assertCounterOfPage(3, 0, 'Opening from cookie should not increase counter.'); + } + + /** + * @test + */ + public function opensDefaultPageIfBotWasDetected(): void + { + $request = new InternalRequest(); + $request = $request->withPageId(2); + $request = $request->withAddedHeader('User-Agent', 'Storebot-Google'); + $result = $this->executeFrontendRequest($request); + + self::assertSame(200, $result->getStatusCode()); + self::assertStringContainsString('Page 2 Title (Variant A)', $result->getBody()->__toString()); + $this->assertPageIsNotCached($result); + $this->assertCookieWasNotSet($result); + $this->assertCounterOfPage(2, 0); + } + + /** + * @test + */ + public function opensRequestedPageIfVariantPageDoesNotExist(): void + { + $request = new InternalRequest(); + $request = $request->withPageId(4); + $request = $request->withAddedHeader('Cookie', 'ab-4=5'); + $result = $this->executeFrontendRequest($request); + + self::assertSame(200, $result->getStatusCode()); + self::assertStringContainsString('Page 4 Title (Variant A)', $result->getBody()->__toString()); + $this->assertPageIsNotCached($result); + $this->assertCookie($result, 'ab-4', '4'); + $this->assertCounterOfPage(4, 1); + $this->assertCounterOfPage(5, 0); + } + + /** + * @test + */ + public function opensRequestedPageIfCookieDoesNotMatchRequestedPage(): void + { + $request = new InternalRequest(); + $request = $request->withPageId(2); + $request = $request->withAddedHeader('Cookie', 'ab-2=5'); + $result = $this->executeFrontendRequest($request); + + self::assertSame(200, $result->getStatusCode()); + self::assertStringContainsString('Page 2 Title (Variant A)', $result->getBody()->__toString()); + $this->assertPageIsNotCached($result); + $this->assertCookie($result, 'ab-2', '2'); + $this->assertCounterOfPage(2, 1); + $this->assertCounterOfPage(5, 0); + } + + /** + * @test + */ + public function opensVariantBForSecondVisitorIfVariantFromCookieDoesNotMatchVariantB(): void + { + $this->opensVariantAForFirstVisitor(); + + $request = new InternalRequest(); + $request = $request->withPageId(2); + $request = $request->withAddedHeader('Cookie', 'ab-2=5'); + $result = $this->executeFrontendRequest($request); + + self::assertSame(200, $result->getStatusCode()); + self::assertStringContainsString('Page 3 Title (Variant B)', $result->getBody()->__toString()); + $this->assertPageIsNotCached($result); + $this->assertCookie($result, 'ab-2', '3'); + $this->assertCounterOfPage(2, 1); + $this->assertCounterOfPage(3, 1); + } + + /** + * @test + */ + public function cookieHasDefaultLifetime(): void + { + $request = new InternalRequest(); + $request = $request->withPageId(2); + $result = $this->executeFrontendRequest($request); + + self::assertSame(200, $result->getStatusCode()); + $cookie = Cookie::fromString($result->getHeaderLine('Set-Cookie')); + self::assertSame(604800, $cookie->getMaxAge()); + } + + /** + * @test + */ + public function cookieHasConfiguredLifetime(): void + { + $request = new InternalRequest(); + $request = $request->withPageId(4); + $result = $this->executeFrontendRequest($request); + + self::assertSame(200, $result->getStatusCode()); + $cookie = Cookie::fromString($result->getHeaderLine('Set-Cookie')); + self::assertSame(2419200, $cookie->getMaxAge()); + } + + /** + * Ensure TYPO3 caching works as expected. + * The first call should create a proper cache entry. + * We should still be able to retrieve the other variant by adding the cookie. + * The 2nd variant should also be delivered from cache on 2nd request. + * + * @test + */ + public function returnsCachedPage(): void + { + $request = new InternalRequest(); + $request = $request->withPageId(2); + $result = $this->executeFrontendRequest($request); + self::assertStringContainsString('Page 2 Title (Variant A)', $result->getBody()->__toString()); + $this->assertPageIsNotCached($result); + + $request = new InternalRequest(); + $request = $request->withPageId(2); + $request = $request->withAddedHeader('Cookie', 'ab-2=2'); + $result = $this->executeFrontendRequest($request); + self::assertStringContainsString('Page 2 Title (Variant A)', $result->getBody()->__toString()); + $this->assertPageIsCached($result); + + $request = new InternalRequest(); + $request = $request->withPageId(2); + $result = $this->executeFrontendRequest($request); + self::assertStringContainsString('Page 3 Title (Variant B)', $result->getBody()->__toString()); + $this->assertPageIsNotCached($result); + + $request = new InternalRequest(); + $request = $request->withPageId(2); + $request = $request->withAddedHeader('Cookie', 'ab-2=3'); + $result = $this->executeFrontendRequest($request); + self::assertStringContainsString('Page 3 Title (Variant B)', $result->getBody()->__toString()); + $this->assertPageIsCached($result); + } + + private function assertCounterOfPage( + int $pageUid, + int $expectedCounter, + string $message = '' + ): void { + $actualCounter = $this->getConnectionPool() + ->getConnectionForTable('pages') + ->select(['tx_abtest_counter'], 'pages', ['uid' => $pageUid]) + ->fetchFirstColumn()[0] ?? 0 + ; + + self::assertSame( + $expectedCounter, + $actualCounter, + 'Counter for page ' . $pageUid . ' was not as expected. ' . $message + ); + } + + private function assertCookie( + InternalResponse $result, + string $name, + string $value + ): void { + $cookie = Cookie::fromString($result->getHeaderLine('Set-Cookie')); + self::assertSame($name, $cookie->getName()); + self::assertSame($value, $cookie->getValue()); + self::assertSame('/', $cookie->getPath()); + self::assertSame('lax', $cookie->getSameSite()); + self::assertNull($cookie->getDomain()); + } + + private function assertCookieWasNotSet(InternalResponse $result): void + { + self::assertSame( + '', + $result->getHeaderLine('Set-Cookie'), + 'Cookie was set but was not expected to be set.' + ); + } + + private function assertPageIsNotCached(InternalResponse $result): void + { + self::assertSame('', $result->getHeaderLine('X-TYPO3-Debug-Cache')); + } + + private function assertPageIsCached(InternalResponse $result): void + { + self::assertStringStartsWith('Cached page generated', $result->getHeaderLine('X-TYPO3-Debug-Cache')); + } +} diff --git a/composer.json b/composer.json index fd1d400..3f1f11d 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,77 @@ { - "name": "svewap/abtest2", + "name": "werkraummedia/abtest", "type": "typo3-cms-extension", - "description": "", - "authors": [], + "license": "GPL-2.0-or-later", + "description": "Provides A/B Testing for TYPO3.", + "homepage": "https://github.com/werkraum-media/abtest", + "support": { + "docs": "https://docs.typo3.org/p/werkraummedia/abtest/master/en-us/", + "email": "coding@daniel-siepmann.de", + "issues": "https://github.com/werkraum-media/abtest/issues", + "source": "https://github.com/werkraum-media/abtest" + }, + "authors": [ + { + "name": "Sven Wappler", + "email": "typo3YYYY@wappler.systems", + "homepage": "https://wappler.systems/", + "role": "Developer" + }, + { + "name": "Daniel Siepmann", + "email": "coding@daniel-siepmann.de", + "homepage": "https://daniel-siepmann.de/", + "role": "Developer" + } + ], "require": { - "typo3/cms-core": "^8.7.1" + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0", + "typo3/cms-core": "^11.5", + "typo3/cms-frontend": "^11.5", + "matomo/device-detector": "^6.1", + "symfony/http-foundation": "^5.4", + "psr/http-message": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "typo3/testing-framework": "^6.16", + "phpunit/phpunit": "^9.6", + "friendsofphp/php-cs-fixer": "^3.14", + "cweagans/composer-patches": "^1.7" }, "autoload": { "psr-4": { - "WapplerSystems\\ABTest2\\": "Classes" + "WerkraumMedia\\ABTest\\": "Classes/" + } + }, + "autoload-dev": { + "psr-4": { + "WerkraumMedia\\ABTest\\Tests\\": "Tests/" + } + }, + "extra": { + "typo3/cms": { + "cms-package-dir": "{$vendor-dir}/typo3/cms", + "extension-key": "abtest", + "web-dir": ".Build/web" + }, + "patches": { + "typo3/testing-framework": { + "Allow to test requests with cookies": "patches/testing-framework-cookies.patch" + } + } + }, + "scripts": { + "post-autoload-dump": [ + "@php -r 'is_dir($extFolder=__DIR__.\"/.Build/web/typo3conf/ext/\") || mkdir($extFolder, 0777, true);'", + "@php -r 'file_exists($extFolder=__DIR__.\"/.Build/web/typo3conf/ext/abtest\") || symlink(__DIR__,$extFolder);'" + ] + }, + "config": { + "allow-plugins": { + "typo3/class-alias-loader": true, + "typo3/cms-composer-installers": true, + "cweagans/composer-patches": true } } } diff --git a/ext_emconf.php b/ext_emconf.php deleted file mode 100644 index 974e855..0000000 --- a/ext_emconf.php +++ /dev/null @@ -1,37 +0,0 @@ - 'AB Test Pages', - 'description' => 'With this extension, administrators can deliver different content for the same URL (AB test), depending on cookies or least showed page to realize a most accurate possible test.', - 'category' => 'misc', - 'author' => 'Sven Wappler', - 'author_email' => 'typo3YYYY@wappler.systems', - 'author_company' => 'WapplerSystems', - 'state' => 'stable', - 'uploadfolder' => false, - 'createDirs' => '', - 'clearCacheOnLoad' => true, - 'version' => '1.0.0', - 'constraints' => - [ - 'depends' => - [ - 'typo3' => '7.6.0-9.5.99', - ], - 'conflicts' => - [], - 'suggests' => - [], - ], -]; - diff --git a/ext_localconf.php b/ext_localconf.php index b0bab82..9077cde 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -2,5 +2,4 @@ defined('TYPO3_MODE') or die(); - -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PostProc']['abtest2'] = 'WapplerSystems\\ABTest2\\Helper->determineContentId'; \ No newline at end of file +\WerkraumMedia\ABTest\Switcher::register(); diff --git a/ext_tables.php b/ext_tables.php deleted file mode 100644 index 9be9d9a..0000000 --- a/ext_tables.php +++ /dev/null @@ -1,3 +0,0 @@ -request->hasHeader('Cookie')) { ++ foreach ($this->request->getHeader('Cookie') as $cookie) { ++ [$cookieName, $cookieValue] = explode('=', $cookie, 2); ++ $_COOKIE[$cookieName] = rtrim($cookieValue, ';'); ++ } ++ } + + // Setting up the server environment + $_SERVER = []; diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..e21df3f --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +parameters: + level: max + paths: + - Classes + - Configuration + - Tests + checkMissingIterableValueType: false + reportUnmatchedIgnoredErrors: false + checkGenericClassInNonGenericObjectType: false diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..144e19f --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,35 @@ + + + + + + Tests/Functional/ + + + + + + Classes + + + + + + + diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..73f1769 --- /dev/null +++ b/shell.nix @@ -0,0 +1,83 @@ +{ pkgs ? import { } }: + +let + + php = pkgs.php81.buildEnv { + extensions = { enabled, all }: enabled ++ (with all; [ + xdebug + ]); + + extraConfig = '' + xdebug.mode = debug + + memory_limit = 4G + ''; + }; + composer = pkgs.php81Packages.composer.override { + inherit php; + }; + + projectInstall = pkgs.writeShellApplication { + name = "project-install"; + runtimeInputs = [ + php + composer + ]; + text = '' + composer install --no-interaction --prefer-dist --no-progress --working-dir="$PROJECT_ROOT" + ''; + }; + projectValidateComposer = pkgs.writeShellApplication { + name = "project-validate-composer"; + runtimeInputs = [ + php + composer + ]; + text = '' + composer validate + ''; + }; + projectValidateXml = pkgs.writeShellApplication { + name = "project-validate-xml"; + runtimeInputs = [ + pkgs.libxml2 + pkgs.wget + projectInstall + ]; + text = '' + project-install + xmllint --schema vendor/phpunit/phpunit/phpunit.xsd --noout phpunit.xml.dist + wget --no-check-certificate https://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd --output-document=xliff-core-1.2-strict.xsd + # shellcheck disable=SC2046 + xmllint --schema xliff-core-1.2-strict.xsd --noout $(find Resources -name '*.xlf') + ''; + }; + projectCodingGuideline = pkgs.writeShellApplication { + name = "project-coding-guideline"; + runtimeInputs = [ + php + projectInstall + ]; + text = '' + project-install + ./vendor/bin/php-cs-fixer fix --dry-run --diff + ''; + }; + +in pkgs.mkShell { + name = "TYPO3 Extension abtest"; + buildInputs = [ + php + composer + + projectValidateComposer + projectValidateXml + projectCodingGuideline + ]; + + shellHook = '' + export PROJECT_ROOT="$(pwd)" + + export typo3DatabaseDriver=pdo_sqlite + ''; +}