From 8e6b29976c30c822440d5fc293930a9a38772801 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Fri, 27 Feb 2026 23:50:33 +0100 Subject: [PATCH] v1.691: testy jednostkowe Domain\, infrastruktura PHPUnit, paczka aktualizacji - Dodano testy: SettingsRepositoryTest, LanguagesRepositoryTest, UserRepositoryTest - Infrastruktura: phpunit.xml, composer.json (phpunit/phpunit ^10), tests/bootstrap.php - Bootstrap stuby: \Shared\Cache\CacheHandler (in-memory), \S - Zaktualizowano docs/TESTING.md dla cmsPRO - Paczka: updates/1.60/ver_1.691.zip + manifest Co-Authored-By: Claude Sonnet 4.6 --- composer.json | 10 + docs/TESTING.md | 120 +++------ phpunit.xml | 20 ++ .../Languages/LanguagesRepositoryTest.php | 123 +++++++++ .../Settings/SettingsRepositoryTest.php | 101 +++++++ tests/Unit/Domain/User/UserRepositoryTest.php | 252 ++++++++++++++++++ tests/bootstrap.php | 51 ++++ updates/1.60/ver_1.691.zip | Bin 0 -> 35054 bytes updates/1.60/ver_1.691_files.txt | 4 + updates/1.60/ver_1.691_manifest.json | 49 ++++ updates/versions.php | 2 +- 11 files changed, 641 insertions(+), 91 deletions(-) create mode 100644 composer.json create mode 100644 phpunit.xml create mode 100644 tests/Unit/Domain/Languages/LanguagesRepositoryTest.php create mode 100644 tests/Unit/Domain/Settings/SettingsRepositoryTest.php create mode 100644 tests/Unit/Domain/User/UserRepositoryTest.php create mode 100644 tests/bootstrap.php create mode 100644 updates/1.60/ver_1.691.zip create mode 100644 updates/1.60/ver_1.691_files.txt create mode 100644 updates/1.60/ver_1.691_manifest.json diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d8145b9 --- /dev/null +++ b/composer.json @@ -0,0 +1,10 @@ +{ + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + } +} diff --git a/docs/TESTING.md b/docs/TESTING.md index 2b56703..ff0f547 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -1,106 +1,54 @@ -# Testowanie shopPRO +# Testowanie cmsPRO ## Szybki start ```bash -# Pelny suite (PowerShell — rekomendowane) -./test.ps1 +# Instalacja PHPUnit (jednorazowo) +composer install + +# Uruchomienie testów +./vendor/bin/phpunit # Konkretny plik -./test.ps1 tests/Unit/Domain/Product/ProductRepositoryTest.php +./vendor/bin/phpunit tests/Unit/Domain/Settings/SettingsRepositoryTest.php # Konkretny test -./test.ps1 --filter testGetQuantityReturnsCorrectValue - -# Alternatywne -composer test # standard -./test.bat # testdox (czytelna lista) -./test-simple.bat # kropki -./test-debug.bat # debug -./test.sh # Git Bash +./vendor/bin/phpunit --filter testAllSettingsReturnsMappedArray ``` ## Aktualny stan ```text -OK (805 tests, 2253 assertions) +Testy jednostkowe dla Domain\ (Faza 2 DDD) ``` -Zweryfikowano: 2026-02-24 (ver. 0.318) - ## Konfiguracja -- **PHPUnit 9.6** via `phpunit.phar` +- **PHPUnit 10** via `composer` - **Bootstrap:** `tests/bootstrap.php` - **Config:** `phpunit.xml` -## Struktura testow +## Struktura testów ``` tests/ -|-- bootstrap.php -|-- stubs/ -| |-- CacheHandler.php (inline w bootstrap) -| |-- Helpers.php (Shared\Helpers\Helpers stub) -| `-- ShopProduct.php (shop\Product stub) -|-- Unit/ -| |-- Domain/ -| | |-- Article/ArticleRepositoryTest.php -| | |-- Attribute/AttributeRepositoryTest.php -| | |-- Banner/BannerRepositoryTest.php -| | |-- Basket/BasketCalculatorTest.php -| | |-- Cache/CacheRepositoryTest.php -| | |-- Category/CategoryRepositoryTest.php -| | |-- Coupon/CouponRepositoryTest.php -| | |-- CronJob/CronJobTypeTest.php -| | |-- CronJob/CronJobRepositoryTest.php -| | |-- CronJob/CronJobProcessorTest.php -| | |-- Dictionaries/DictionariesRepositoryTest.php -| | |-- Integrations/IntegrationsRepositoryTest.php -| | |-- Languages/LanguagesRepositoryTest.php -| | |-- Layouts/LayoutsRepositoryTest.php -| | |-- Newsletter/NewsletterRepositoryTest.php -| | |-- Pages/PagesRepositoryTest.php -| | |-- PaymentMethod/PaymentMethodRepositoryTest.php -| | |-- Producer/ProducerRepositoryTest.php -| | |-- Product/ProductRepositoryTest.php -| | |-- ProductSet/ProductSetRepositoryTest.php -| | |-- Promotion/PromotionRepositoryTest.php -| | |-- Settings/SettingsRepositoryTest.php -| | |-- ShopStatus/ShopStatusRepositoryTest.php -| | |-- Transport/TransportRepositoryTest.php -| | |-- Update/UpdateRepositoryTest.php -| | `-- User/UserRepositoryTest.php -| `-- admin/ -| `-- Controllers/ -| |-- ArticlesControllerTest.php -| |-- DictionariesControllerTest.php -| |-- IntegrationsControllerTest.php -| |-- ProductArchiveControllerTest.php -| |-- SettingsControllerTest.php -| |-- ShopAttributeControllerTest.php -| |-- ShopCategoryControllerTest.php -| |-- ShopCouponControllerTest.php -| |-- ShopPaymentMethodControllerTest.php -| |-- ShopProducerControllerTest.php -| |-- ShopProductControllerTest.php -| |-- ShopProductSetsControllerTest.php -| |-- ShopPromotionControllerTest.php -| |-- ShopStatusesControllerTest.php -| |-- ShopTransportControllerTest.php -| `-- UsersControllerTest.php -| `-- api/ -| |-- ApiRouterTest.php -| `-- Controllers/ -| |-- OrdersApiControllerTest.php -| |-- ProductsApiControllerTest.php -| `-- DictionariesApiControllerTest.php -`-- Integration/ (puste — zarezerwowane) +├── bootstrap.php ← autoloader + stuby (CacheHandler, S) +└── Unit/ + └── Domain/ + ├── Languages/LanguagesRepositoryTest.php + ├── Settings/SettingsRepositoryTest.php + └── User/UserRepositoryTest.php ``` -## Dodawanie nowych testow +## Stuby (bootstrap.php) -1. Plik w `tests/Unit/Domain//Test.php`, `tests/Unit/admin/Controllers/Test.php` lub `tests/Unit/api/Controllers/Test.php`. +- `\Shared\Cache\CacheHandler` — in-memory stub z `fetch()`/`store()`/`delete()`/`reset()` +- `\S` — stub z `delete_cache()`, `htacces()`, `get_domain()`, `send_email()` +- `medoo` — mockowany przez PHPUnit (`$this->createMock(\medoo::class)`) + +## Dodawanie nowych testów + +1. Plik w `tests/Unit/Domain//Test.php`. 2. Rozszerz `PHPUnit\Framework\TestCase`. 3. Nazwy metod zaczynaj od `test`. 4. Wzorzec AAA: Arrange, Act, Assert. @@ -108,19 +56,11 @@ tests/ ## Mockowanie Medoo ```php -$mockDb = $this->createMock(\medoo::class); -$mockDb->method('get')->willReturn(42); +$db = $this->createMock(\medoo::class); +$db->method('get')->willReturn(['id' => 1]); -$repo = new ProductRepository($mockDb); -$value = $repo->getQuantity(123); +$repo = new SettingsRepository($db); +$value = $repo->visitCounter(); -$this->assertEquals(42, $value); +$this->assertSame('1', $value); ``` - -## Bootstrap — stuby - -`tests/bootstrap.php` rejestruje autoloader i definiuje stuby: -- `Redis`, `RedisConnection` — klasy Redis (aby nie wymagac rozszerzenia) -- `Shared\Cache\CacheHandler` — inline stub z `get()`/`set()`/`exists()`/`delete()`/`deletePattern()` -- `Shared\Helpers\Helpers` — z `tests/stubs/Helpers.php` -- `shop\Product` — z `tests/stubs/ShopProduct.php` diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..397b728 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,20 @@ + + + + + + tests/Unit + + + + + + autoload/Domain + autoload/Shared + + + + diff --git a/tests/Unit/Domain/Languages/LanguagesRepositoryTest.php b/tests/Unit/Domain/Languages/LanguagesRepositoryTest.php new file mode 100644 index 0000000..4a3dd68 --- /dev/null +++ b/tests/Unit/Domain/Languages/LanguagesRepositoryTest.php @@ -0,0 +1,123 @@ +createMock(\medoo::class); + } + + protected function setUp(): void + { + \Shared\Cache\CacheHandler::reset(); + } + + // --- languagesList --- + + public function testLanguagesListReturnsArray(): void + { + $db = $this->mockDb(); + $db->method('select')->willReturn([['id' => 'pl', 'name' => 'Polski']]); + + $repo = new LanguagesRepository($db); + $this->assertSame([['id' => 'pl', 'name' => 'Polski']], $repo->languagesList()); + } + + public function testLanguagesListReturnsEmptyWhenNull(): void + { + $db = $this->mockDb(); + $db->method('select')->willReturn(null); + + $repo = new LanguagesRepository($db); + $this->assertSame([], $repo->languagesList()); + } + + // --- languageDetails --- + + public function testLanguageDetailsReturnsRowWhenFound(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn(['id' => 'pl', 'name' => 'Polski']); + + $repo = new LanguagesRepository($db); + $this->assertSame('pl', $repo->languageDetails('pl')['id']); + } + + public function testLanguageDetailsReturnsNullWhenNotFound(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn(null); + + $repo = new LanguagesRepository($db); + $this->assertNull($repo->languageDetails('xx')); + } + + // --- activeLanguages --- + + public function testActiveLanguagesQueriesDbAndCaches(): void + { + $expected = [['id' => 'pl', 'name' => 'Polski', 'domain' => null]]; + $db = $this->mockDb(); + $db->expects($this->once())->method('select')->willReturn($expected); + + $repo = new LanguagesRepository($db); + $this->assertSame($expected, $repo->activeLanguages()); + // Drugi odczyt — z cache (mock select nie zostanie wywołany drugi raz) + $this->assertSame($expected, $repo->activeLanguages()); + } + + public function testActiveLanguagesReturnsEmptyWhenNull(): void + { + $db = $this->mockDb(); + $db->method('select')->willReturn(null); + + $repo = new LanguagesRepository($db); + $this->assertSame([], $repo->activeLanguages()); + } + + // --- maxOrder --- + + public function testMaxOrderReturnsInteger(): void + { + $db = $this->mockDb(); + $db->method('max')->willReturn('5'); + + $repo = new LanguagesRepository($db); + $this->assertSame(5, $repo->maxOrder()); + } + + // --- translationDelete --- + + public function testTranslationDeleteReturnsTrueOnSuccess(): void + { + $db = $this->mockDb(); + $db->method('delete')->willReturn(1); + + $repo = new LanguagesRepository($db); + $this->assertTrue($repo->translationDelete(1)); + } + + public function testTranslationDeleteReturnsFalseOnFailure(): void + { + $db = $this->mockDb(); + $db->method('delete')->willReturn(0); + + $repo = new LanguagesRepository($db); + $this->assertFalse($repo->translationDelete(1)); + } + + // --- translationDetails --- + + public function testTranslationDetailsReturnsRowOrNull(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn(['id' => 1, 'text' => 'hello']); + + $repo = new LanguagesRepository($db); + $this->assertSame(['id' => 1, 'text' => 'hello'], $repo->translationDetails(1)); + } +} diff --git a/tests/Unit/Domain/Settings/SettingsRepositoryTest.php b/tests/Unit/Domain/Settings/SettingsRepositoryTest.php new file mode 100644 index 0000000..349f975 --- /dev/null +++ b/tests/Unit/Domain/Settings/SettingsRepositoryTest.php @@ -0,0 +1,101 @@ +createMock(\medoo::class); + } + + protected function setUp(): void + { + \Shared\Cache\CacheHandler::reset(); + } + + // --- allSettings --- + + public function testAllSettingsReturnsMappedArray(): void + { + $db = $this->mockDb(); + $db->method('select')->willReturn([ + ['param' => 'site_name', 'value' => 'Test CMS'], + ['param' => 'email', 'value' => 'admin@test.pl'], + ]); + + $repo = new SettingsRepository($db); + $result = $repo->allSettings(); + + $this->assertSame('Test CMS', $result['site_name']); + $this->assertSame('admin@test.pl', $result['email']); + } + + public function testAllSettingsReturnsEmptyArrayWhenDbReturnsNull(): void + { + $db = $this->mockDb(); + $db->method('select')->willReturn(null); + + $repo = new SettingsRepository($db); + $this->assertSame([], $repo->allSettings()); + } + + public function testAllSettingsUsesCache(): void + { + $db = $this->mockDb(); + $db->expects($this->never())->method('select'); + + \CacheHandlerStub::store('settings_details', ['cached' => '1']); + + $repo = new SettingsRepository($db); + $result = $repo->allSettings(); + + $this->assertSame('1', $result['cached']); + } + + // --- update --- + + public function testUpdateCallsDbUpdateWhenParamExists(): void + { + $db = $this->mockDb(); + $db->method('count')->willReturn(1); + $db->expects($this->once())->method('update')->willReturn(true); + $db->expects($this->never())->method('insert'); + + $repo = new SettingsRepository($db); + $this->assertTrue($repo->update('site_name', 'Nowa Nazwa')); + } + + public function testUpdateCallsDbInsertWhenParamMissing(): void + { + $db = $this->mockDb(); + $db->method('count')->willReturn(0); + $db->expects($this->once())->method('insert')->willReturn(true); + $db->expects($this->never())->method('update'); + + $repo = new SettingsRepository($db); + $repo->update('new_param', 'value'); + } + + // --- visitCounter --- + + public function testVisitCounterReturnsValue(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn('1234'); + + $repo = new SettingsRepository($db); + $this->assertSame('1234', $repo->visitCounter()); + } + + public function testVisitCounterReturnsNullWhenEmpty(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn(null); + + $repo = new SettingsRepository($db); + $this->assertNull($repo->visitCounter()); + } +} diff --git a/tests/Unit/Domain/User/UserRepositoryTest.php b/tests/Unit/Domain/User/UserRepositoryTest.php new file mode 100644 index 0000000..b2b47c6 --- /dev/null +++ b/tests/Unit/Domain/User/UserRepositoryTest.php @@ -0,0 +1,252 @@ +createMock(\medoo::class); + } + + protected function setUp(): void + { + \Shared\Cache\CacheHandler::reset(); + } + + // --- find --- + + public function testFindReturnsUserArray(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn(['id' => 1, 'login' => 'admin']); + + $repo = new UserRepository($db); + $this->assertSame('admin', $repo->find(1)['login']); + } + + public function testFindReturnsNullWhenNotFound(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn(null); + + $repo = new UserRepository($db); + $this->assertNull($repo->find(99)); + } + + // --- findByLogin --- + + public function testFindByLoginReturnsUser(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn(['id' => 1, 'login' => 'admin']); + + $repo = new UserRepository($db); + $this->assertNotNull($repo->findByLogin('admin')); + } + + // --- all --- + + public function testAllReturnsArray(): void + { + $db = $this->mockDb(); + $db->method('select')->willReturn([['id' => 1], ['id' => 2]]); + + $repo = new UserRepository($db); + $this->assertCount(2, $repo->all()); + } + + public function testAllReturnsEmptyArrayWhenNull(): void + { + $db = $this->mockDb(); + $db->method('select')->willReturn(null); + + $repo = new UserRepository($db); + $this->assertSame([], $repo->all()); + } + + // --- hasPrivilege --- + + public function testHasPrivilegeReturnsTrueForAdminUser(): void + { + $db = $this->mockDb(); + $repo = new UserRepository($db); + // userId === 1 zawsze ma uprawnienia, bez zapytania do DB + $db->expects($this->never())->method('count'); + $this->assertTrue($repo->hasPrivilege('articles', 1)); + } + + public function testHasPrivilegeReturnsTrueWhenPrivilegeExists(): void + { + $db = $this->mockDb(); + $db->method('count')->willReturn(1); + + $repo = new UserRepository($db); + $this->assertTrue($repo->hasPrivilege('articles', 2)); + } + + public function testHasPrivilegeReturnsFalseWhenPrivilegeMissing(): void + { + $db = $this->mockDb(); + $db->method('count')->willReturn(0); + + $repo = new UserRepository($db); + $this->assertFalse($repo->hasPrivilege('articles', 2)); + } + + // --- logon --- + + public function testLogonReturnsZeroWhenUserNotFound(): void + { + $db = $this->mockDb(); + // Pierwsze get() (sprawdź czy login istnieje) → null + $db->method('get')->willReturn(null); + + $repo = new UserRepository($db); + $this->assertSame(0, $repo->logon('unknown', 'pass')); + } + + public function testLogonReturnsMinusOneWhenAccountBlocked(): void + { + $db = $this->mockDb(); + // Pierwsze get() → użytkownik istnieje, drugie → konto zablokowane (null) + $db->method('get')->willReturnOnConsecutiveCalls( + ['id' => 2, 'login' => 'user'], + null + ); + + $repo = new UserRepository($db); + $this->assertSame(-1, $repo->logon('user', 'pass')); + } + + public function testLogonReturnsOneOnSuccess(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturnOnConsecutiveCalls( + ['id' => 2, 'login' => 'user'], // login istnieje + ['id' => 2, 'status' => 1, 'error_logged_count' => 0], // nie zablokowany + ['id' => 2] // hasło poprawne + ); + $db->method('update')->willReturn(true); + + $repo = new UserRepository($db); + $this->assertSame(1, $repo->logon('user', 'pass')); + } + + // --- isLoginTaken --- + + public function testIsLoginTakenReturnsTrueWhenExists(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn('user'); + + $repo = new UserRepository($db); + $this->assertTrue($repo->isLoginTaken('user')); + } + + public function testIsLoginTakenReturnsFalseWhenFree(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn(null); + + $repo = new UserRepository($db); + $this->assertFalse($repo->isLoginTaken('newuser')); + } + + // --- verifyTwofaCode --- + + public function testVerifyTwofaCodeReturnsFalseWhenUserNotFound(): void + { + $db = $this->mockDb(); + $db->method('get')->willReturn(null); + + $repo = new UserRepository($db); + $this->assertFalse($repo->verifyTwofaCode(1, '123456')); + } + + public function testVerifyTwofaCodeReturnsFalseWhenTooManyFailedAttempts(): void + { + $db = $this->mockDb(); + $user = [ + 'id' => 2, + 'twofa_failed_attempts' => 5, + 'twofa_expires_at' => date('Y-m-d H:i:s', time() + 600), + 'twofa_code_hash' => password_hash('123456', PASSWORD_DEFAULT), + ]; + $db->method('get')->willReturn($user); + + $repo = new UserRepository($db); + $this->assertFalse($repo->verifyTwofaCode(2, '123456')); + } + + public function testVerifyTwofaCodeReturnsFalseWhenExpired(): void + { + $db = $this->mockDb(); + $user = [ + 'id' => 2, + 'twofa_failed_attempts' => 0, + 'twofa_expires_at' => date('Y-m-d H:i:s', time() - 1), + 'twofa_code_hash' => password_hash('123456', PASSWORD_DEFAULT), + ]; + $db->method('get')->willReturn($user); + $db->method('update')->willReturn(true); + + $repo = new UserRepository($db); + $this->assertFalse($repo->verifyTwofaCode(2, '123456')); + } + + public function testVerifyTwofaCodeReturnsTrueOnValidCode(): void + { + $code = '123456'; + $db = $this->mockDb(); + $user = [ + 'id' => 2, + 'twofa_failed_attempts' => 0, + 'twofa_expires_at' => date('Y-m-d H:i:s', time() + 600), + 'twofa_code_hash' => password_hash($code, PASSWORD_DEFAULT), + ]; + // find() wywołuje get() dwa razy (raz przez verifyTwofaCode, raz przez update) + $db->method('get')->willReturn($user); + $db->method('update')->willReturn(true); + + $repo = new UserRepository($db); + $this->assertTrue($repo->verifyTwofaCode(2, $code)); + } + + // --- delete --- + + public function testDeleteReturnsTrueOnSuccess(): void + { + $db = $this->mockDb(); + $db->method('delete')->willReturn(1); + + $repo = new UserRepository($db); + $this->assertTrue($repo->delete(2)); + } + + // --- save — walidacja --- + + public function testSaveReturnsErrorWhenPasswordTooShort(): void + { + $db = $this->mockDb(); + $db->method('delete')->willReturn(1); + + $repo = new UserRepository($db); + $result = $repo->save(0, 'newuser', 'on', '', '123', '123', 0, []); + + $this->assertSame('error', $result['status']); + } + + public function testSaveReturnsErrorWhenPasswordsMismatch(): void + { + $db = $this->mockDb(); + $db->method('delete')->willReturn(1); + + $repo = new UserRepository($db); + $result = $repo->save(0, 'newuser', 'on', '', 'password1', 'password2', 0, []); + + $this->assertSame('error', $result['status']); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..cc85cf5 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,51 @@ +zrD3YWMEv zS^aePl9vJjMF9W+kO2M`O|7n84pdTL06-rM0D%AcRnO4M%vwv&T+fx(*3`BjVbf-X z9&uA*lWFmW$au2JG&TNGgd=P0HPpI(hZt%5>LW^UqR z>yI9fHC~=AmQC`K1SS1~;^Vjd0Z6#gq*v>nMVn~5+ru=65zDU4wxj7imYiAMPN!wl z_m<2!)S{|W=M9>6sUc|*IOIkJXeGKx|sfxwiN6jh|jDgl|_jA@jjtf%|YiK2* zQy}&(Vq0hYiw>nz#VLuFVlWD>3r5JS0|r-God|{#cxsh=-!yT%=OJR7XF5D z-hUgzJh*qMp&D+EoH$W9Ow|gbKdawE0lQxIfg+<_O!NS~2)$-uNo)4l4F=!-1_S!; z`oo6KwvFkERMNjRR0YEu*%qhcP-cKzBwVwyo~LywrS;0O9vA!KJkEnop6|()C%fa- z5&m=JNzM_@@;up3QRKw~pO|zoaHifI2f4Ou<<|g?4f#z98Ovaq7=Mh_oTA9&?qP#H(ksfjrb}5FGO*DA?idB)<6Rk0BC~-01*C#C^KtA zqrXGyhmXw;E5^rb>5IUF z*F(?4N)O^n^>-|P;wP`{p_um%+fF_%(`(F|smP+pYC0{meS$Fd*ryPDAyaCL!EitNluW5bn+2!|XWk-=axfQM@rWg7@BT>;sF|am$~H zGkS8pWAE@t1dsVX$t5={&YypSZ3dtvD2fxR6XYK}uL<$Hct!E{c+t0av_hWmJ~=x& z*;#z&%ZdS44fuY3YvjrVO*diFCpF4Rb*ttOMI zZLo=uQ4S#bemy3Gy`=7kB5Efehk=lS4h{>m5o|-&c(r|>yf{*>qr~8OVc5=C z^d_q~k_ye345^^~8T6=ivj^rP$U+2_KFv5Ud>i>rYA=cs(Auv@A=2X?qfFWAsIjWc zq;6%-m?lAfXDVtyvqy#w`79_2eK0W6Np6T; z{Si=hN^(=(h2B)6L&l^hDOGb*YOYP6w#40v#=!FaI|Mk*kQ+hk)(>~y`o=kV&96U$ zCQb$pK}PFyTe4JHOX_FbB5-Z(&#=M}!oIlC2@!99{He6S;n z18$MvzSJot%RtfM9B-Lk)Swb`PNqj+HYZ>y>+(-wW@zNinnW?Y9J`L9xjTaQA=`2hWM8v+0%IyJSvcAkG-h+i{3Cr2Ag8$ClU zVH+#GuMqctU;VdD>z}KQ?QN_b{~wG0>#vne_3Vud|Fs$t@X)5IrSg<{lK1rhXI}&3 zKOe-vQqRGGR!Gml)ab8R8#K-U-OG;%Hs!5%N$&*h0MyevxFHzJynCVhZ86ozg@lxF zDHOdLsS25HM*_BR(Kvk4$ET3#r+dW>^(-`_8rWSHu$p~6&)oZby)Ia|QJ9}4gP78c zEBf$)LW<|IyLi3;-XHj!urTQ%0dc%-_$1lUqu+A_uz6_fD8tDJiFmXpK%fIY$ z`X6>J{L?HpE+J6!e^Q&G`ho}xzye&oNnI)Hxw_OA;_2*9VE+?y^eln__cr}B?*1wy~(yeG`+S7gI= zd=5eAjPc=gDA=W*WSqbcND@074j`5hQMxyo_OGv2Pd<{cK43w++W^Db$BLSM87Zz* zNMy~y1dWOnB%a1B)itpQsadDAI@_g1;g(Z{oaW54tew&eeIJK2_5l)t;yu;1-ShDr z=0L_0da|D4jgQ+F3#qPhNJVo~?CMnY1n+Ikk<0J8mZlRE+RT`xhR2UZ?IOQn6!?Psw{a zOZI+Rs6MV5O;{K*CFR^vfe9Mp)(FnvY?KJ+jSgvvH@r=-zdhZdmMdx4CsB}3wGObI zGok;)A7yH2=O39~Jb4t@sZ4wKCSS2&P2soDo3qS#1>Ct`1!jahkn`vD1ZH&EVjMQX z$_&REDg%!(Bq%=%u@)H<8J;>)v)|ety;;Re9yG`6&iZY_VP>}qa}S5u6|L8jP1W|X zYYE+fuF%}eGc-dmqVBMVCUFCTwaoXdu{C#AR|@W~Gk-r`Bm5RJ8)*jd`ylN^Kf@T!F{zO>o>5t>UIoI%}TSCGu1z}Bk6!etUz|V_)6qgsA!S!_(i{t4*zW?C7 z=y0u?+34=DgPFq=atEwE@KsLW%kc>4Q3#jDcropy-vzm+y`X7}e3rPGZ&2p13tPl< z_WaBWejtIyi^b*9ymBp9q2dYB_$5L+Z*k?V&!ZG{ zb2SR%pO2-ZCs<*O?9U2@iYNjbgpovAb>GoaJsa;0{_@AP@Bx}j&nWTbJxb6$a8P%{ z&7r<;KMoz*-A2`)is<7J`QKog{i1&{{t##S7l?+w2HO7wld|pK*l+be*e^s58E?FX zE4|aoWXM(?2FbbuX4!w*AL059S*qq~{q=JaT`~NJTe2A#W;r%W4mdT(O2fS^3$9wQ zzPi3pqgq%x^@`ES2Vbo<3&9KAwgzU(2YuG<=ZH)%ucl7wA7fUk%1zGMPEyCY31Ci5(J>#A z*VorJS`RI)SF!U=`0!UB8#AKLAtpK?9)3J_=BN+nf3nzaA2mHphaq`sKzPut460Ku7+Z>m72ty`3*M9* zIg#Y&7z(#q)VM%~`AW9MNYl5XRrKos9MupFBf+-eeq{!LRP0F@CoI6mH1L&7x)V6D zZ|s*<8@ZGO`PvPoq#@hv3rA{~Je*W#KL7*=`(_t)BTXCXIgIlg@t|nKfAd^XAk{@& z%+(?|ECg`w8x$~xh)ZiDl~J99u~0yf1;eT#X!iGM|6(6hSGSX3nK6YVq!9@8rDiZ7 z;JwB<$rKuv4Y$FEgm#KNI=zq^&d(|zK>GSHp0Bn+K^6fJv;t!IsNB=(chlhvhLtxXIZdt}%2SXt zhB*>s6hyp-Bg9pZ@X!aj#b&f+NczW86qua{4Vt6mr2=Gqvn`rTLbXCZbWLrYY4ifO+Xnfu7NbuLZAb3- zGN_2lM%!Uf{KT)a+;{>nAE*P_H+A^=wk}cy%!)4`kY#Av`@5?IIFsMPL`Wsruw!p= zo{8Mx4}lU7#C?stJ2~uMoYcA5NsQ$qK@h`#{LdFNr+^NEO(83I-e)s;o)C7spT0*BWf71U~^0v{oKT0k+2(={h`y8?6JUI!HACfy!P+z#@^%i!$dvSNi$^>A+~$pzqfO9m{^Y24BD z)oin-AKoYJn=NV+r^rM8z~X;D>)*w6 z>kXFby?VNtU7U=(HBQTPkQ-Soj}+Eq-PFO)C&-_c$^%kE!O3%gQr%7 zu#)-S(f2U<^Gk5L>7_pi73^kU} z6G4;pmh_eUb1gC3!hlanZ8u&a*Suw$0}3rd$HAOl{7X-0C1$CuX@ueSE(aF_5U-ev zHJV#D_U;jHM|eLMSX15tTqF*LTwE390mObhhi{kho?3-e!JL>(lW@)CDw zyByxC^+ocKL0IiT1r&*oH%i|CA&#FulC&4Gs=TCVA%TBVjd;8VahUag{rnA46jIYK z@#G)Ok4Ta@&z}wFChz=C3ChY9J&zb%lYls()`32eaQcJC zj2tfi6{?)YW}i0i##C#c9oME7M|UhE$oS*97OEK|#!;QD@I}#%Wf+Jkz6&u>bn}?o z%gt8bbpOOy{LldUR>QHVB_fi2z>3}3_8@##$)J^jbFbKHs$4tiBpA$qWIvqfw-s$_ zyod^uvRaA*4yhQiLK#J}Sas)}6tORM{TKsoPyZs%y4b0b`7y< zcj*3lz-(Z}LH81n@>ELOa#}>xkzTB_BzWQ;e+H5njLKJIz*D)U+)gB-=Oj79~F z(lFP=Rjy@#S2;s0L#tZG?1$=!GKW<<73W(8zM2gC_Ii)q+U0|HamBTUCKDCeA<%JY zEBCepU2G$9zHqv&hW?$@5Xl$G-rsqNf-T&wBFahhu^GU`NtV*PG>Kv_)K+{z6K%bP7jho*EE&`<^+6@?dR|e^LFy|qW+rlGz zwgq)%$u3|iL1bN+(}1LYh_H836PvMCD5(6j$p{4mh% zvq$?HFbCHX_E*6Z*Eyn-!l!%JH%oLnJ@C!)FSBxJa{I7UXeLd#Iim1Xc|uM zWBI7v2-gEg|3yPo*2UxxiPK!4EabC7F)#w}50HQ3!}$vz&8hBo+8_YHPZ$7z;{S&a zB_l^iGi#H-!6WI1l=Uh-;yU&fZZkbTnQc0$RFQ1!aN(-+w1^U9J#+HBe7>Y2Yep_V z!t}1=YQ!Beu4JyTqGB4T0HNJRI>NJYaZ}TNXT~Hu4Sy0dkl-xg;Uia?DS_PL?Xst{ zoi}fGoWl)mkfNXs-gd$*r(R7cY!C(LgU>IhQw8jMoZQ+}I|*g>ZqNT(W>B+khc!BzTx zp` zkX2u!VUjuNH%jL;^Eg~gE&CytA63{EmJ!-zAa#anH;7)@!q4|_mq9aBuZqyiUG#DL z?QA(I(Sn7|u~2agp)eYhr!XvVngn?I$+r-Jb6^zZ6tcqR-!=^1@<)DzS4`m|C*@)! z!!T}u$Ou%nH)J9nZ0;YOBstBPpzw8<$SD9Njo>+KNzx~XIF z`YO9xC;N=e!a8bQfL$iApki{J;JT!!AJa^Ujs$yJ>p+EZdI|SGFRwNKx$cSb%|)`M zs({BMr#{)vdEgAE>KTsM3UlzS+DD7p*1jjqS*+5mCSd;qKP9@gd!+FK)Oz7-3cG$M zdem+eXL_{8DmP^7P7b-HHf$_J=!dx$FI7uwldg!9>uP%^KGS zc_1%l$?0jii8WdeIC)0>W?^iVO8QVodj?_x?QCj;;m&zWJ-dB&hf_MNQP^U<1}|CO zGRQdI_spHoqM<5x7RfXfknBi3>*({;R(nk|ilFAaCjGeZz?+K)N3EwZa{=CvEM z8|?j_m0bP52oGgwld532F>{KX+vsJ-I0vZki>x> z$DT{p4Vs4?I&EM)uGzYU48~~4Pr2&JIl(@BK0P~^2A%p!uFXFjM3*MPo$Q_iQyJH; z`3o1DgHO|$h8#Dn2mCQXE@I7^I%i2Ux1@5x4N~_R@?kJFD~U}Bq1pn3&fs-y0g*vi z3_zJ~j94$YQO-Fe;##=5kT8&rFjEMJX(%+J{)uNW8i@3g^1np{QC+x!6xkIe=khIq zu~Ck7x3t?^sO<=THEQ~$hcraO)L}n{n~mQX#3|7xs17*|Yy%H9qqi#TaXuwo!)fXt z2_~1={cg z)?V>YYy)}@>eu&c5TUhFqA~(LYF;b4Eu{Zf@%i$f(S(XWN<^wG!D&o#Q~6lepu6hI zrU+%gI7g6Kg3Z4$t*vn{vc_Y@ACPMo38rY)t!m#;fwOA?w6p~L6-n?fh-2eRmun?~ zBRJ5UDY-TD1yEoXtnat02PQzC*zOT@3Z(l{P8tXE_ok5p^L zwDY=GopM+(-)}U3PHEGSG51e++HS^wj3n6gj%kl5D(#HOe#bVbpC&Jw%_$2~b#S<8 zUhBmYW+qPc^?J=_p!JHXY$!HoLi@;{2j|B@hD|C%Wp+1fam{UuZWk|pD6r?RW!C?7HXTsV}yGD)E4V7aE%5VjVURdiE{4+1Z=@5`NwS*h1XgLqub%7^K@GwHX_F&{Crn9tJBQrYl6y1h8rjuz^^LsX8}1_~yMrLHSnsPmKPm zzE!IV+*o4dqstp2t5jN*kYTOuE1tgqh61ilR7NmGfv~pr%M6I+OvD_ydIwv@!@{Na z?yFLwA57@6)$jfQdfmN-$m8Q-){VYoy=%4Q{8gb!GT|%Q@7#;_Tg>wDTjOk)0gU|c zhB*bDSx{)>z(70&X>Lo+?i`euwLGc)g*;GCb!y8;lv%Z9m^#3K69*2#MAz<{Ei-0U zkh1vG?YJ$YaFPT{NN7FJQ5SYTRZP*9HFtmuSsv0G17>!6$f6IKLooiv21oPnIJbG1 z477}n?~!fmdD~}KN#toUDOn~P51C*@^EvpV>lfv;-xffa*Oz7g)e0!K1| zJk=Qc_Q?o<25jF|gWzqA1W#YD?y4VPXZoOcH7fT0?A&r7?1XOnSx6Ot9k7yTN(Phz z9(1M>l7Q%dR?#OxW|FA?S>_HJf4ssU%*Vz&n%v5s%q~@K32rS!Jz)qB2UqM6PQ@4= zI)~AyeqhoiQ69jNjf}S+=UG)Jx?y(YTT7cMeFxnZv8*y;g@(xH2>DtyVokmF1dqUu z3LlpL4rc&Q)EWEe#N^#ttz_s2zOKSVI%c$HA!j<07Y^v{Kzc`Es5>xZnk7A1+4fJ* zNpeug?E`y(RslE53Vb?S|5FA{nRx?N+ww`!iQ6j` z+V_;)%=+Fi@R+zJuU)b_GN^_qOdM8BhUX9I(;=HyaxFmnYijK!T*NZSE0P&cboOvk zsq|)QVYz-)#F2O|pOKhPEq+vgv-hABrHg|sI&+=ae0AY?K}*%!1fsGfQjSm`j|G+* zWhO8eRZ~)eU4dqtyFmSiQi%_CNtK7zGG>=&_tU^WrFNJUXoNU zfzyX!2JGBJt;Gl$=_qE#=bpkK6 zo~#Fp9Cc+8R(l6%c0WwcM*9^c@FB(q_vtyaSM27tO+>9vMB9G=;Q4fYetVO5RNuZ@ zps`vB$*w3&oA@cX$6U^!NgUMGSmX^^#I1K$0`+@L8K{Zfypg5tVP3MuQ*K~2t3fkE zuxFL~`W#h1G`4197E16((wpM!AZB7_Ak33WvOKB9730DHDgOTI0Sk1;1Cf*C`dl8B z!XMgkID&g@3Z(^`Er*85Y*5P#qmfG&M$ikOW|c!J{SH#R@7Qseq@!D&)zcAn4pp>? z+?9UwfX3vg#VQC@b&fi6DMVEy1{r6iJIe2MSKVTcdbP>Tj!5Z6TgHp;k;?N{pDl+R z1+0aGJ*Jq2SsQwfCC=ap*B&|-5xdS(g-R6U&I&i^%8|^j+lomkgG{UqpFwpoD$Dr2prikWzA|^T;~x|^i2E+-uZc)_fa)*u})G{I878- z&GtGz#1oa>Rbj$eF z*0o^jvu+Q~{pL?uc^+$(l8Ds|QIe1(l)h375X!##K=?hc@2}6Q@Yoca(?hki8e$>s zVE#*Y(?n9IH?SPUPhUDND>qGG?bIv4<#MQk(b`0XD5Ve;&aMo^6n4UVeApp$dpwv% zsJWmnfdNZ$0^YN5=M|=um}K@iBLR6ABbU-MB~~bP1yW;*5+J2jI1)RqbVz_6X?3Y7 z8AcIAJ{pWFSr0Z%Ds=WkRY+k5Ua02!n^{~x-{A@(Iz+>x>2p)oLSAz;r3_#DnV8?M zDFEcBIy;o1Sb!q*hq{=*5y3R&x5ng_->X3%y3;c=sk)=T%06==l-EOBe=X#9E!=n ztaa6-Mbji1*iYM9^|~BboG?v&8mK`pppoL?I(GcF!56=)(%UjGMJUu zpy^F~ynXpzu?5B}vU}IJ{%)gta@gL8cxZABX12qu`0M_0mMM#Vx<&YR{h<1bM>>&5j9OoLNg6%?K=!}z=&y7{ z>#y5?^JiRr)f!vun-}_TA95(+;CczkwIFo$KZ&_&DCJQ&nBUHIRS82Eq?K^&*k#7< z#7S{MA^<(!-Mln!m~P{m<=;nsSvoyxxmPA;khcZWWn`U{p0s9^n)7a^%N?9r(55Q* z$Vx*)Sc+Oc?nKFTWZpO2v&<57BJ5lzPNdp69MCK*Zd6qPjq$t&+d{LPD#q_f+8!^F zMuIHvu{!86Gp3$8(9yeT#|j9^kBkFYcx&DlXe%EC(E#63vMXpz-*vKMH^Yo*(cd0T zbR5rW&NZB>wc#58a4v=dc`zRRx5I*5U1DxqEn%1%ZK^2ahGU0BHKS~4y4*cJ6Ft>5 zAnt7pO$5xkRWR0D?5*?Rb5HJeW0jUo@JC>)9sX%Vcs^yk@nMP**KI|j{dOnh?hS_7 zoO5F^s)`dKDvZL)EASn;-VNXv_3lpZ4~dB^54 zIe1+c_wG{)Bl(#iw~@hN7&A6=+$Rw5L$Gb^>4-5J=yahRIVYRH`WtXgoZ1Zjj7#9M z+Kp$JlU)Sgv?}(YWskF6$L1_O#@z`A8;d7^S-0R$2fl9NiQjIk_GqOlV&?X@n{H)_ z8_%1029melFsF~JU5B2BC@aqefnXs`6XhY3QnZm&%6y0T-I&E4XW{|8sbIs5dP(EFP+qK_j9!EJGolF$Y*{3OJYk#PrMeVC8cta`R5OX zxjf?Jpx?%C>_fl~quiKxOCQ$v1hGuG5uD<|R(+H3~una@FgBYr>x->gROCPW%VoV6Pu%s6vXt#-E80ItCfbDKZThO2t(_TpPl&H#&9q7zW<~hqhi;4hBxlZ>SHh4ns|sga8V&3n3#*L zdCEdqyF5M=NN7pkOY`~yYhe~I-(`pTV2k&Rs?Q14y)RMLV0C*AdmT{@AIPG2d;0pf z52t&ZkEdfb-%-p<)k91)NpEr)uKIBVoo>Lyfv198nLpigsI7t891wK7&^iMq$L>R9 z@?{hfKU5O7f+6l+BY1sp12}gGpUokQEJ#fltyl|Qqqik%t;o>wGFmq2n~Jx-AIq6N zsW_HOL!7Iq3B{YKxU7t8hmZ5a`p9w5(QlvT&jVNdrXqg6@HO`Sc>nSVN(_kujm*T} zbWFh)YwNu6AZ`6#f9UB*>RXhU;P>U9jSc=8H8#oxqY*O3yBK{i5y0D8&D{N$##-?S zeXl+HEbyYH{06qKqjHN&AL_Lg$TCz&_=dv4orVj7CxCR3d^Dl&&2vz`YD}vQ(?Fou z&ND%eGf>}GuHdYj3o$^O_AX3Z{E*k;h132Qf?dFqTXsm zI$kkGbFWVMKH;QZ{}}1m!oAJ6t9;hVr&*%G{GOVmNuFMMS@0{#XMD(r~1C2=_!bRDp=IvwXNNp&c(69I@8%5 zjE&j&q%YgneazL#;u3m(;Tu*LE9Z7OzEyp11QsF`Gj1ztLK8EQIiQ+8g+{^7d79mM zo}GnF2NAHC_lwK~1c==u_fKknDt&fDO&eS~=0uJjv%?rI(uv{QB)wtMUaK*&5GA<> zo>`S~uFgFqsjy0tFZRjuu82BPs0Jt$nh*PviIOEoqZ+Do_?D^=j9>Sr&-b2QYl;)& z#zbZ(H-0Mq+G;BsG(FX|nZwmK{V8LmdS|^T31aA=8tQ)23T~|s#G+A`f>!-R$4ey( zCRvVHDWW~{h}ia&TFBxEin3Vs_yTbJDa;>4K7Q%F9(nb@F4Ql=@7m?QanB!2=GqOuH_-~`LSR<%?1e0{xO9~k* z7=rb$k9cA7ieKKVyix|21p2uXb^p8r4c7gpU43xzcunVITDm!@4RNCCE%DaB?gJkL z#%iLd*(vgl!+>tSh`uDjD@CibV=5t6VJ?YAv^mzLNL7{Ed;wiGq%2h1u4S9z-u2dB z#A=02za{}C7Z@)3lj+6=Rj&K=;2*BzesR>1I$j7xUCN7gsxfmz%|&vuaB!&hKPA@k zNIw&Zhl_J0G`w6?+#WG?jDut;MGEKb|IAi%qcf>1WMQS|M?rnhXbB62D6mB_$ zpv!|qfizfNMN+KrX{0R6%$M=FzpzBh-Qn@pUv9a=LQXHt05^KH8s!|;4&6@nm$aG! zP}N6N?Oi9G>gTBULbPKy-zV6=1xm`7K#?^gq80dRfU$lxIsU5w_V@FK|BnV(QhbkO zA3uu7qu*PfCZrYgidWf!2&UNFu`Q)v9z3RAGqfAaqH zeK~b#8bDVYPze*PNG=Y?f?*tLgP2J|%7RRLtPgqm_`_7B7c0pO{*s_~b)wU(q`xby zORW(ns8cofjuiRI?6PWL2-)4mr3utn2x5M`Iz( z@%$rsjC@FA#c4s;YyFE>%GYJ`MQp^k%jMcmZmVFsv@XGt&OzFv3(#!zSUt-pMw6IL zEl1thGlTEU`a)iqyG2Ay7aw&^J^Ku7{0B4^qs5WJpWV&p5OqJRjOu)1HxKdT?j~MJC=qu7s3^e}Y$^c?qPGNunNJ4a&~KB)wc0@R50 zQ-7jYNM|?CF~b_jFdS##TE!SNzQo`>>sSHrG&{p6u*_3 z9DywBD-`-f7Uv>n11at-yQjP*Rd|KE`u+dSb%^t`)L=vwvK@Igsbm^`4R-IE2KYUYRq>A z+Qh7e(SmD_Yf0K7YN|djc*$wJToECwx#~6zjI(t9wBPZ_LMM_)AoAK1(!aYCNkqq8 zKI`;`Ch9I;1{T2vT~>G@{4OtyB*jNncu7# z`QimRt``A_RS33IFJV`=4bmC`5Z78Vr{#oOO_4j8B0lIgN-`J-=ZqjH(zniZ2}9Px z@+)Lw^)?igwGcSE7(O))#-Pz2H3hRPcqiZTTNa6jtOR*q`YYgymrVIp_U5*q_ePDs zoypz(bm_vII~^_o{Kf>e6)`H0fq*A=3_PGb7>*nhSsS&T+4BMAyhg$&vqW{dS{^4M8 zBTL(_R*%*{KL7IWxtE#Cnt1%uqj%LONPh{Edf!i0CWaxNNwo;sn& zr6R@s{R-<32!fI9$2i`OworlH9s+KA2}n%T?59MJo) zD*+(Rtn0oFi8e~HsuPfk`9HrtJ@%wc4}@^J7~U;2 z+v`j16dm9d812f+ew~X1a9$7-i4tz1JmKF3d{A%;5WUKIK!hCp_Aw`o$_MN+cc%dI zOEX}9!2*LK{OA>tVs0KNSo_ATnfvnco`{E!Tgrk|EF(2%%kBe8!IZf;z2{WtRvrv9 z!S=90Lx_Ah7PS*v>ZU!+>A0iLUa&%mrFm;&M<4MTD6wUyi!caZ5o{<>K!d|^@SY#9 zjQh>Ikg|ooB+hx&&G%krEo}Dt)9g~}Ao1j%NO(me?D6&=36Tc9s@k|N(pZVC;;ft!JbWlBVQoQpbt=jRJF^|t0BPk55?ci@46as z+<=o}v^=24X z=>jjHoU|i{sn$d%gRr!iZoEAf{Co;7NAhW)gs38g0>))D>IGm#AgEm{oY2JGq&G%K zM?>O|sM2i+WO*H&^8uAJa(`Tu%#7|hqXy#fr&uz>RE+YpG6y6f&WF~?`}((mVlN+j zY+vcf8Ab36>y#aYO&!Q_IwHAhVu^!!yMBgR{5kRdxRZSBwlkyCC~+*%a{d-*5Qr?> z5eG+#9b^NUH+zl2XnydXH}BSNKAVp;*DU45HcHq!zzV(MAnQ;p4#JeQ6$u}{WCKQp ze*AuRDhhYOzTyprE0fm{9G+^#F6B4VIeq=4-$JU2hlvN1*nJ2)BpoSjZ+EtgK~~-b z=+eyVz!^O+hN^$2X(JM-?nvWK*yE9>hv|By2z_VnIl+g%bziggEt{7kVj!{gM;B~cdALp1>+(Z;;&Zcyg67@?gj5vSb;E21h} zkCe=lnD5ABa3^up4fGEm*VrbAN_z87$W+(BrY+mD0-UpcyD^cf>1~tv7QuvZxFAqW zLsiJU5xv@#k78>8@S@C@mhj(s1T{uxML#Ebc+*|q)%WhS)zW=I_U<$3@A`%%xrPEQ z9oBEpw-XmoNZ>Ge(%{lWLe@_k|sizCeFn!JmaAC zG`klhWwYB!7SsfwLP%T)2`@P7pQosUSK2SoGy$dZ2Q02g4Wijc~EFKiQU}hAIMJB)6c9l&=R5TV$fo#vUzlhQYTV$#xo0H&EQQI7tu^cl<+t*Yi68Q z*(7H3Al(fviPEp%c`;%GkmfZU*?kgIk603{D80qid&sMbrJa#6^0XZCS|tOfo2aLu zMWGF2^!o{VBNn3Elx2HpCvC7YiIhKAn?yRaKsoj6O}%6w;SVXp$SK79QD25` zI4?C=T*JwL?z*QqvNDI``L5p#RzXPYcQAy%7x*U;K2Bw1i2Zc)^0&q_%7?OW&TBA+ z)#Fw__%$x!U~0MUa4FK?-@fENeR+PdrkaDPI_~txFjF*GDu;&)_-7H~w-(S$4rYc> z-FR0fR~llJj6}DK#N_>HhV<;K?9@`V2U}OzKY_H7W0ol42Zw_>YLHkw(y%0cQUiH2 zNFpQzb|ppP-%~4|sFTZUgoDF{_8<`j@rYQGM%X~(@^f=wd}FvhOmS8(CxPtTc`=%# z&DfL8Tcgm3oG0`%bqvfnwS;Qnp?Z;46gZ%iW+n!2i}GE>e+0#eK489JEY_L(;l>!z zniH)^9>MbQa|HnC-GX!d8g`dWg>fJgY@{=y?N+;6S^R@SRItsG*-^lh9fiwqSt2jq zb|fWDrvyMIW9TQ;R6+T#so0`~FUqlG#sr!7?oz=*hmMl}L={f|ShJLj74|U8Y3Bmr zC>bY`zKzODP}W6)pc{4r{R0u>3{eF4v>Mcv!{-^(;o`C({m9;n*$U+= z&hTJ>zIy>$o>dI2SP?v5A%)9%c4sh?m)#Zyhb(iW%pq zE~2eMtSv+vCp^QVND?nz+pvM$@P|-=T)ctu>#Ji!Eo00NPB*EBBhY&ne?BBPEJr8= zxD~A)k$l~p2e~UULOBpdEKZcNgd<|?_#D1;_#UIcRt9Fo#VA%>W)&^gYU`TMh17@5 zoS$$?Sfq?Pj68GlrR+)0@!!TO{bURD9v-dre*ke7OC^*L7zl=c4?FK= z&cU)%^AVjGodUVpi^K3#+ma?XHU8)Zj&@*wAlk_qrV_ZlyQ>dsvtsw0iFLBozTrQ{ zQ)6}(t04*2wfiK^*Ib|J~sWv2g^QK5=0+8 zs*%ikX=0l<+(%o4q$6z94w}PcKZ$6DGbA8fY#Je5&@{_WueV__OM&cWT60ymGLO0k zO2w)b7#AgsG3grX_td>%$v6*X$;geynsSz(Adaxdy4aP*vE5>H@#yRxgAi$g%~yAn zA!|x7>a;tualzC&Ou#aqeo91!7s=L6yfA=0R=mp@nz|f6|AC<;YO8)megmPqu|~c? zv$_0(l42s`ch8zI@&@N&T+e4stoe5)OiqIo6-%TiLx145ZMm7@+^A-3(*y&ie0VOx zB$9;y!teT|(VmL9G9`k9zNq=T!XR9>%v@tEqLPXR_Q}$`lBDg@;a;1~4n+sXS|2@2 z@W;Yp-B+fm^*V4N%7|CGNKs?R}zLI zIS$Bp{+#4<5@bZWc+YQgF}23UX+P=;RQB05yL|R@OPqI^F`sjBEgpsT)nobAPQt=W z&TqfPJ{o4L{7p`Dp`_ z-#(7m=YGx!oHj^DA7HcfOzYgM9*Jg_z^~}dvM>UMiwWeAtRh7OLb*jgV@&( z9|XIR)tY;cTyl>uGHp8vCtQ}}mD^FY{hD6@RL^Wiu+*7vN47my&WH&+i0re3;6|S( zI%7JedtDI*GUX*%dA`b!rT2j8{38SjLIopv2YDVkKAc)n_yPJS0L=b(*+h%{8D5Yh zQSo}|Y$jh^5b0M-mvbnh(CnmHSez1;j8Ha5Iw1{FRDYj=n!ooGB*mRw`I{}WV6H7W zseK@&SrEG*c))6kg~DuBzNSOTy3mU8;2F-31=4PZ8Gqf}8-mR4QM?=(Fk@pS#~b3k zZ#i&pfQ%6GekItx@_-%^2mt2DHHJLOEJm!oF$alK|K}bfiB)P}Yl?W!4UtE(ery+TD_Z1vYhDb$T<7}kR57H539^?2hkYmg|CA(!nV#}(H ziUt$nH52Ta=yrtIU4O0-@CfNo3Bz^SAMvhP)I>94!**_f4Zcc2@C0(mceJ;dL%6g) z0jjH&IzXO^Lb{R}*zOhqdyGZ@wd?{p3Ovt>VPSIF0SrSU#FRL+imA{t0yB1VW#0fU zHC;KVj7k`}oT!9$>QfML1!ZQ*skpbI#OQSl^EaD@l`IdOm7Mg@hJ9J>x*0{31xsc@ z$O?V3>v2mdt)8VP#uIk+QN_%P083PCZ`0k3AO{46bg32cQc?6x1DMDCRjmGP5XdoV!n4nPnXphE6r zDqkxI3&zh7ktDKQjUfGv5E@j69EhNwKc@OrHaXcxxX$x;iEtH6O)-zIg3C|fBxEVu zH*+5}0Z|B5z|BUnjw#~x&|DIZOGosSRJvr=D7rz1r2!R}gj+&SgM-&~*bcLDVq!(i zU=ny+N8W;WyU&uulwcbXYfuV>Hau&zPX`}F;+-^O2Sv2bE^wxLcddr2?Lj7+_~^lAjI)E z>Dw0)Cu}KTRq?6=$)H#V%tJ>GLXxq#DDbT-E&fD>K|K@z()c!SG3i502RN9I-9MxR8I^e;HMeElP(nVuJI-jgIq|kmQJuIU0)kESqK7dX=jYUmslFq?r0^+uSMMmG6Uw-mA#<2e&`p%4Gz2|NaP_#j zKC|a;Gw62LFC{!6lO1nYcc%eiO69oP9HyV;AwTKmdOc6FWZcKH(Qi%_g^$1}q8sfl z2?{j+jDfKBA3q6Acxi=1(X*#6H+8{}i=S4inc0vA?h=Ro*Pyi%1hhH#D}*KH z>3Rh(RXgJFFC)UD-OL5VnsE&q8aj0 zxL0wx9>5!A=xtfTjMd@T8+^$JyU78^Y@oxwh+Q1WY{GGAeE{Gy{N7H=fd$S5j0R!B z*(Z|ejgeeEObEx!0nx=l*jls9sJqU#n+&X65FNxq`&|H7d8?oND($S+P&Rrq5Zq$p z%r~$=U^fla@`yhm=U65g_KIzj4eY0!LG<^zTDk*cAxs~Zu<%Hmi=}OKa{9Qo=mm%1 zJR&Tg-ZTPccrz~P4IO)8*QNYLB2wg|RQscGLL&*MEaqyxv>kHaQM$RlRqPk?45B7S z_*gnSKiQ;#K?(8!mD@QsOI}GkU8(gLq<$w$UZoWkuL zKniE@DcI=l(XW=?;4`0vl3FI9MR#Y$L27+Q_s4p|3oqUzN^H{Ul0$yR z=aev%S%dj(D~prX3v?`OujT(R+$Ra4*sJ^5U~JPJ9wPI3{QWKc$Dx(R?wq$n41>ex z-90p(cP%exc83iJDNZ{gR$INOtmI@sh>3=2ia{h=n*((BTjEP0ffNC~a2(F+nXvuZc=UCFJ8s&W_; zfov)p-Du;f)HTvpmfs$M2RIm z!qdzAbs7o{vC{zL;OL|WHIkulY$k=XgTU0p8e#{TWE1$Al9J;1kM6j6MvS6jt zW?<*d_l!l93Q067NY~e3wJ16DZ`ejw??Ir6`$Gc+MxtZ-f`WEYjjNJTY+Z2knI+`z zi#{RDO{|g0Van|A+P}oqaFxy_Kw9DMtL%jWPebh**`X!kNOE^hpO)DT0gm|(Mv=$6+ZsEyH#YCmkC>(Vz7 z%pwz09oxn5)+$U|TmAy)L{xm6DQ?MZ>7u&MXxad{^C^L`9;;w%z_xp%3T@|(EdNmLi^>aUr0l0%e<%(=r6v~{VHT%N-dPk{!07XShPFZc%{OQy zDXWzQDX4%0t(Cs`9~{TeY*8;ipUJn>Qh@R-m+cAFRx}UvS`#aM60TNd)M|^z-#vJc zj8)s?+OnmX2k~Yn>D<*89{XE4W=UKhzl( zyFCYKz5g_@x*UCb>)v>{Ekx5lR?{ZVcPv1bu}Cg3naBLG1fKnRb=r>%EXW>oD@?s|CFQ2P;pSG4;E+B)Yw3~X)7c+TG``@ZP_l^v$&`EWF4p3N@}&%EW$o0_f?BPY#bVCorG_5Y;7jqP z$F9@?mKJs0_zMpGv`5S1J0?__v(e{~;xsizt`@n@Z{64*0J4|)~4xBAjwr1No#G(tNI5g@Rl9`2imj^Sy6)*KiI}g`r z0hQEg4ZD&WB`V9Y-1*L0AC4rbscw`(heKmebj&@M}PaAKRQs!q^ z-_j_SJ>S|Ijb27B9_&!QIa>hDMnPA$joDS2&+=I|EmO8QxXXzDB4@6#9$#2G1rc|E$GC$)a5#qFUWFV(t zO4iyzAwmDHZemb8wOl1mGMuJD5N>rjeoM|5aJ}NX!tt|ees!sAQG8w`OD+Fo9%xyvv8_R|fW583;l1ZdC^v0)_a&2E z71mY+(D^1_(96u}eT#RwoOR6v!OuGWOSRZ_@()XG{IH}$>GK>)zQq&5{hQ!XM^uYI zn^Pvr49)uR-ovOU>Mx0TfX^RS|4CQ0v;0$0+Y#0hrAxU5>dAkNFn96k zjoF9hf^)4+qTU$K78QiyO5m&1%t?xghqgGXWl8xb&VC>vjT3h2@+VJS8p7m<0t?t}h?L<(@Vv8w> zDOSS+dT+<=f1vTaKxfTv88$N}KAksrQ6-Z*WW4FJ<p zyl#MINvj)lWYo+(l%tTJsIo+&)NQOB>^BeHRQ&9JQEew8Ni^ne8Ov=y*(J?tldlBP zZ>iFoW!2Qp?!P@-UJ~0lH)M_Tli?-4MA@&@E@?~nvhwUcq@aEk~$1Ek4m&8@PV>x(y&p$A42OOfi2f)^$kbidb{&$YIFa5!bo$Z zN6R-7ZcY9{8X!Rt5&AR;44Z3??{3y#CDNS5sa^4jovApeEUaT?*L-OrvJ-vIW8kZT zJlZUxVHKFQQC`40nUix_7#p9M;w1{emeowrclZ=H0}%W;xtTssf@~`3cAHZAdR8D; z_K6(}2?{~wV34Q9FXVw)c9RVmX$`8tX`$oAc+-a?!^P=CmuRO+1ySjZ!vM%U7$n@fJ59P(PHHrU* z;O4Kzw6_%kKmK?@;?4qUG#vHf#jXUdabHMpelrcYigd_Z@CF^FPFj&TqD3mjUVz`2 zsIfy8L?zdrmRwFLabv@-yMX~&?|AKyM`?BZ%(ALj`{vMSGkqX2|1m$YO`4jVz+Xg@ zK+RT}b^0M5;c9WmczPhIS6lX_=qtP5nG>?1$k9uSe z6K-6O5T-K&z^9xKBvnH0Ce>FXi}xCU^m>|^tW5*h+S}<%M-btLGPTGKY3~l@$PRh$ zi4|_LuSTyIM^j1yDmYI!?%Yqdh1lsKgY(%m!8Pi4{Xlp5w)P|$#c=7v{WM>-Q1DV} z5*}sm5+ng#8?oEf{sMgY+WFDuW-z*c^yROm9X=kFro#w#s~g=-=qkf=^d1PNZfSh0 zYR#G8kX<&Nz!UviQ=1{bKrS>n<|@0)%l4GRekx)h#>S@%)U4*6VFXANod9$vla9d8mMqXIHXk}Ksw~)Z1!JR4G}zzd4>29)RAd4=&9QUN5r9zgV?wI=c4cW$5tdM#L|}3?<@hIJ{~Jwz$aNY zb}iH&_CLiPJq>v+F?R?@EtA(HxbsqPd&!j(s;ed{dG4hLc@15Ikf(KJt?~WUHaKhg;bt~_skdU>8V?)O>Qn9fJQ(orwNXU*O~ zxw)FN#HkZyJ^`?=a~SmVu@DV1Ln^D8wuI-fz3g4MnT+{uQ!s)NMr$4Lzz z!}FrTyGJA0F3r|=MGh9r$zs^ZM4Maqx_#`z2ZA>5iHFJyYAOuoA~YTav89~FVK&$G zgnW&KXF>TP)xcXEgl;feqC>iUaTO&E5Hc5I<2V)hbuI$jOt3LjlND+8{wXU2_(&^u zN?ttb;bC(b-_Hd7BUyrTb$iV;KQp(o#J+oKH^N%wn1^!_5Axvcqi3yFWhBJC)z-Qo z7Rm#jltZTm$$9ir3Rj>gBG2@DET1D58#1AH2nojwK1K z*W={Ut=g=KZ-#ud>j6SH*s?j;FTo>`5D{!6OJl%iNH5b5x?<4YpN%u0ogY@S8w<4y ziL`sLOARwp@8j!pnFXIwoEOd_wyoj4C*a-TsKn#kv!bAfLo)r}_&Y#hHfO8A>FS;K zF3Q(xVlOw)7?jEdIjbd!-R(Ud>ihP@zJnxPX&v#$B(sY%a11SNX&~(?2RQG++py!^ z_IfnyAz1-NL^rS?HTsAX9Wf*Lp-B1WKx6CYCgK(S&?FiU|E8$N8Akj~OjmJpY4Tjl zVtoYgYE6~Nln7?+AN>=9VnBT!>J>n<*$K9)ApTVz(Xj8Yzo`lzel!+ZG&0HRel!*; ze@ylK4|%qK_nm#HO{)J}a&kYu+q9P!Ch*dy%?~bsD7tFYQls_sVzpXYT}cI|LC-v( zBE0=sm&1ZjCwe9V6%2CV#_DkqjZJE@fX*Z*h1UpO$k`&f|NJB%KCMtB)p|G$m`5UR zk`a)0T@VT`68)ZU-9$2HUb<1S9Fm6&+>mOucZ_y4-cKOy5ER+dZ3vK-knou}f)|>IVhtDlETX*oEBywwygFUnTdZKu4 zmZK}ufOsG@8l5637I-2ObTIet(}{#1%0U1Xg;n~cT_o)&=@iKff9T1SLNXZ;PUG7z z_W8|!x_WyA`_&Y9`|;$wV(+^~evB-pe{=-?k8b9FJvskS&Q$-$y@ZaXiM4~d&d1>V zU%i3zs%}=R%n0v1Upsp1X${$`l7trPH4ZE-5)CmH7=>GfrcnW@W*Zb$5XmU2Tkl&h zBj@4}4LB_PB#!u^cDcNqIAh=5f{alutU%2tc7vO-H7Am{b{h-rnD>KL)mGIiCU?;1 zZtYN=Wp)@aLeI-P8yf*C*V8|H0CQaJq9AR7(z{icS`mB_y%6ONoTz6>WaxLit!B88 zFq+|3zG4;kA3euzn=4v5JvN|R6Py^aOhyaz!vd>D4k=B`Du&`BAX&n`YDE*zX1I2T zqnXf3?_hPi`I5%K28`i512o5?Uzj-{POojlUb98oB+d_@l+D6CE-Kh@*h2)VejEU( zu6Yp3rh0|o+xNpQl`{fWV*>>H3CJmsuUxP4g^lofN9?H}<=&LY-eGSn3_MEi{oBa- zlqkAg@ylb`mKlN<$i-kAby#aHLQ9z&g!}gJJfbEAkE%X$)baUX!BFQOJ**@cWpc1} z1yG~n*6DRc|NIz5@|G4D63@0zp81w;&)N(t8`8D|jFpt%mjkz5Qr*?)NC@$wfZA#o&OQ4Q5AcTvS(;pZEI_{1NG0oJIjh`QnNT zkY}&0ZC~(vTj)>jll8=V3=-xBHh<_sViZ#-yuv8A{G8gQ=hcGI8Ohg0b;A`^rgrXM z%&<4=lYphQx5LPJxqFO@+f}NZjG~c}%mj2_jMd(&&TbeI;1Q9OPIHYj^q*zot@#mD zF6+g{!@aD2qV=hfXdzX1a#GS#+gxAF==&U9W>sGjN()#q*4IRWV!*5`Oi~$SsI+fq z-V(FwDvr*6s&NZQsh<@S(&)zv7n8z>zB=DZoQf(siliKj+z`(yDO5Nj_Zxt&jwBre z6uR{Rg|-|dpg2IqrnWNeR%EL=s!@}t$X@rDD&!yR-8s^fLDxd#)Vl25qEqc}k?@kC zBXD4~ArMA4SQs9pWoOO>9ncj&+|xbzu^A%=a@*U(_qJ`R`^T#vJ)%d}Bq<~m2aK9j z7;$FpU2`+uOEo5d6F5}GwUlKxXIs%(KrB_;RE_nVl8Sw?ovgtDnVk!ij$JI$)w5Ct zJ=)a)-5M{~h04YcriC1k1dGxDKI2eOG;0U~U9){t-LPc3cPyD%;pnyHzHyPKB0*+m zx#QbLkEw73bmd2?_ksIe)5BdXfu@Y{#d|(=oQC)zmG-9$h-#c}JQb(~!+S zd7!Vu2fSBvTC964?6A_PPu3eRi@=zf`nF86=I}i zE$gjfKbt^!DCITPm7YqLlIhYc4;gx{M;y%%nFHXQUt2RXrnFUghCiK2RZC3UY_xcR z!!aMb)WPZ$I;}0$59qMR4wf5N6WrPI#X=hNp#-#s+4-y*TV+x%sN~Xt#VJ@M$Sp^V zi1A~Eq-(d!2nafRv}!&~?Xay)_cQrQVhVL77rDXh%YeVO*irCtu%=cTCnyDO2*4Q| z5{?=xO%CnA`#W}8P<5d+j^vDPVoLA!E?5lNxA_OoJZ{s>h|SHOSp{`lM3Z6*NI8Ng1AlN zm6gpe%5WyTp}gu!G_Y-!KsD6b+IOU_)>AWJCp;iXz5R}7 zXB+n5#qOioi`(&s^46Nfade&=ZB?$MQeDG5oCEf_Gli95)@YISXtIbA6;YGWBYHqB z>`iT5Q{t3MG9GM0&S!cW41VqaWCY6Y+=v%lEiszSc4=3uhN)3U-nFALz~TWW&IbVnY~d+3&gn|>98-STy(8_JYAa0mGsvrCL^9Klo86q9KH?vc zruC9JpH$R*m*s6*jwkdj-$M1D$2iPEDaF*xKs4j!HtR=XGB0go)=$qYG3?E(GHSjc zSZ5DVjNEcuBfehR{|F@Jb=V*ciK~^S<|{P0ihwX@oyri z=OlMdLeSphu61rItxAjsxs)foDMP42Nt4TB&17uZ1GM;4yO$$tI(-q?LFaRR7J<|- z%Sc3y#78KajS+qQ!nsUl04u~u84s&Bs&pFbe(xeYkL~bw?L|B2SWNrS-cyhy6YfZn+%fJl#XN5a8z)If2@}tszX_<_E*mlze_~-YhgPC~eEt%bNol8F z-m>gTrZZPbo`kl{FIy=)|CaSK8Y(Cu)l}yMlAoTCS_8`iS$z5kUaefL|F9be8((&@ zzb4+Ci4v8tvd?2BGT+PdyGY;E$5lO!{pl3#`KFpR^DqN+Tp!esbYY<;Fo4n3bISkbeNpkW$>GR7WT*sDtf1g4yyJ3f zxDn1p$&=V>%R2P}XE6-g)A*Ss*{hR$UY8Xe&VOeCs2vtOsu%F92~<#z4qUD}rZ)<0 zkW>B++<6+6Ycw!RBHkLhCr<`HuXn^^&oD1vVhFFAQJeK{s3qvi7tlC>ux)@nFw@;q zg+**`!p1nqRK*NHq}}(Do_NplJb=@tTs5paylY{9tl}#Qq9V>fWDGY%hdoi0nr=d6 z`R@XxXePCyQm0(mW^(9zy5NNhmO6EVZp!&K#orx@3rq~c3R2gzZUq+&{n&-%NTK4; z{Odz0p{%ZE$CD;^CG!M@w?7ADQ;g2Y(l{Ao&E-T5!N?MVM&j!XqYznXcD8$VW)(uP z1sOqx{XBI->y9(XQ@WtH$DCe0JSxzwbFBE?p9s1CS{351dGPnuj-F%0-j2Arsk$mD)6j>6q0P--*&^r12+4DsK% zTyVn&R()vstZ7(P+j<4rWTzaXhEdqp!S5U@+`eI6I7d|0W-Uvq=5X2VTsY>bYQImi zv8zh|hTHQ;cdMRerv%CH0hskf(MTwItT0;KHs5}GvfrZPfF>MB;RDRWYwh!uwe<%V@NPS@tdsC zAd1g0@MoEuNwGWf0Kt>`ar8%@n@HqvF5|4&CfYySdb&(|E)#D8TT>Y`buqIb#0d8g z@M8}QfnY1lgH24Ub0=&)(dL+tX<+bQCG>AWMGq~Av595f#@K!+e%IFYxmq3X6j@>6wSqz{w zfoMJS9e9VI;hOm4)d*b?$Q%r8>zvcwiZwB$L#$0>fr!qr+q;oWMipXh7NLprlqj_|Q$rwQh$*f5SL)T_*; zYO{PXjAtt;P*43Fq1d^doyZ80gcWOMoB*PvVXlo&r@*?QO?PVyQ7v`Ns4TxFksM%$ z)it+b2{9^5ke7mIR9eRjOjRa8BxRx3WlM?+Ob_d>ZbFJwrgWYx0sR#gj-VR>sHHf{ zQItY1CIMm{^)o%AhM{Cbq5-z#kcXqJEvC;w6GFDs_j!hRx5~tO z{O;j46Qb&wu_OV&I!6cC@6vNO!JMi;Dh*Q2ZrjpoQ9qPO(fBjma&Bsoh(`6Gwsnz! z=e%KP0t(T8C6UcRJVL0 z^DP6}O_PN|WdZ|@NlmaFs7ZxHiHqeyo56$!5uGfs%s2dryr_aXTW4GcF~s-i+_GH#s^L0_^+M-ki$a7cdz zysT&(3Y^R4zXw_RwdVk1d&~-W3u0YMIuH|Xm?54mC)-=}yA?A3@;Y#T$-9gH9E0HA zvqc%Gq}nS#v2ui(sapB*4`SNz_7kE5-fpXsBIA^h5>eOja9yGL$=v$uIwjEfQ#w#vNQWrNY zD0*(-R+EQ>6X;Yo(bSV$|=w93>VD8``9>*L|5k+qtQrR}C&cSr2S|pxE zN0Vw*&!sQ1_28o>wXezNl^jS}Z$3RG!6lF_d=s0Qt?lrI%T-q7XjCJ2CE?0yI*Ar$ zrWD{V77_FQsw#Hn)|A(iTJ3T*K|GHXZOLk(B963T3fd;sBQrwx9CPEPr_;x)X6x?nhW4JWZ*i8HFf>BFjaC&wr0ss^8)`eMFT~kAa!_|qv zkbI2BhKp)CayXeP1HN)U8RDi-$M=l_&S*Yc&0pGGtuJx_Uy?iPp^4+>di2@0s%w=y zF8blGv0Oj}-fMq-)NAMdo^ugzz?k!+EVe4dq%sI6I?IU(iE&3je0~Wqd`J};{3s6o za@E7$g=yv4();bQ>CK*ha%XwN_iG8g89lx+)PubJ^}wC>ejI{GuD_&6`mk_KT7~ns z+|UtfoCV*cy7!FWP|B)d-X@A3I+_AX2cL%Oa0!r4;fk+mBx&C_Cy;^i6+rt!C`A0v zQitccwW>v_HbN=_m6Qt64p~?;rkx`PxrFc!AzO1nQ7AkU?WvqHn(qB?X<$;pX`3FMggJ2{hi*BB_dWCoy+n5Qm5kVMK*cqye7j z`F*v_SF4F5MS>{Hg3gO7cgv-u-3t{L79ebmPBDd%__YWWZCH>GUUkJD{0We@AU~o#v(GqXIqX$!3VU&Q7j&y7I&&4}pu@FN8 zJ21?9Gaw4kHipOv`M_T!5=;lsq!AFa2=B(f(H4vmaz}W-#-i4!_Bsf3t9}o(%cwd= zj9vtTG*_q%^I9JLfq3@=5KBH0Exlnk%y57p6{OZkf7lFiKD$ZYC?!G=Q3d>4FUQxA zaBWh8t8(WtVN|*${d&|&>g9!a*JRXl2hr{(ZTjTdPuUg&7u&H}HFV!&Oc&@MhmHs! ze2Gt(zuQcsryvQFxDA#8sE$xe*9?=YSGIT~-+L%Z_%Ob%C-%EW@|`-A?H|))vNmNO zI+7Tj78uT{G?J=ko-Q+D8D5RiqIR5GOCk9^S41fx4G0#m!a)}6U0L@%gUi>{f)JLc z8=Hb+O}{3BR#A3kA4&_c__bsRy@KY;tlk#wX9-wLW`{Blu zt;Sa$RDz9yfw{7MwO@UzkcXaD?Uo#Z!Ud>U0{}=$C1NAJ11skuRQ#w!pHb5!6LAcQ zkyxrzv2GAp)T2uVvr5EIG%joPy zz!zR*CZ;%N@CKnyncD_{RRapAY#BAh{U0AXvf^gOD%HSnpFjKeP=wzP;0`LAtQoGP z#1li5mG}eYTTlClzFyp$IR$3~;!+^6)=m9{T|j1(Nn;oxou93A7};7%q!<{t@)(wS|Lg17eBlzjk~9y%g)VtOtkxjNyHvj@SW5V zIt+9?v=;9slP)dMk+g%`&s2~imGhT(5B(2dQ?iOc4rer24Y-DtUuuWu=-R(#C!yc;#2lr4Wd94zQw+MPG)c0nf>-k~jU;LMoEfDjdDY><*u9JC4mK-D=bv*WPG+OL4kG4IoNb^H_h#gZ&)3Q9AO-jk`=8~) zQ8ySlLSlYbdHl_BjQ1}?uApl8jE@q_&4>HyUoVCAKHTR1$_Db~C9FPFvbn%R-f3f^ z7eZvq6uIQ1^Fk5j@$>V5=CHjhm>g_j92_Ht(TYk&x+xhh$J_4P zwtwn%41I^4Q#GleQ(74@$N3tSsiE1!@`R%v@qEbTg&h&}dRxt-nF$O2+D*!O#Ec~p zBcNCU!-^K5P>e{YmB=X?KxYhO{3u6~2mwAmz*h@6)#8WL^~|Y22i8>}2PQOWtq~Op zL0pGQJgI=gnkd#JkKVJ&tiGepZvf6&?QxL-oYVN49-N7gIiJc4W)VHwVsxY?D-c>! zRkDRJ7Y|eca!a5O1V}_L5cy+9*(uXs82kzV^Af5!rj)XWVl6+J%6=gr6D$34<;sB3 z{k8-6o$x9%&*Ia9c{D*>VHDvED=DI>X}yE>Sgo#3%wTdbLH*hY@tZV;tzV*XJ8vm= zuUU44a`nA=#se5?F=foH>dmb>g;m`YqC``la`A}@p9m!;U&YPCG*GS@vt3dpc7sywabj||V`Fr6Wldv%kX}_w7n({+D{@m=7+mK=AQJPL zM+fGttGYVOa+zznnFdYDGbxA5cYz{<0pl`718EE|AA$$JX89QF(s`fg@+4nDi_?a3 z19!e1dUYQ!7eI0Bz5AeP^!^CiA&V43zc@MV(V4aUvm+~z#;A;`55O^gH8k)PicIjx_i;r=S;uz#GxbSYDqRv#y?{YQ-u z^5Y!-+v&Ak`P6c^3FU1azy*Aw%R8<3GimLr?v{8>vc#H2l_b3c{W_2&1ude5UG8eM ze48Y#*ORD+f6nE+ov=&}<#LL4=zSDF!DRYov-G9c(82NpDiQ%ohqej@vXr3){S8m} z;9lm{`^(PLopT;;Wd)5?ROS6H4KDSh)TGzUBx!C`QtmGNVlGmRT~#~Nc-+@(M*I#b zy97aU*jMLc@J|V`!_>z|h5iHKAMFHO7FM5T9>w7xpQ*w=W>Z6)>~`->^}$og00<~R zG@eva9bzC^tj%cgo?G?D3>lT-LJZfq209PBI!d`f-pH@q7V>%5s6xvj{y_pii_PpK6$K6}S zxHUB%I&ovIsxAc*>zC@=LBWP8OD2S>FlgxjQ&pwLXVvi)#^9IZ4`OamqL%eiX|?D< z&2!cBFtA>D1M;9NSuvDYu6-F(Zc><%CO|SnXTFO z@_b~wqY*hlU4gG4DM{U~hP2RAp)jBnWcG+@7${B=Lk$gl7#{efJz(;ct};-63xym| zsJv`N5_`kP22u^-a~pLEUdW@1wf$YB=s~H$0H>`pf15KokC0|%1VpVQlEc|d->g|9 zb+i7C59DOW;_zXyyu%IlQN$Li?OIu!5kQ*&MUUc*ZS4T!7@&u^OTstJih|3w(7gtL zgK_!1kQP9G-aJAVyM7cbI3|D>b$(Al(1?8KI#p8_mH0_#0&sdgz(SeKlTcD7ZP+V3 z!Z^GUzJ`>gZ?>d4!!X{ELC$te*b~V&(EQ##cxb-FB~gqSz!pivKN&%r%W2~Rs$XAfQH5RtG`G9W;`>N~ zfL6Z>*V#Rl6C%26DKmzpTpCLgkZe})4nLC2ncIf`%l1p5jterAOWdoPc|oJ%vOeti z`N36rUI^b)7qOCO^g&1uy^t|ttP&N#ynXUFI8gsL>VOQTfH7&GAdQTo^!lt~^7{3O ztEVMmxbs!qAEl5z#H$&U*f!8;(MEb`K`#$`&44fH#c zP~P-i{H6ZjnEL`~CE+LKaqik6wZb(DCE0hsVSOMcc!s0k4X(Xka0K} z42lyl)(6K(mCP>6r8mMelnA&IIz8hx^o#~&oVGqA)&?JJBm=7DaUG*mcFNj!yyB&kd|Sj&U3t6A7TYV%E`emim=-{gW+N z70Uga&i9r==iQi3z1M-UpfcWdEPl4AW#Pp`F918LiJ7hgx59)Dx03cu|0j8dhFT@ZAr`Pc*MiuAKA!Q$APBuZyR()iwn}1BEgfa)_64_LQ}n=BwJ~U^FmuY~jdySol0S4`qzY?M(OId(G9P=yEf7 z@bY-^Sr5G@E;6Wi0&x>f5&`Q}DXGn(U_J&tfnWkJR9MkXX#9L(!@>A4Qa9g`JE=}4 z`_7!?ylc>-Zi5^M4?+pQ<-XBviw&k8q?sSY*^hklyN4{h!;bY zyUPhtCr_sQK(#`@4q`o|4uXDv09K#xr&=uKYYSbhZBhu0gB2-hp9N4B*|7yu9;VGL zFXU0gY-l%qsAR*x<+GGRO0-r?rc_AgaS=o~fD*qTk zR~#ir9AjtYQo`Zm6E}YK<4kEqw6A-S_90Aq@n`)z@pDolAgDCf^kDu4gZC&@lSCIm zy4KVywX+4lE?Zf4!1w9~63?G5RaQBjbPQ|cH5#$iM&-#hI6a)ebY6Mciv+Xr)eC** zlF9vyW_~osz_Y-;VKm&*%bJd-)i7q2ocmPcV>i&<3JyBnr^1uzH|uPxa5`FE8czdm z-7IU6cjR*wAotMcg1e(+y_Ur$UDtLbr@71XLKC==4UKOfcAkJh$p5glg#h^ak(YG# zu%VEAVGf$zoY+hqW*OQ|0oy!9sO@#>i<3XFK6msH^$$C|J9cDYvF%(s{R$N`+M|% z_*VZ;`I9a5FAB#;miYGp{FCzUj=lec{HGQ3F9^&>j`<&uzm1uHr~Jw9_!s5;zbXH4 zKmMKYCzIP>1a_GJTF1XO!+&S|$yxRn0}}Q(#-DsM0!|&WbDWm@4{$-K$e{%o*u>2?XPYR*Guu}BDVgL0n>d$(izc2Mq zqLjaww2c4F{DVa0@4!EG8~*|psr~`@d++h@s6X|_{z3_;|2OJ?@09(W`={=lUtBEx z|K|SItMfbPPwg1LKz4>7AX6RZ|9l$%w2k#E%Esa&>YtlhzyGN}mAifgc{=8^$_>(DpdXi4008Wdx4(*MOWuEd_5T2=fx{92 literal 0 HcmV?d00001 diff --git a/updates/1.60/ver_1.691_files.txt b/updates/1.60/ver_1.691_files.txt new file mode 100644 index 0000000..1d496a4 --- /dev/null +++ b/updates/1.60/ver_1.691_files.txt @@ -0,0 +1,4 @@ +F: ../backup_20250512_232458.zip +F: ../backup_tmp.json +F: ../sitemap_cmsenproject-dcpl.xml +F: ../sitemap_cmsproproject-dcpl.xml \ No newline at end of file diff --git a/updates/1.60/ver_1.691_manifest.json b/updates/1.60/ver_1.691_manifest.json new file mode 100644 index 0000000..8a72fe4 --- /dev/null +++ b/updates/1.60/ver_1.691_manifest.json @@ -0,0 +1,49 @@ +{ + "changelog": "Refaktoryzacja DDD Faza 0+1: PSR-4 autoloader, Shared (CacheHandler, Helpers, Html, ImageManipulator, Tpl), Domain (LanguagesRepository, SettingsRepository, UserRepository), testy jednostkowe Domain\\, docs/", + "version": "1.691", + "files": { + "added": [ + "autoload/Domain/Languages/LanguagesRepository.php", + "autoload/Domain/Settings/SettingsRepository.php", + "autoload/Domain/User/UserRepository.php", + "autoload/Shared/Cache/CacheHandler.php", + "autoload/Shared/Helpers/Helpers.php", + "autoload/Shared/Html/Html.php", + "autoload/Shared/Image/ImageManipulator.php", + "autoload/Shared/Tpl/Tpl.php" + ], + "deleted": [ + "backup_20250512_232458.zip", + "backup_tmp.json", + "sitemap_cmsenproject-dcpl.xml", + "sitemap_cmsproproject-dcpl.xml" + ], + "modified": [ + "admin/ajax.php", + "admin/index.php", + "ajax.php", + "api.php", + "autoload/admin/class.Site.php", + "autoload/admin/factory/class.Languages.php", + "autoload/admin/factory/class.Settings.php", + "autoload/admin/factory/class.Users.php", + "autoload/class.Cache.php", + "autoload/class.Html.php", + "autoload/class.Image.php", + "autoload/class.S.php", + "autoload/class.Tpl.php", + "autoload/front/factory/class.Languages.php", + "autoload/front/factory/class.Settings.php", + "cron.php", + "index.php" + ] + }, + "checksum_zip": "sha256:f53230f36d391828f4e368f3fc3420d8f9430ca507a1d4f57a0988823ac22192", + "sql": [ + + ], + "date": "2026-02-27", + "directories_deleted": [ + + ] +} \ No newline at end of file diff --git a/updates/versions.php b/updates/versions.php index 2b32f1e..dc132a5 100644 --- a/updates/versions.php +++ b/updates/versions.php @@ -1,5 +1,5 @@