From 285cbe551564e54135faf452d05bb0169d893d69 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Mon, 16 Feb 2026 21:25:50 +0100 Subject: [PATCH] ver. 0.282: Banners frontend migration, Cache cleanup, Shared\Cache namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Banners frontend: front\Views\Banners (new), BannerRepository +2 frontend methods, front\view\Site przepięty, usunięte front\factory\Banners i front\view\Banners - Cache cleanup: eliminacja legacy class.Cache.php (file-based cache), 13 metod front\factory przepiętych z \Cache::fetch/store na CacheHandler - Shared\Cache namespace: CacheHandler i RedisConnection przeniesione do Shared\Cache\, 60 odwołań CacheHandler i 12 odwołań RedisConnection przepiętych, usunięte backward-compat wrappery class.CacheHandler.php i class.RedisConnection.php - Naprawione rozbieżności kluczy cache (random_products, category_name) Co-Authored-By: Claude Opus 4.6 --- .phpunit.result.cache | 2 +- autoload/Domain/Article/ArticleRepository.php | 12 +- autoload/Domain/Banner/BannerRepository.php | 80 ++++++++++++ autoload/Domain/Cache/CacheRepository.php | 4 +- .../Domain/Dashboard/DashboardRepository.php | 4 +- .../Dictionaries/DictionariesRepository.php | 16 ++- .../Domain/Languages/LanguagesRepository.php | 6 +- autoload/Domain/Layouts/LayoutsRepository.php | 4 +- autoload/Domain/Product/ProductRepository.php | 2 +- .../Domain/Promotion/PromotionRepository.php | 4 +- .../Scontainers/ScontainersRepository.php | 4 +- .../Domain/Settings/SettingsRepository.php | 4 +- .../Cache/CacheHandler.php} | 8 +- autoload/Shared/Cache/RedisConnection.php | 45 +++++++ .../admin/Controllers/SettingsController.php | 4 +- autoload/class.Cache.php | 57 --------- autoload/class.RedisConnection.php | 53 -------- autoload/class.S.php | 4 +- autoload/front/Views/Banners.php | 21 ++++ autoload/front/controls/class.ShopBasket.php | 2 +- autoload/front/factory/class.Banners.php | 73 ----------- autoload/front/factory/class.Layouts.php | 10 +- autoload/front/factory/class.Menu.php | 4 +- autoload/front/factory/class.Pages.php | 6 +- autoload/front/factory/class.Scontainers.php | 2 +- .../front/factory/class.ShopAttribute.php | 14 ++- autoload/front/factory/class.ShopCategory.php | 34 ++++-- .../front/factory/class.ShopPaymentMethod.php | 30 +++-- autoload/front/factory/class.ShopProduct.php | 66 ++++++++-- .../front/factory/class.ShopTransport.php | 26 +++- autoload/front/view/class.Banners.php | 22 ---- autoload/front/view/class.Site.php | 5 +- autoload/shop/class.Category.php | 2 +- autoload/shop/class.Product.php | 6 +- autoload/shop/class.ProductAttribute.php | 6 +- autoload/shop/class.ProductCustomField.php | 2 +- autoload/shop/class.Promotion.php | 2 +- docs/CHANGELOG.md | 32 +++++ docs/DATABASE_STRUCTURE.md | 6 +- docs/FRONTEND_REFACTORING_PLAN.md | 39 ++++-- docs/PROJECT_STRUCTURE.md | 36 ++++-- docs/REFACTORING_PLAN.md | 9 +- docs/TESTING.md | 8 +- docs/UPDATE_INSTRUCTIONS.md | 14 +-- .../Domain/Banner/BannerRepositoryTest.php | 115 ++++++++++++++++++ .../Unit/Domain/Cache/CacheRepositoryTest.php | 4 +- .../Controllers/SettingsControllerTest.php | 2 +- tests/bootstrap.php | 6 +- updates/0.20/ver_0.281.zip | Bin 0 -> 7732 bytes updates/0.20/ver_0.281_files.txt | 2 + updates/0.20/ver_0.282.zip | Bin 0 -> 80768 bytes updates/0.20/ver_0.282_files.txt | 3 + updates/changelog.php | 16 +++ updates/versions.php | 2 +- 54 files changed, 594 insertions(+), 346 deletions(-) rename autoload/{class.CacheHandler.php => Shared/Cache/CacheHandler.php} (86%) create mode 100644 autoload/Shared/Cache/RedisConnection.php delete mode 100644 autoload/class.Cache.php delete mode 100644 autoload/class.RedisConnection.php create mode 100644 autoload/front/Views/Banners.php delete mode 100644 autoload/front/factory/class.Banners.php delete mode 100644 autoload/front/view/class.Banners.php create mode 100644 updates/0.20/ver_0.281.zip create mode 100644 updates/0.20/ver_0.281_files.txt create mode 100644 updates/0.20/ver_0.282.zip create mode 100644 updates/0.20/ver_0.282_files.txt diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 02003ba..a8e7764 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":1,"defects":{"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.004,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsPromoPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularWhenPromoIsHigher":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsProductName":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveReturnsBool":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveReturnsBool":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorAcceptsRepository":0.004,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasUnarchiveMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testUnarchiveMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorRequiresProductRepository":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsBannerWithTranslations":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testDeleteReturnsTrue":0.002,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveInsertsNewBanner":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithRedis":0.001,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheRedisUnavailable":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithoutRedis":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheReturnStructure":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testCanBeInstantiated":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasSaveSettingsMethod":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasGetSettingsMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheAjaxMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasViewMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testIsNotAbstract":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testCanCreateController":0.005,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testEditMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsArticleWithRelations":0.005,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsNullWhenArticleDoesNotExist":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedFilesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedImagesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveReturnsZeroWhenInsertFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveSetsStatusToMinusOne":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveReturnsFalseWhenUpdateFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderUpdatesImageOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderSkipsEmptyValues":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasBrowseListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasGalleryOrderSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testBrowseListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testGalleryOrderSaveMethodReturnType":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminUsesBoundParamsForTitleFilter":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveWithLegacyFormat":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveUpdatesExistingTranslationsByBannerAndLang":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testListForAdminIncludesThumbnailSrc":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testRestoreSetsStatusToZero":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeletePermanentlyRemovesArticleAndRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListArchivedForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsUnitWithTranslations":0.001,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsNullWhenUnitNotFound":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testSaveInsertsNewUnitAndTranslationsForStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testDeleteRemovesUnitAndTranslations":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdReturnsTextFromDatabase":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdSupportsStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testAllUnitsReturnsArrayIndexedById":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsArrayOrNull":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveLanguageRejectsInvalidLanguageId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveTranslationInsertsNewTranslationAndReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDeleteTranslationReturnsBoolean":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdReturnsLanguageWithStartFlag":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdFallsBackToFirstLanguageOrPl":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsLayoutWithRelations":0.001,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testDeleteReturnsFalseWhenOnlyOneLayoutExists":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsDefaultLayoutWhenRecordDoesNotExist":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testSaveInsertsNewLayoutAndReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testListAllReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsNullForInvalidId":0.003,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSaveSettingsUpdatesHeaderAndFooter":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testDeleteTemplateReturnsFalseForAdminTemplate":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateByNameReturnsText":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsDefaultContainerForInvalidId":0.001,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsContainerWithTranslations":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDetailsForLanguageReturnsNullForInvalidData":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserWhenExists":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsErrorWhenLoginIsTaken":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsOkWhenAvailable":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveCreatesUserWithNormalizedSwitches":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithoutPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrue":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsFalseOnFailure":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsUserByLogin":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsSuccessForValidCredentials":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsNegativeOneForBlockedUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseAfterMaxAttempts":0.101,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.101,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.202,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseWhen2FADisabled":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseForInvalidEmail":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testUpdateByIdCallsDbUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testConstructorAcceptsRepository":0.003,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testConstructorRequiresDictionariesRepository":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testConstructorRequiresLanguagesRepository":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testConstructorRequiresLayoutsRepository":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testConstructorAcceptsDependencies":0.003,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testConstructorRequiresRepositoryAndRenderer":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testConstructorRequiresRepositoryAndLanguagesRepository":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasViewListMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserEditMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasTwofaMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasLoginFormMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testConstructorRequiresUserRepository":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserReturnsDefaultsForNull":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserCastsTypes":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserHandlesPartialData":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveFilesOrderUpdatesFilesOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveFilesOrderSkipsEmptyValues":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPagesSummaryForArticlesBuildsLabels":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testUpdateImageAltDelegatesToDatabase":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testMarkFileToDeleteDelegatesToDatabase":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindReturnsDefaultCouponForInvalidId":0.001,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindNormalizesCouponData":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testSaveInsertsCouponAndReturnsId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testSaveUpdatesCouponAndReturnsId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingsReturnsArray":0.002,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingUpdatesExistingValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingInsertsNewValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testInvalidProviderThrowsException":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testLinkProductUpdatesDatabase":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testUnlinkProductClearsFields":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloGetAccessTokenReturnsNullWithoutSettings":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListThrowsForInvalidType":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testAllPublicMethodsExist":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSettingsTableMapping":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShopproProviderWorks":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenusListReturnsArray":0.002,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenuDeleteReturnsFalseWhenMenuHasPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testGenerateSeoLinkAddsSuffixWhenBaseSlugExists":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testPageUrlPreviewBuildsLanguagePrefixedUrlForNonDefaultLanguage":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testFindReturnsDefaultPromotionForInvalidId":0.001,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testSaveInsertsPromotionAndReturnsId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageAltChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileNameChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageAltChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileNameChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloSettingsMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloDataFetchMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloProductMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllShopproMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testApiloSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testShopproSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testVoidReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveSellasistMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveBaselinkerMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorRequiresPagesLanguagesAndLayoutsRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorRequiresPromotionRepository":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullForNegativeId":0.001,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsStatusWithIdZero":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindNormalizesNullApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveUpdatesColorAndApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithIdZeroWorks":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithEmptyApiloStatusIdSetsNull":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveRejectsNegativeId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsValue":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsNullWhenNotSet":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdForApilo":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdReturnsNullForUnknownIntegration":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testAllStatusesReturnsOrderedList":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorRequiresShopStatusRepository":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsFalseForFarFutureDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsTrueForNearExpiryDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloIntegrationStatusReturnsMissingConfigMessage":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListRejectsErrorPayload":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListAcceptsIdNameList":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullForInvalidId":0.001,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindNormalizesData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveUpdatesRowAndReturnsId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSavePreservesNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllForAdminReturnsRowsIncludingInactive":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindKeepsNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveNormalizesStatusValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdHandlesNullAndInt":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsStringForNonNumericValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullForInvalidId":0.001,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindNormalizesDataAndIncludesPaymentMethods":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindHandlesNullMaxWpAndApiloId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveInsertReturnsNewId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveUpdateReturnsExistingId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveInsertReturnsNullOnFailure":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveResetsDefaultWhenSettingNew":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveSwitchValuesNormalization":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testListForAdminWhitelistsSortColumn":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetApiloCarrierAccountIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetApiloCarrierAccountIdReturnsIntOrNull":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetTransportCostReturnsFloatOrNull":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testAllForAdminReturnsAllTransports":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testConstructorRequiresPaymentMethodRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFindAttributeReturnsDefaultAttributeForInvalidId":0.002,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testListForAdminWhitelistsSortDirectionAndPerPage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesRemovesObsoleteRowsAndSetsDefault":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesDeletesTranslationWhenNameIsEmpty":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testGetAttributeValueByIdUsesDefaultLanguageWhenNotProvided":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSortTypesReturnsExpectedKeys":0.001,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsReturnsDefaultForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsLoadsTranslations":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderReturnsFalseForNonArray":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderUpdatesOrderAndParent":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderReturnsFalseForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderUpdatesCategoryProductOrder":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsFalseWhenHasChildren":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsTrueWhenDeleted":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsFirstAvailableTitle":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testListForAdminWhitelistsSortAndPagination":0.001,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientReturnsEmptyOnMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientNormalizesRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsZeroForMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsAggregatedValues":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testConstructorAcceptsDb":0.001,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testHasAllPublicMethods":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testSalesGridReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testLastOrdersReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testMostViewedProductsReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testBestSalesProductsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsFallbackWhenEmpty":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsList":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsEmptyArrayWhenNone":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsDefaultsToPl":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsForDifferentLanguage":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionUpdatesStatus":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailReturnsFalseForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSignupReturnsFalseForExistingEmail":0.001,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConstructorAcceptsOptionalDependencies":0.003,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusesReturnsMappedArray":0.001,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testNextAndPrevOrderIdReturnNullForInvalidInput":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindReturnsDefaultProducerForInvalidId":0.001,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindNormalizesProducerData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveInsertsNewProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveUpdatesExistingProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllProducersReturnsFormattedList":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testProducerProductsReturnsPaginatedResults":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindReturnsDefaultSetForInvalidId":0.001,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindNormalizesSetData":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveInsertsNewSetAndSyncsProducts":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveUpdatesExistingSet":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testAllSetsReturnsFormattedList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditReturnsMap":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsEmptyArray":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsNullForInvalidProduct":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsCorrectPrices":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentZeroPercentNullsPromo":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsAssociativeArray":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenNoSettings":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsHandlesNullFromDb":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsCorrectParam":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueUsesParamNotHardcoded":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsEmptyStringWhenNotFound":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testConstructorAcceptsDb":0.001,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasUpdateMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testUpdateReturnsArray":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasRunPendingMigrationsMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testRunPendingMigrationsWithNoResults":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasPrivateHelperMethods":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsErrorsForMissingDefaultLanguageAndDefaultSelection":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsEmptyArrayForValidRows":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorAcceptsDependencies":0.001,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorRequiresCategoryAndLanguagesRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorRequiresClientRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsService":0.003,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderAdminService":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasMassEditActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasViewListMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasEditAndSaveMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasOperationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasCombinationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasImageAndFileMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditReturnsString":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditSaveReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testGetProductsByCategoryReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasFormBuildingHelpers":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testSaveMethodReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorRequiresProductSetRepository":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateAllMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsSortedIds":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsNullForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsInt":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsZeroForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesPagination":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsBool":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsFalseForNonNoindex":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsReturnsArticlesArray":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":0}} \ No newline at end of file +{"version":1,"defects":{"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.003,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsPromoPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularWhenPromoIsHigher":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsProductName":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveReturnsBool":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveReturnsBool":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorAcceptsRepository":0.003,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasUnarchiveMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testUnarchiveMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorRequiresProductRepository":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsBannerWithTranslations":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testDeleteReturnsTrue":0.002,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveInsertsNewBanner":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithRedis":0.001,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheRedisUnavailable":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithoutRedis":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheReturnStructure":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testCanBeInstantiated":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasSaveSettingsMethod":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasGetSettingsMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheAjaxMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasViewMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testIsNotAbstract":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testCanCreateController":0.004,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testEditMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsArticleWithRelations":0.005,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsNullWhenArticleDoesNotExist":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedFilesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedImagesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveReturnsZeroWhenInsertFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveSetsStatusToMinusOne":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveReturnsFalseWhenUpdateFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderUpdatesImageOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderSkipsEmptyValues":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasBrowseListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasGalleryOrderSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testBrowseListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testGalleryOrderSaveMethodReturnType":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminUsesBoundParamsForTitleFilter":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveWithLegacyFormat":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveUpdatesExistingTranslationsByBannerAndLang":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testListForAdminIncludesThumbnailSrc":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testRestoreSetsStatusToZero":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeletePermanentlyRemovesArticleAndRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListArchivedForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsUnitWithTranslations":0.001,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsNullWhenUnitNotFound":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testSaveInsertsNewUnitAndTranslationsForStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testDeleteRemovesUnitAndTranslations":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdReturnsTextFromDatabase":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdSupportsStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testAllUnitsReturnsArrayIndexedById":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsArrayOrNull":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveLanguageRejectsInvalidLanguageId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveTranslationInsertsNewTranslationAndReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDeleteTranslationReturnsBoolean":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdReturnsLanguageWithStartFlag":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdFallsBackToFirstLanguageOrPl":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsLayoutWithRelations":0.001,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testDeleteReturnsFalseWhenOnlyOneLayoutExists":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsDefaultLayoutWhenRecordDoesNotExist":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testSaveInsertsNewLayoutAndReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testListAllReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsNullForInvalidId":0.002,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSaveSettingsUpdatesHeaderAndFooter":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testDeleteTemplateReturnsFalseForAdminTemplate":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateByNameReturnsText":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsDefaultContainerForInvalidId":0.001,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsContainerWithTranslations":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDetailsForLanguageReturnsNullForInvalidData":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserWhenExists":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsErrorWhenLoginIsTaken":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsOkWhenAvailable":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveCreatesUserWithNormalizedSwitches":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithoutPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrue":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsFalseOnFailure":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsUserByLogin":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsSuccessForValidCredentials":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsNegativeOneForBlockedUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseAfterMaxAttempts":0.077,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.077,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.154,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseWhen2FADisabled":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseForInvalidEmail":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testUpdateByIdCallsDbUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testConstructorRequiresDictionariesRepository":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testConstructorRequiresLanguagesRepository":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testConstructorRequiresLayoutsRepository":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testConstructorRequiresRepositoryAndRenderer":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testConstructorAcceptsDependencies":0.001,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testConstructorRequiresRepositoryAndLanguagesRepository":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasViewListMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserEditMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasTwofaMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasLoginFormMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testConstructorRequiresUserRepository":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserReturnsDefaultsForNull":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserCastsTypes":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserHandlesPartialData":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveFilesOrderUpdatesFilesOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveFilesOrderSkipsEmptyValues":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPagesSummaryForArticlesBuildsLabels":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testUpdateImageAltDelegatesToDatabase":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testMarkFileToDeleteDelegatesToDatabase":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindReturnsDefaultCouponForInvalidId":0.001,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindNormalizesCouponData":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testSaveInsertsCouponAndReturnsId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testSaveUpdatesCouponAndReturnsId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingsReturnsArray":0.002,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingUpdatesExistingValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingInsertsNewValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testInvalidProviderThrowsException":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testLinkProductUpdatesDatabase":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testUnlinkProductClearsFields":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloGetAccessTokenReturnsNullWithoutSettings":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListThrowsForInvalidType":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testAllPublicMethodsExist":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSettingsTableMapping":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShopproProviderWorks":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenusListReturnsArray":0.001,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenuDeleteReturnsFalseWhenMenuHasPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testGenerateSeoLinkAddsSuffixWhenBaseSlugExists":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testPageUrlPreviewBuildsLanguagePrefixedUrlForNonDefaultLanguage":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testFindReturnsDefaultPromotionForInvalidId":0.001,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testSaveInsertsPromotionAndReturnsId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageAltChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileNameChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageAltChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileNameChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloSettingsMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloDataFetchMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloProductMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllShopproMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testApiloSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testShopproSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testVoidReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveSellasistMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveBaselinkerMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorRequiresPagesLanguagesAndLayoutsRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorRequiresPromotionRepository":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullForNegativeId":0.001,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsStatusWithIdZero":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindNormalizesNullApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveUpdatesColorAndApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithIdZeroWorks":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithEmptyApiloStatusIdSetsNull":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveRejectsNegativeId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsValue":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsNullWhenNotSet":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdForApilo":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdReturnsNullForUnknownIntegration":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testAllStatusesReturnsOrderedList":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorRequiresShopStatusRepository":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsFalseForFarFutureDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsTrueForNearExpiryDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloIntegrationStatusReturnsMissingConfigMessage":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListRejectsErrorPayload":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListAcceptsIdNameList":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullForInvalidId":0.001,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindNormalizesData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveUpdatesRowAndReturnsId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSavePreservesNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllForAdminReturnsRowsIncludingInactive":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindKeepsNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveNormalizesStatusValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdHandlesNullAndInt":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsStringForNonNumericValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullForInvalidId":0.001,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindNormalizesDataAndIncludesPaymentMethods":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindHandlesNullMaxWpAndApiloId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveInsertReturnsNewId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveUpdateReturnsExistingId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveInsertReturnsNullOnFailure":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveResetsDefaultWhenSettingNew":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveSwitchValuesNormalization":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testListForAdminWhitelistsSortColumn":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetApiloCarrierAccountIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetApiloCarrierAccountIdReturnsIntOrNull":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetTransportCostReturnsFloatOrNull":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testAllForAdminReturnsAllTransports":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testConstructorRequiresPaymentMethodRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFindAttributeReturnsDefaultAttributeForInvalidId":0.002,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testListForAdminWhitelistsSortDirectionAndPerPage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesRemovesObsoleteRowsAndSetsDefault":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesDeletesTranslationWhenNameIsEmpty":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testGetAttributeValueByIdUsesDefaultLanguageWhenNotProvided":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSortTypesReturnsExpectedKeys":0.001,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsReturnsDefaultForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsLoadsTranslations":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderReturnsFalseForNonArray":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderUpdatesOrderAndParent":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderReturnsFalseForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderUpdatesCategoryProductOrder":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsFalseWhenHasChildren":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsTrueWhenDeleted":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsFirstAvailableTitle":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testListForAdminWhitelistsSortAndPagination":0.001,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientReturnsEmptyOnMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientNormalizesRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsZeroForMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsAggregatedValues":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testConstructorAcceptsDb":0.001,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testHasAllPublicMethods":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testSalesGridReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testLastOrdersReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testMostViewedProductsReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testBestSalesProductsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsFallbackWhenEmpty":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsList":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsEmptyArrayWhenNone":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsDefaultsToPl":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsForDifferentLanguage":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionUpdatesStatus":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailReturnsFalseForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSignupReturnsFalseForExistingEmail":0.001,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConstructorAcceptsOptionalDependencies":0.003,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusesReturnsMappedArray":0.001,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testNextAndPrevOrderIdReturnNullForInvalidInput":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindReturnsDefaultProducerForInvalidId":0.001,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindNormalizesProducerData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveInsertsNewProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveUpdatesExistingProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllProducersReturnsFormattedList":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testProducerProductsReturnsPaginatedResults":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindReturnsDefaultSetForInvalidId":0.001,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindNormalizesSetData":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveInsertsNewSetAndSyncsProducts":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveUpdatesExistingSet":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testAllSetsReturnsFormattedList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditReturnsMap":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsEmptyArray":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsNullForInvalidProduct":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsCorrectPrices":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentZeroPercentNullsPromo":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsAssociativeArray":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenNoSettings":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsHandlesNullFromDb":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsCorrectParam":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueUsesParamNotHardcoded":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsEmptyStringWhenNotFound":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testConstructorAcceptsDb":0.001,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasUpdateMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testUpdateReturnsArray":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasRunPendingMigrationsMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testRunPendingMigrationsWithNoResults":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasPrivateHelperMethods":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsErrorsForMissingDefaultLanguageAndDefaultSelection":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsEmptyArrayForValidRows":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorAcceptsDependencies":0.001,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorRequiresCategoryAndLanguagesRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorRequiresClientRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsService":0.003,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderAdminService":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasMassEditActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasViewListMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasEditAndSaveMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasOperationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasCombinationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasImageAndFileMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditReturnsString":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditSaveReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testGetProductsByCategoryReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasFormBuildingHelpers":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testSaveMethodReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorRequiresProductSetRepository":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateAllMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsSortedIds":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsNullForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsInt":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsZeroForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesPagination":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsBool":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsFalseForNonNoindex":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsReturnsArticlesArray":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testBannersReturnsActiveBannersWithFlatLanguages":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testBannersReturnsNullWhenNoBanners":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testMainBannerReturnsActiveBannerWithFlatLanguages":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testMainBannerReturnsNullWhenNoBanner":0}} \ No newline at end of file diff --git a/autoload/Domain/Article/ArticleRepository.php b/autoload/Domain/Article/ArticleRepository.php index ae654d9..aea397a 100644 --- a/autoload/Domain/Article/ArticleRepository.php +++ b/autoload/Domain/Article/ArticleRepository.php @@ -874,7 +874,7 @@ class ArticleRepository */ public function articleDetailsFrontend(int $articleId, string $langId): ?array { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::articleDetailsFrontend:{$articleId}:{$langId}"; $objectData = $cacheHandler->get($cacheKey); @@ -946,7 +946,7 @@ class ArticleRepository default: $order = 'id ASC'; break; } - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::articlesIds:{$pageId}:{$langId}:{$limit}:{$sortType}:{$from}:{$order}"; $objectData = $cacheHandler->get($cacheKey); @@ -994,7 +994,7 @@ class ArticleRepository */ public function pageArticlesCount(int $pageId, string $langId): int { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::pageArticlesCount:{$pageId}:{$langId}"; $objectData = $cacheHandler->get($cacheKey); @@ -1084,7 +1084,7 @@ class ArticleRepository */ public function articleNoindex(int $articleId, string $langId): bool { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::articleNoindex:{$articleId}:{$langId}"; $objectData = $cacheHandler->get($cacheKey); @@ -1107,7 +1107,7 @@ class ArticleRepository */ public function topArticles(int $pageId, int $limit, string $langId): ?array { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::topArticles:{$pageId}:{$limit}:{$langId}"; $objectData = $cacheHandler->get($cacheKey); @@ -1156,7 +1156,7 @@ class ArticleRepository */ public function newsListArticles(int $pageId, int $limit, string $langId): ?array { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::newsListArticles:{$pageId}:{$limit}:{$langId}"; $objectData = $cacheHandler->get($cacheKey); diff --git a/autoload/Domain/Banner/BannerRepository.php b/autoload/Domain/Banner/BannerRepository.php index b2f8ee1..d70ee6b 100644 --- a/autoload/Domain/Banner/BannerRepository.php +++ b/autoload/Domain/Banner/BannerRepository.php @@ -312,4 +312,84 @@ class BannerRepository $this->db->insert('pp_banners_langs', $translationData); } + + // ─── Frontend methods ─────────────────────────────────────────── + + /** + * Pobiera aktywne banery (home_page = 0) z filtrowaniem dat, z Redis cache. + * Zwraca dane w formacie zgodnym z szablonami: $banner['languages'] = płaski wiersz. + */ + public function banners(string $langId): ?array + { + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "BannerRepository::banners:{$langId}"; + + $objectData = $cacheHandler->get($cacheKey); + + if ($objectData) { + return unserialize($objectData); + } + + $today = date('Y-m-d'); + $results = $this->db->query( + "SELECT id, name FROM pp_banners " + . "WHERE status = 1 " + . "AND (date_start <= '{$today}' OR date_start IS NULL) " + . "AND (date_end >= '{$today}' OR date_end IS NULL) " + . "AND home_page = 0" + )->fetchAll(); + + $banners = null; + if (is_array($results) && !empty($results)) { + foreach ($results as $row) { + $row['languages'] = $this->db->get('pp_banners_langs', '*', [ + 'AND' => ['id_banner' => (int)$row['id'], 'id_lang' => $langId] + ]); + $banners[] = $row; + } + } + + $cacheHandler->set($cacheKey, $banners); + + return $banners; + } + + /** + * Pobiera glowny baner (home_page = 1) z filtrowaniem dat, z Redis cache. + * Zwraca dane w formacie zgodnym z szablonami: $banner['languages'] = plaski wiersz. + */ + public function mainBanner(string $langId): ?array + { + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "BannerRepository::mainBanner:{$langId}"; + + $objectData = $cacheHandler->get($cacheKey); + + if ($objectData) { + return unserialize($objectData); + } + + $today = date('Y-m-d'); + $results = $this->db->query( + "SELECT * FROM pp_banners " + . "WHERE status = 1 " + . "AND (date_start <= '{$today}' OR date_start IS NULL) " + . "AND (date_end >= '{$today}' OR date_end IS NULL) " + . "AND home_page = 1 " + . "ORDER BY date_end ASC " + . "LIMIT 1" + )->fetchAll(); + + $banner = null; + if (is_array($results) && !empty($results)) { + $banner = $results[0]; + $banner['languages'] = $this->db->get('pp_banners_langs', '*', [ + 'AND' => ['id_banner' => (int)$banner['id'], 'id_lang' => $langId] + ]); + } + + $cacheHandler->set($cacheKey, $banner); + + return $banner; + } } diff --git a/autoload/Domain/Cache/CacheRepository.php b/autoload/Domain/Cache/CacheRepository.php index 9c161e7..ece6237 100644 --- a/autoload/Domain/Cache/CacheRepository.php +++ b/autoload/Domain/Cache/CacheRepository.php @@ -14,11 +14,11 @@ class CacheRepository private $basePath; /** - * @param \RedisConnection $redisConnection Połączenie z Redis (nullable) + * @param \Shared\Cache\RedisConnection $redisConnection Połączenie z Redis (nullable) * @param string $basePath Ścieżka bazowa do katalogów cache */ public function __construct( - ?\RedisConnection $redisConnection = null, + ?\Shared\Cache\RedisConnection $redisConnection = null, string $basePath = '../' ) { $this->redisConnection = $redisConnection; diff --git a/autoload/Domain/Dashboard/DashboardRepository.php b/autoload/Domain/Dashboard/DashboardRepository.php index 4f3cfc7..e99ecae 100644 --- a/autoload/Domain/Dashboard/DashboardRepository.php +++ b/autoload/Domain/Dashboard/DashboardRepository.php @@ -13,7 +13,7 @@ class DashboardRepository public function summaryOrders(): int { try { - $redis = \RedisConnection::getInstance()->getConnection(); + $redis = \Shared\Cache\RedisConnection::getInstance()->getConnection(); if ( $redis ) { $cached = $redis->get( 'summary_ordersd' ); if ( $cached !== false ) { @@ -33,7 +33,7 @@ class DashboardRepository public function summarySales(): float { try { - $redis = \RedisConnection::getInstance()->getConnection(); + $redis = \Shared\Cache\RedisConnection::getInstance()->getConnection(); if ( $redis ) { $cached = $redis->get( 'summary_salesd' ); if ( $cached !== false ) { diff --git a/autoload/Domain/Dictionaries/DictionariesRepository.php b/autoload/Domain/Dictionaries/DictionariesRepository.php index 30169ca..c8153bb 100644 --- a/autoload/Domain/Dictionaries/DictionariesRepository.php +++ b/autoload/Domain/Dictionaries/DictionariesRepository.php @@ -256,19 +256,17 @@ class DictionariesRepository private function cacheFetch(string $key) { - if (!class_exists('\Cache') || !method_exists('\Cache', 'fetch')) { - return false; + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cached = $cacheHandler->get(self::CACHE_SUBDIR . ':' . $key); + if ($cached) { + return unserialize($cached); } - - return \Cache::fetch($key, self::CACHE_SUBDIR); + return false; } private function cacheStore(string $key, string $value): void { - if (!class_exists('\Cache') || !method_exists('\Cache', 'store')) { - return; - } - - \Cache::store($key, $value, self::CACHE_TTL, self::CACHE_SUBDIR); + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheHandler->set(self::CACHE_SUBDIR . ':' . $key, $value, self::CACHE_TTL); } } diff --git a/autoload/Domain/Languages/LanguagesRepository.php b/autoload/Domain/Languages/LanguagesRepository.php index 483dc12..fe6d044 100644 --- a/autoload/Domain/Languages/LanguagesRepository.php +++ b/autoload/Domain/Languages/LanguagesRepository.php @@ -337,7 +337,7 @@ class LanguagesRepository */ public function defaultLanguage(): string { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = 'Domain\Languages\LanguagesRepository::defaultLanguage'; $objectData = $cacheHandler->get($cacheKey); @@ -361,7 +361,7 @@ class LanguagesRepository */ public function activeLanguages(): array { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = 'Domain\Languages\LanguagesRepository::activeLanguages'; $objectData = $cacheHandler->get($cacheKey); @@ -389,7 +389,7 @@ class LanguagesRepository */ public function translations(string $language = 'pl'): array { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "Domain\Languages\LanguagesRepository::translations:$language"; $objectData = $cacheHandler->get($cacheKey); diff --git a/autoload/Domain/Layouts/LayoutsRepository.php b/autoload/Domain/Layouts/LayoutsRepository.php index 093282a..b791c30 100644 --- a/autoload/Domain/Layouts/LayoutsRepository.php +++ b/autoload/Domain/Layouts/LayoutsRepository.php @@ -297,12 +297,12 @@ class LayoutsRepository private function clearFrontLayoutsCache(): void { - if (!class_exists('\CacheHandler')) { + if (!class_exists('\Shared\Cache\CacheHandler')) { return; } try { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); if (method_exists($cacheHandler, 'deletePattern')) { $cacheHandler->deletePattern('*Layouts::*'); } diff --git a/autoload/Domain/Product/ProductRepository.php b/autoload/Domain/Product/ProductRepository.php index 128d5ba..79cd2aa 100644 --- a/autoload/Domain/Product/ProductRepository.php +++ b/autoload/Domain/Product/ProductRepository.php @@ -639,7 +639,7 @@ class ProductRepository \S::delete_dir( '../thumbs/' ); if ( !$isNew ) { - $redis = \RedisConnection::getInstance()->getConnection(); + $redis = \Shared\Cache\RedisConnection::getInstance()->getConnection(); if ( $redis ) { $redis->flushAll(); } diff --git a/autoload/Domain/Promotion/PromotionRepository.php b/autoload/Domain/Promotion/PromotionRepository.php index 41d7239..7479a9c 100644 --- a/autoload/Domain/Promotion/PromotionRepository.php +++ b/autoload/Domain/Promotion/PromotionRepository.php @@ -406,12 +406,12 @@ class PromotionRepository private function invalidateActivePromotionsCache(): void { - if (!class_exists('\CacheHandler')) { + if (!class_exists('\Shared\Cache\CacheHandler')) { return; } try { - $cache = new \CacheHandler(); + $cache = new \Shared\Cache\CacheHandler(); if (method_exists($cache, 'delete')) { $cache->delete('\shop\Promotion::get_active_promotions'); } diff --git a/autoload/Domain/Scontainers/ScontainersRepository.php b/autoload/Domain/Scontainers/ScontainersRepository.php index bc94b33..843ddbd 100644 --- a/autoload/Domain/Scontainers/ScontainersRepository.php +++ b/autoload/Domain/Scontainers/ScontainersRepository.php @@ -215,11 +215,11 @@ class ScontainersRepository private function clearFrontCache(int $containerId): void { - if ($containerId <= 0 || !class_exists('\CacheHandler')) { + if ($containerId <= 0 || !class_exists('\Shared\Cache\CacheHandler')) { return; } - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = '\front\factory\Scontainers::scontainer_details:' . $containerId; $cacheHandler->delete($cacheKey); } diff --git a/autoload/Domain/Settings/SettingsRepository.php b/autoload/Domain/Settings/SettingsRepository.php index da559c4..ab21ce7 100644 --- a/autoload/Domain/Settings/SettingsRepository.php +++ b/autoload/Domain/Settings/SettingsRepository.php @@ -148,7 +148,7 @@ class SettingsRepository */ public function allSettings(bool $skipCache = false): array { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = 'Domain\Settings\SettingsRepository::allSettings'; if (!$skipCache) { @@ -177,7 +177,7 @@ class SettingsRepository */ public function getSingleValue(string $param): string { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "Domain\Settings\SettingsRepository::getSingleValue:$param"; $objectData = $cacheHandler->get($cacheKey); diff --git a/autoload/class.CacheHandler.php b/autoload/Shared/Cache/CacheHandler.php similarity index 86% rename from autoload/class.CacheHandler.php rename to autoload/Shared/Cache/CacheHandler.php index 20ccdd6..a79bb94 100644 --- a/autoload/class.CacheHandler.php +++ b/autoload/Shared/Cache/CacheHandler.php @@ -1,4 +1,6 @@ -redis = \RedisConnection::getInstance()->getConnection(); + $this->redis = RedisConnection::getInstance()->getConnection(); } catch (\Exception $e) { $this->redis = null; } @@ -22,7 +24,7 @@ class CacheHandler return null; } - public function set($key, $value, $ttl = 86400) // 86400 = 60 * 60 * 24 (1 dzień) + public function set($key, $value, $ttl = 86400) { if ($this->redis) { $this->redis->setex($key, $ttl, serialize($value)); diff --git a/autoload/Shared/Cache/RedisConnection.php b/autoload/Shared/Cache/RedisConnection.php new file mode 100644 index 0000000..3a6c17b --- /dev/null +++ b/autoload/Shared/Cache/RedisConnection.php @@ -0,0 +1,45 @@ +redis = new \Redis(); + + try { + if (!$this->redis->connect($config['redis']['host'], $config['redis']['port'])) { + error_log("Nie udalo sie polaczyc z serwerem Redis."); + $this->redis = null; + return; + } + + if (!$this->redis->auth($config['redis']['password'])) { + error_log("Autoryzacja do serwera Redis nie powiodla sie."); + $this->redis = null; + return; + } + } catch (\Exception $e) { + error_log("Blad podczas polaczenia z Redis: " . $e->getMessage()); + $this->redis = null; + } + } + + public static function getInstance() + { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + public function getConnection() + { + return $this->redis; + } +} diff --git a/autoload/admin/Controllers/SettingsController.php b/autoload/admin/Controllers/SettingsController.php index 16abd22..da0c2eb 100644 --- a/autoload/admin/Controllers/SettingsController.php +++ b/autoload/admin/Controllers/SettingsController.php @@ -33,7 +33,7 @@ class SettingsController \S::delete_dir('../temp/'); \S::delete_dir('../thumbs/'); - $redis = \RedisConnection::getInstance()->getConnection(); + $redis = \Shared\Cache\RedisConnection::getInstance()->getConnection(); if ($redis) { $redis->flushAll(); } @@ -54,7 +54,7 @@ class SettingsController \S::delete_dir('../temp/'); \S::delete_dir('../thumbs/'); - $redis = \RedisConnection::getInstance()->getConnection(); + $redis = \Shared\Cache\RedisConnection::getInstance()->getConnection(); if ($redis) { $redis->flushAll(); } diff --git a/autoload/class.Cache.php b/autoload/class.Cache.php deleted file mode 100644 index e3f76c4..0000000 --- a/autoload/class.Cache.php +++ /dev/null @@ -1,57 +0,0 @@ - $data[0] ) - { - if ( file_exists( $filename ) ) - unlink( $filename ); - return false; - } - return $data[1]; - } -} -?> \ No newline at end of file diff --git a/autoload/class.RedisConnection.php b/autoload/class.RedisConnection.php deleted file mode 100644 index e4cbfa9..0000000 --- a/autoload/class.RedisConnection.php +++ /dev/null @@ -1,53 +0,0 @@ -redis = new \Redis(); - - try - { - // Próba połączenia z serwerem Redis - if (!$this->redis->connect($config['redis']['host'], $config['redis']['port'])) - { - // Logowanie błędu bez rzucania wyjątku - error_log("Nie udało się połączyć z serwerem Redis."); - $this->redis = null; - return; - } - - // Próba autoryzacji - if (!$this->redis->auth($config['redis']['password'])) - { - error_log("Autoryzacja do serwera Redis nie powiodła się."); - $this->redis = null; - return; - } - } - catch (\Exception $e) - { - // Obsługa wyjątków, bez rzucania błędu - error_log("Błąd podczas połączenia z Redis: " . $e->getMessage()); - $this->redis = null; - } - } - - public static function getInstance() - { - if (self::$instance === null) - { - self::$instance = new self(); - } - return self::$instance; - } - - public function getConnection() - { - return $this->redis; - } -} \ No newline at end of file diff --git a/autoload/class.S.php b/autoload/class.S.php index 17e7531..5d050f9 100644 --- a/autoload/class.S.php +++ b/autoload/class.S.php @@ -105,7 +105,7 @@ class S static public function clear_redis_cache() { - $redis = \RedisConnection::getInstance() -> getConnection(); + $redis = \Shared\Cache\RedisConnection::getInstance() -> getConnection(); $redis -> flushAll(); } @@ -115,7 +115,7 @@ class S { try { - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); // Wyczyść cache produktu dla wszystkich języków i permutacji $cacheHandler -> deletePattern( "shop\\product:$product_id:*" ); // Wyczyść cache związane z opcjami ilościowymi diff --git a/autoload/front/Views/Banners.php b/autoload/front/Views/Banners.php new file mode 100644 index 0000000..d60881e --- /dev/null +++ b/autoload/front/Views/Banners.php @@ -0,0 +1,21 @@ +banners = $banners; + return $tpl->render('banner/banners'); + } + + public static function mainBanner($banner) + { + if (!\S::get_session('banner_close') && is_array($banner)) { + $tpl = new \Tpl; + $tpl->banner = $banner; + return $tpl->render('banner/main-banner'); + } + } +} diff --git a/autoload/front/controls/class.ShopBasket.php b/autoload/front/controls/class.ShopBasket.php index 80b2f07..4aa90b1 100644 --- a/autoload/front/controls/class.ShopBasket.php +++ b/autoload/front/controls/class.ShopBasket.php @@ -403,7 +403,7 @@ class ShopBasket \S::set_session( 'google-analytics-purchase', true ); \S::set_session( 'ekomi-purchase', true ); - $redis = \RedisConnection::getInstance() -> getConnection(); + $redis = \Shared\Cache\RedisConnection::getInstance() -> getConnection(); if ( $redis ) $redis -> flushAll(); diff --git a/autoload/front/factory/class.Banners.php b/autoload/front/factory/class.Banners.php deleted file mode 100644 index f40417b..0000000 --- a/autoload/front/factory/class.Banners.php +++ /dev/null @@ -1,73 +0,0 @@ -get($cacheKey); - - if ( !$objectData ) - { - $results = $mdb -> query( 'SELECT id, name FROM pp_banners WHERE status = 1 AND ( date_start <= \'' . date( 'Y-m-d' ) . '\' OR date_start IS NULL ) AND ( date_end >= \'' . date( 'Y-m-d' ) . '\' OR date_end IS NULL ) AND home_page = 0' ) -> fetchAll(); - if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row ) - { - $row['languages'] = $mdb -> get( 'pp_banners_langs', '*', [ 'AND' => [ 'id_banner' => (int)$row['id'], 'id_lang' => $lang[0] ] ] ); - $banners[] = $row; - } - - $cacheHandler -> set( $cacheKey, $banners ); - } - else - { - return unserialize($objectData); - } - - return $banners; - } - - public static function main_banner() - { - global $mdb, $lang_id; - - $cacheHandler = new \CacheHandler(); - $cacheKey = "\front\factory\Banners::main_banner:$lang_id"; - - $objectData = $cacheHandler -> get( $cacheKey ); - - if ( !$objectData ) - { - $banner = $mdb -> query( 'SELECT ' - . '* ' - . 'FROM ' - . 'pp_banners ' - . 'WHERE ' - . 'status = 1 ' - . 'AND ' - . '( date_start <= \'' . date( 'Y-m-d' ) . '\' OR date_start IS NULL ) ' - . 'AND ' - . '( date_end >= \'' . date( 'Y-m-d' ) . '\' OR date_end IS NULL ) ' - . 'AND ' - . 'home_page = 1 ' - . 'ORDER BY ' - . 'date_end ASC ' - . 'LIMIT 1' ) -> fetchAll(); - $banner = $banner[0]; - if ( $banner ) - $banner['languages'] = $mdb -> get( 'pp_banners_langs', '*', [ 'AND' => [ 'id_banner' => (int)$banner['id'], 'id_lang' => $lang_id ] ] ); - - $cacheHandler -> set( $cacheKey, $banner ); - } - else - { - return unserialize( $objectData ); - } - - return $banner; - } -} \ No newline at end of file diff --git a/autoload/front/factory/class.Layouts.php b/autoload/front/factory/class.Layouts.php index 5454e2f..30f06a3 100644 --- a/autoload/front/factory/class.Layouts.php +++ b/autoload/front/factory/class.Layouts.php @@ -12,7 +12,7 @@ class Layouts { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Layouts::default_layout"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -35,7 +35,7 @@ class Layouts { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Layouts::product_layout:$product_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -87,7 +87,7 @@ class Layouts { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Layouts::article_layout:$article_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -110,7 +110,7 @@ class Layouts { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Layouts::category_layout:$category_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -148,7 +148,7 @@ class Layouts { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Layouts::active_layout:$page_id"; $objectData = $cacheHandler -> get( $cacheKey ); diff --git a/autoload/front/factory/class.Menu.php b/autoload/front/factory/class.Menu.php index eba3f15..f1f8fb6 100644 --- a/autoload/front/factory/class.Menu.php +++ b/autoload/front/factory/class.Menu.php @@ -7,7 +7,7 @@ class Menu { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Menu::menu_details:$menu_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -31,7 +31,7 @@ class Menu { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Menu::menu_pages:$menu_id:$parent_id"; $objectData = $cacheHandler->get($cacheKey); diff --git a/autoload/front/factory/class.Pages.php b/autoload/front/factory/class.Pages.php index 920d0fe..79a7ea5 100644 --- a/autoload/front/factory/class.Pages.php +++ b/autoload/front/factory/class.Pages.php @@ -7,7 +7,7 @@ class Pages { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Pages::page_sort:$page_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -48,7 +48,7 @@ class Pages if ( $lang_tmp ) $lang_id = $lang_tmp; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Pages::page_details:$id:$lang_id"; $objectData = $cacheHandler->get($cacheKey); @@ -69,7 +69,7 @@ class Pages { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Pages::main_page_id"; $objectData = $cacheHandler->get($cacheKey); diff --git a/autoload/front/factory/class.Scontainers.php b/autoload/front/factory/class.Scontainers.php index c3a7c1f..675282b 100644 --- a/autoload/front/factory/class.Scontainers.php +++ b/autoload/front/factory/class.Scontainers.php @@ -7,7 +7,7 @@ class Scontainers { global $mdb, $lang; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\Scontainers::scontainer_details:$scontainer_id"; $objectData = $cacheHandler->get($cacheKey); diff --git a/autoload/front/factory/class.ShopAttribute.php b/autoload/front/factory/class.ShopAttribute.php index 8755742..858fa47 100644 --- a/autoload/front/factory/class.ShopAttribute.php +++ b/autoload/front/factory/class.ShopAttribute.php @@ -7,7 +7,7 @@ class ShopAttribute { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\ShopAttribute::value_details:$value_id:$lang_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -31,12 +31,20 @@ class ShopAttribute { global $mdb; - if ( !$attribute = \Cache::fetch( 'attribute_details_' . $attribute_id . '_' . $lang_id ) ) + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = 'attribute_details_' . $attribute_id . '_' . $lang_id; + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) { $attribute = $mdb -> get( 'pp_shop_attributes', '*', [ 'id' => (int)$attribute_id ] ); $attribute['language'] = $mdb -> get( 'pp_shop_attributes_langs', [ 'lang_id', 'name' ], [ 'AND' => [ 'attribute_id' => (int)$attribute_id, 'lang_id' => $lang_id ] ] ); - \Cache::store( 'attribute_details_' . $attribute_id . '_' . $lang_id, $attribute ); + $cacheHandler->set( $cacheKey, $attribute ); + } + else + { + return unserialize( $objectData ); } return $attribute; } diff --git a/autoload/front/factory/class.ShopCategory.php b/autoload/front/factory/class.ShopCategory.php index 0cf6fd4..008f75e 100644 --- a/autoload/front/factory/class.ShopCategory.php +++ b/autoload/front/factory/class.ShopCategory.php @@ -6,10 +6,18 @@ class ShopCategory { global $mdb; - if ( !$category_sort = \Cache::fetch( "get_category_sort:$category_id" ) ) + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "get_category_sort:$category_id"; + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) { $category_sort = $mdb -> get( 'pp_shop_categories', 'sort_type', [ 'id' => $category_id ] ); - \Cache::store( "get_category_sort:$category_id", $category_sort ); + $cacheHandler->set( $cacheKey, $category_sort ); + } + else + { + return unserialize( $objectData ); } return $category_sort; @@ -19,11 +27,19 @@ class ShopCategory { global $mdb, $lang_id; - if ( !$category_name = \Cache::fetch( 'category_name' . $lang_id . '_' . $category_id . 'tmp' ) ) + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = 'category_name' . $lang_id . '_' . $category_id; + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) { $category_name = $mdb -> get( 'pp_shop_categories_langs', 'title', [ 'AND' => [ 'category_id' => (int)$category_id, 'lang_id' => $lang_id ] ] ); - \Cache::store( 'category_name' . $lang_id . '_' . $category_id, $category_name ); + $cacheHandler->set( $cacheKey, $category_name ); + } + else + { + return unserialize( $objectData ); } return $category_name; @@ -45,7 +61,7 @@ class ShopCategory { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\ShopCategory::blog_category_products:$category_id:$lang_id:$limit"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -95,7 +111,7 @@ class ShopCategory { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\ShopCategory::category_products_count:$category_id:$lang_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -166,7 +182,7 @@ class ShopCategory break; endswitch; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\ShopCategory::products_id:$category_id:$sort_type:$lang_id:$products_limit:$from:$order"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -245,7 +261,7 @@ class ShopCategory { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\ShopCategory::categories_details:$parent_id"; $objectData = $cacheHandler->get($cacheKey); @@ -275,7 +291,7 @@ class ShopCategory { global $mdb, $lang_id; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\ShopCategory::category_details:$category_id"; $objectData = $cacheHandler->get($cacheKey); diff --git a/autoload/front/factory/class.ShopPaymentMethod.php b/autoload/front/factory/class.ShopPaymentMethod.php index ffd774e..d8b0957 100644 --- a/autoload/front/factory/class.ShopPaymentMethod.php +++ b/autoload/front/factory/class.ShopPaymentMethod.php @@ -17,15 +17,16 @@ class ShopPaymentMethod public static function payment_methods_by_transport( $transport_method_id ) { $transport_method_id = (int)$transport_method_id; + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = 'payment_methods_by_transport' . $transport_method_id; - $payments = \Cache::fetch( $cacheKey ); + $objectData = $cacheHandler->get( $cacheKey ); - if ( $payments !== false && is_array( $payments ) ) { - return $payments; + if ( $objectData ) { + return unserialize( $objectData ); } $payments = self::repo()->forTransport( $transport_method_id ); - \Cache::store( $cacheKey, $payments ); + $cacheHandler->set( $cacheKey, $payments ); return $payments; } @@ -38,12 +39,18 @@ class ShopPaymentMethod public static function payment_method( $payment_method_id ) { $payment_method_id = (int)$payment_method_id; + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = 'payment_method' . $payment_method_id; - $payment_method = \Cache::fetch( $cacheKey ); + $objectData = $cacheHandler->get( $cacheKey ); - if ( $payment_method === false ) { + if ( !$objectData ) + { $payment_method = self::repo()->findActiveById( $payment_method_id ); - \Cache::store( $cacheKey, $payment_method ); + $cacheHandler->set( $cacheKey, $payment_method ); + } + else + { + return unserialize( $objectData ); } return $payment_method; @@ -51,15 +58,16 @@ class ShopPaymentMethod public static function payment_methods() { + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = 'payment_methods'; - $payment_methods = \Cache::fetch( $cacheKey ); + $objectData = $cacheHandler->get( $cacheKey ); - if ( $payment_methods !== false && is_array( $payment_methods ) ) { - return $payment_methods; + if ( $objectData ) { + return unserialize( $objectData ); } $payment_methods = self::repo()->allActive(); - \Cache::store( $cacheKey, $payment_methods ); + $cacheHandler->set( $cacheKey, $payment_methods ); return $payment_methods; } diff --git a/autoload/front/factory/class.ShopProduct.php b/autoload/front/factory/class.ShopProduct.php index ee07173..bd59b15 100644 --- a/autoload/front/factory/class.ShopProduct.php +++ b/autoload/front/factory/class.ShopProduct.php @@ -47,7 +47,7 @@ class ShopProduct { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\ShopProduct::is_product_active:$product_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -85,10 +85,18 @@ class ShopProduct { global $mdb; - if ( !$price = \Cache::fetch( 'get_minimal_price:' . $id_product ) ) + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = 'get_minimal_price:' . $id_product; + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) { $price = $mdb -> min( 'pp_shop_product_price_history', 'price', [ 'AND' => [ 'id_product' => $id_product, 'price[!]' => str_replace( ',', '.', $price_brutto_promo ) ] ] ); - \Cache::store( 'get_minimal_price:' . $id_product, $price ); + $cacheHandler->set( $cacheKey, $price ); + } + else + { + return unserialize( $objectData ); } return $price; @@ -108,11 +116,19 @@ class ShopProduct { global $mdb, $lang_id; - if ( !$product_name = \Cache::fetch( 'product_name' . $lang_id . '_' . $product_id ) ) + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = 'product_name' . $lang_id . '_' . $product_id; + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) { $product_name = $mdb -> get( 'pp_shop_products_langs', 'name', [ 'AND' => [ 'product_id' => (int)$product_id, 'lang_id' => $lang_id ] ] ); - \Cache::store( 'product_name' . $lang_id . '_' . $product_id, $product_name ); + $cacheHandler->set( $cacheKey, $product_name ); + } + else + { + return unserialize( $objectData ); } return $product_name; @@ -122,12 +138,20 @@ class ShopProduct { global $mdb; - if ( !$product_image = \Cache::fetch( 'product_image:' . $product_id ) ) + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = 'product_image:' . $product_id; + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) { $results = $mdb -> query( 'SELECT src FROM pp_shop_products_images WHERE product_id = :product_id ORDER BY o ASC LIMIT 1', [ ':product_id' => (int)$product_id ] ) -> fetchAll( \PDO::FETCH_ASSOC ); $product_image = $results[ 0 ][ 'src' ]; - \Cache::store( 'product_image:' . $product_id, $product_image ); + $cacheHandler->set( $cacheKey, $product_image ); + } + else + { + return unserialize( $objectData ); } return $product_image; @@ -137,7 +161,7 @@ class ShopProduct { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\ShopProduct::product_wp:$product_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -159,14 +183,22 @@ class ShopProduct { global $mdb; - if ( !$products = \Cache::fetch( 'random_productsa_' . $product_id . '_' . $lang_id ) ) + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = 'random_products_' . $product_id . '_' . $lang_id; + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) { $results = $mdb -> query( 'SELECT id FROM pp_shop_products WHERE status = 1 ORDER BY RAND() LIMIT 6' ) -> fetchAll(); if ( is_array( $results ) and!empty( $results ) ) foreach ( $results as $row ) $products[] = \front\factory\ShopProduct::product_details( $row[ 'id' ], $lang_id ); - \Cache::store( 'random_products_' . $product_id . '_' . $lang_id, $products ); + $cacheHandler->set( $cacheKey, $products ); + } + else + { + return unserialize( $objectData ); } return $products; @@ -176,14 +208,22 @@ class ShopProduct { global $mdb; - if ( !$products = \Cache::fetch( "promoted_products-$limit" ) ) + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "promoted_products-$limit"; + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) { $results = $mdb -> query( 'SELECT id FROM pp_shop_products WHERE status = 1 AND promoted = 1 ORDER BY RAND() LIMIT ' . $limit ) -> fetchAll(); if ( is_array( $results ) and!empty( $results ) ) foreach ( $results as $row ) $products[] = $row[ 'id' ]; - \Cache::store( "promoted_products-$limit", $products ); + $cacheHandler->set( $cacheKey, $products ); + } + else + { + return unserialize( $objectData ); } return $products; @@ -227,7 +267,7 @@ class ShopProduct if ( !$product_id ) return false; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\ShopProduct::product_details:$product_id:$lang_id"; $objectData = $cacheHandler->get($cacheKey); diff --git a/autoload/front/factory/class.ShopTransport.php b/autoload/front/factory/class.ShopTransport.php index 2b41a87..6d404e1 100644 --- a/autoload/front/factory/class.ShopTransport.php +++ b/autoload/front/factory/class.ShopTransport.php @@ -13,7 +13,7 @@ class ShopTransport { global $mdb, $settings; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\front\factory\ShopTransport::transport_methods"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -57,12 +57,20 @@ class ShopTransport { global $mdb; - if ( !$cost = \Cache::fetch( 'transport_cost_' . $transport_id ) ) + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = 'transport_cost_' . $transport_id; + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) { $repo = new \Domain\Transport\TransportRepository($mdb); $cost = $repo->getTransportCost($transport_id); - \Cache::store( 'transport_cost_' . $transport_id, $cost ); + $cacheHandler->set( $cacheKey, $cost ); + } + else + { + return unserialize( $objectData ); } return $cost; } @@ -71,12 +79,20 @@ class ShopTransport { global $mdb; - if ( !$transport = \Cache::fetch( 'transport' . $transport_id ) ) + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = 'transport' . $transport_id; + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) { $repo = new \Domain\Transport\TransportRepository($mdb); $transport = $repo->findActiveById($transport_id); - \Cache::store( 'transport' . $transport_id, $transport ); + $cacheHandler->set( $cacheKey, $transport ); + } + else + { + return unserialize( $objectData ); } return $transport; } diff --git a/autoload/front/view/class.Banners.php b/autoload/front/view/class.Banners.php deleted file mode 100644 index 7c7d02e..0000000 --- a/autoload/front/view/class.Banners.php +++ /dev/null @@ -1,22 +0,0 @@ - banners = $banners; - return $tpl -> render( 'banner/banners' ); - } - - public static function main_banner( $banner ) - { - if ( !\S::get_session( 'banner_close' ) && is_array( $banner ) ) - { - $tpl = new \Tpl; - $tpl -> banner = $banner; - return $tpl -> render( 'banner/main-banner' ); - } - } -} diff --git a/autoload/front/view/class.Site.php b/autoload/front/view/class.Site.php index 9e5b4a2..45226f2 100644 --- a/autoload/front/view/class.Site.php +++ b/autoload/front/view/class.Site.php @@ -23,6 +23,7 @@ class Site global $page, $settings, $settings, $lang, $lang_id; $articleRepo = new \Domain\Article\ArticleRepository( $GLOBALS['mdb'] ); + $bannerRepo = new \Domain\Banner\BannerRepository( $GLOBALS['mdb'] ); if ( (int) \S::get( 'layout_id' ) ) $layout = new \cms\Layout( (int) \S::get( 'layout_id' ) ); @@ -59,8 +60,8 @@ class Site \front\view\Site::copyright(), $html ); - $html = str_replace( '[BANER_STRONA_GLOWNA]', \front\view\Banners::main_banner( \front\factory\Banners::main_banner() ), $html ); - $html = str_replace( '[BANERY]', \front\view\Banners::banners( \front\factory\Banners::banners() ), $html ); + $html = str_replace( '[BANER_STRONA_GLOWNA]', \front\Views\Banners::mainBanner( $bannerRepo->mainBanner( $lang_id ) ), $html ); + $html = str_replace( '[BANERY]', \front\Views\Banners::banners( $bannerRepo->banners( $lang_id ) ), $html ); $html = str_replace( '[KATEGORIE]', \Tpl::view( 'shop-category/categories', [ 'level' => $level, diff --git a/autoload/shop/class.Category.php b/autoload/shop/class.Category.php index 4158a9e..d69046e 100644 --- a/autoload/shop/class.Category.php +++ b/autoload/shop/class.Category.php @@ -59,7 +59,7 @@ class Category implements \ArrayAccess { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\shop\Category::get_subcategory_by_category:$category_id"; $objectData = $cacheHandler -> get( $cacheKey ); diff --git a/autoload/shop/class.Product.php b/autoload/shop/class.Product.php index 1f27100..464d299 100644 --- a/autoload/shop/class.Product.php +++ b/autoload/shop/class.Product.php @@ -126,7 +126,7 @@ class Product implements \ArrayAccess try { // Get the Redis connection instance - $redis = \RedisConnection::getInstance()->getConnection(); + $redis = \Shared\Cache\RedisConnection::getInstance()->getConnection(); // Check if Redis connection is valid if ( $redis ) @@ -303,7 +303,7 @@ class Product implements \ArrayAccess { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\shop\Product::product_sets_when_add_to_basket:$product_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -536,7 +536,7 @@ class Product implements \ArrayAccess { global $mdb, $settings; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\shop\Product::get_product_permutation_quantity_options:v2:$product_id:$permutation"; $objectData = $cacheHandler -> get( $cacheKey ); diff --git a/autoload/shop/class.ProductAttribute.php b/autoload/shop/class.ProductAttribute.php index d000fea..7ff49b1 100644 --- a/autoload/shop/class.ProductAttribute.php +++ b/autoload/shop/class.ProductAttribute.php @@ -8,7 +8,7 @@ class ProductAttribute implements \ArrayAccess { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\shop\ProductAttribute::is_value_default:$value_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -63,7 +63,7 @@ class ProductAttribute implements \ArrayAccess { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\shop\ProductAttribute::get_attribute_order:$attribute_id"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -93,7 +93,7 @@ class ProductAttribute implements \ArrayAccess { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\shop\ProductAttribute::get_value_name:$value_id:$lang_id"; $objectData = $cacheHandler -> get( $cacheKey ); diff --git a/autoload/shop/class.ProductCustomField.php b/autoload/shop/class.ProductCustomField.php index 4ffaa64..a7da2bc 100644 --- a/autoload/shop/class.ProductCustomField.php +++ b/autoload/shop/class.ProductCustomField.php @@ -19,7 +19,7 @@ class ProductCustomField implements \ArrayAccess { try { - $redis = \RedisConnection::getInstance() -> getConnection(); + $redis = \Shared\Cache\RedisConnection::getInstance() -> getConnection(); if ( $redis ) { diff --git a/autoload/shop/class.Promotion.php b/autoload/shop/class.Promotion.php index 17655dd..6895ef6 100644 --- a/autoload/shop/class.Promotion.php +++ b/autoload/shop/class.Promotion.php @@ -21,7 +21,7 @@ class Promotion extends DbModel { global $mdb; - $cacheHandler = new \CacheHandler(); + $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "\shop\Promotion::get_active_promotions"; $objectData = $cacheHandler -> get( $cacheKey ); diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9510f3f..dae77c0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,38 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. --- +## ver. 0.282 (2026-02-16) - Cache cleanup, Shared namespace + +- **Shared\Cache namespace** — przeniesienie CacheHandler i RedisConnection do `Shared\Cache\` + - NOWY: `autoload/Shared/Cache/CacheHandler.php` — glowna implementacja + - NOWY: `autoload/Shared/Cache/RedisConnection.php` — glowna implementacja singleton + - USUNIETA: `autoload/class.CacheHandler.php` — wrapper usuniety, 60 odwolan przepietych na `\Shared\Cache\CacheHandler` + - USUNIETA: `autoload/class.RedisConnection.php` — wrapper usuniety, 12 odwolan przepietych na `\Shared\Cache\RedisConnection` +- **Eliminacja legacy Cache** — usuniecie plikowego cache `class.Cache.php` + - USUNIETA: `autoload/class.Cache.php` — legacy file-based cache (store/fetch z base64+serialize) + - UPDATE: `front\factory\ShopProduct` — 5 metod przepietych z `\Cache::` na `CacheHandler` (get_minimal_price, product_name, product_image, random_products, promoted_products) + - UPDATE: `front\factory\ShopPaymentMethod` — 3 metody przepiete (payment_methods_by_transport, payment_method, payment_methods) + - UPDATE: `front\factory\ShopCategory` — 2 metody przepiete (get_category_sort, category_name) + - UPDATE: `front\factory\ShopTransport` — 2 metody przepiete (transport_cost, transport) + - UPDATE: `front\factory\ShopAttribute` — 1 metoda przepieta (attribute_details) + - UPDATE: `Domain\Dictionaries\DictionariesRepository` — prywatne wrappery cacheFetch/cacheStore przepiete na CacheHandler + - FIX: naprawione rozbieznosci kluczy cache (random_products, category_name) — fetch i store uzywaly roznych kluczy + - Testy: 454 OK, 1449 asercji + +--- + +## ver. 0.281 (2026-02-16) - Banners frontend migration + +- **Banners (frontend)** — migracja na Domain + Views + - NOWE METODY w `BannerRepository`: `banners(string $langId)`, `mainBanner(string $langId)` — z Redis cache, filtrowanie dat, plaski format `$banner['languages']` (zgodny z szablonami) + - NOWY: `front\Views\Banners` — czysty VIEW (`banners()`, `mainBanner()`) + - USUNIETA: `front\factory\Banners` — logika przeniesiona do `BannerRepository` + - USUNIETA: `front\view\Banners` — zastapiona przez `front\Views\Banners` + - UPDATE: `front\view\Site::show()` — przepiecie linii 62-63 na repo + Views (bezposrednio `$bannerRepo->banners()` / `$bannerRepo->mainBanner()`) + - Testy: 454 OK, 1449 asercji (+4 nowe testy w BannerRepositoryTest) + +--- + ## ver. 0.280 (2026-02-16) - Articles frontend migration - **Articles (frontend)** — pelna migracja na Domain + Views diff --git a/docs/DATABASE_STRUCTURE.md b/docs/DATABASE_STRUCTURE.md index 2547590..c5fa7d0 100644 --- a/docs/DATABASE_STRUCTURE.md +++ b/docs/DATABASE_STRUCTURE.md @@ -129,7 +129,9 @@ Banery. | date_end | Data zakończenia | | home_page | Czy na stronie głównej 0/1 | -**Używane w:** `Domain\Banner\BannerRepository` +**Używane w:** `Domain\Banner\BannerRepository`, `front\Views\Banners` + +**Aktualizacja 2026-02-16 (ver. 0.281):** metody frontendowe `banners()`, `mainBanner()` dodane do `Domain\Banner\BannerRepository`. Fasady `front\factory\Banners` i `front\view\Banners` deleguja do repo/Views. ## pp_banners_langs Tłumaczenia banerów. @@ -144,7 +146,7 @@ Tłumaczenia banerów. | html | Kod HTML | | text | Tekst | -**Używane w:** `Domain\Banner\BannerRepository` +**Używane w:** `Domain\Banner\BannerRepository`, `front\Views\Banners` ## pp_articles Artykuły. diff --git a/docs/FRONTEND_REFACTORING_PLAN.md b/docs/FRONTEND_REFACTORING_PLAN.md index 1e7c5f4..0537e9f 100644 --- a/docs/FRONTEND_REFACTORING_PLAN.md +++ b/docs/FRONTEND_REFACTORING_PLAN.md @@ -40,7 +40,7 @@ Panel administratora (33 moduły) został w pełni zmigrowany na architekturę D | Settings | Fasada (BUG: get_single_settings_value ignoruje $param) | NISKI | | Languages | USUNIĘTA — przepięta na Domain | — | | Layouts | Fasada | NISKI | -| Banners | Fasada | NISKI | +| Banners | USUNIETA — przepieta na Domain | — | | Menu | Fasada | NISKI | | Pages | Fasada | NISKI | | ShopAttribute | Fasada | NISKI | @@ -51,7 +51,8 @@ Panel administratora (33 moduły) został w pełni zmigrowany na architekturę D |-------|--------| | Site | KRYTYCZNY — show() ~600 linii, pattern substitution engine | | ShopCategory | VIEW z logiką routingu (infinite scroll vs pagination) | -| Articles, Banners, Menu, Scontainers | Czyste VIEW | +| Articles, Menu, Scontainers | Czyste VIEW | +| Banners | PRZENIESIONA do `front\Views\Banners` | | Languages, Newsletter | PRZENIESIONE do `front\Views\` (nowy namespace) | | ShopClient, ShopOrder, ShopPaymentMethod | Czyste VIEW | | ShopTransport | PUSTA klasa (placeholder) | @@ -79,12 +80,12 @@ Panel administratora (33 moduły) został w pełni zmigrowany na architekturę D |-------|-------|--------| | S | ~1130 | htacces() ~500 linii — generowanie .htaccess; reszta to utility | | Tpl | ~90 | OK — silnik szablonów, bez zmian | -| CacheHandler | ~50 | OK — Redis wrapper | -| RedisConnection | ~40 | OK — singleton | +| CacheHandler | ~50 | ZMIGROWANY do `Shared\Cache\CacheHandler` — wrappery usuniete | +| RedisConnection | ~40 | ZMIGROWANY do `Shared\Cache\RedisConnection` — wrappery usuniete | | Email | ~100 | OK — PHPMailer wrapper (drobne poprawki) | | Log | ~20 | OK — audit logging | | DbModel | ~60 | OK — base ORM | -| Cache | ~50 | LEGACY — file-based cache, rozważyć usunięcie | +| Cache | ~50 | USUNIETA — zastapiona CacheHandler (Redis) w ver. 0.282 | | Html | ~80 | OK — form helpers | | Image | ~100 | OK — GD wrapper | | Mobile_Detect | — | Third-party, bez zmian | @@ -246,19 +247,37 @@ Legacy Cleanup --- -### Etap: Banners, Menu, Pages, Layouts Frontend Services +### Etap: Banners Frontend — ZREALIZOWANY + +**Cel:** Migracja `front\factory\Banners` i `front\view\Banners` do Domain + Views. + +**DODANE METODY (do istniejącej klasy `BannerRepository`):** +- `banners(string $langId): ?array` — aktywne banery (home_page=0), filtrowanie dat, Redis cache, plaski format languages +- `mainBanner(string $langId): ?array` — baner glowny (home_page=1), filtrowanie dat, Redis cache, plaski format languages + +**NOWE:** +- `front\Views\Banners` — czysty VIEW (`banners()`, `mainBanner()`) + +**ZMIANA:** +- `front\factory\Banners` → USUNIETA (logika przeniesiona do `BannerRepository`) +- `front\view\Banners` → USUNIETA (zastapiona przez `front\Views\Banners`) +- `front\view\Site::show()` — przepiecie na `$bannerRepo->banners()` / `$bannerRepo->mainBanner()` + `\front\Views\Banners::` +- Testy: 4 nowe w `BannerRepositoryTest` (454 OK, 1449 asercji) + +--- + +### Etap: Menu, Pages, Layouts Frontend Services **Cel:** Migracja pozostałych fabryk "liściowych". **NOWE:** -- `Domain/Banner/BannerFrontendService.php` — `mainBanner()`, `banners()` (filtrowanie po datach) - `Domain/Menu/MenuFrontendService.php` — `menuDetails()`, `menuPages()` (rekurencja) - `Domain/Pages/PagesFrontendService.php` — `pageDetails()`, `mainPageId()`, `langUrl()`, `pageSort()` - `Domain/Layouts/LayoutsFrontendService.php` — `activeLayout()`, `articleLayout()`, `productLayout()`, `categoryLayout()`, `defaultLayout()`, `categoryDefaultLayout()` -- Testy: 4 pliki testowe +- Testy: 3 pliki testowe **ZMIANA:** -- `front/factory/Banners`, `Menu`, `Pages`, `Layouts` → fasady +- `front/factory/Menu`, `Pages`, `Layouts` → fasady **BUG FIX:** `cms\Layout::__get()` — poprawka referencji do `$this->data` @@ -485,7 +504,7 @@ front\factory\ShopPromotion::promotion_type_XX() → shop\Product::is_product_on **USUNIĘCIE:** - Martwy kod `eval()` dla `[PHP]` bloków (jeśli nieużywany) -- `class.Cache.php` (legacy file-based) jeśli wszystkie użycia przeniesione na `CacheHandler` +- ~~`class.Cache.php` (legacy file-based)~~ **ZREALIZOWANE** w ver. 0.282 - Pusta klasa `front\view\ShopTransport` **ZMIANA:** diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index e7631e9..4e620fa 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -6,23 +6,27 @@ Dokumentacja struktury projektu shopPRO do szybkiego odniesienia. ### Klasy odpowiedzialne za cache -#### RedisConnection -- **Plik:** `autoload/class.RedisConnection.php` +#### RedisConnection (`Shared\Cache\RedisConnection`) +- **Plik:** `autoload/Shared/Cache/RedisConnection.php` - **Opis:** Singleton zarządzający połączeniem z Redis - **Metody:** - `getInstance()` - pobiera instancję połączenia - `getConnection()` - zwraca obiekt Redis -#### CacheHandler -- **Plik:** `autoload/class.CacheHandler.php` +#### CacheHandler (`Shared\Cache\CacheHandler`) +- **Plik:** `autoload/Shared/Cache/CacheHandler.php` - **Opis:** Handler do obsługi cache Redis - **Metody:** - - `get($key)` - pobiera wartość z cache - - `set($key, $value, $ttl = 86400)` - zapisuje wartość do cache + - `get($key)` - pobiera wartość z cache (zwraca zserializowany string, wymaga `unserialize()`) + - `set($key, $value, $ttl = 86400)` - zapisuje wartość do cache (serializuje wewnętrznie) - `exists($key)` - sprawdza czy klucz istnieje - `delete($key)` - usuwa pojedynczy klucz - `deletePattern($pattern)` - usuwa klucze według wzorca +#### USUNIĘTA: Cache (legacy file-based) +- ~~`autoload/class.Cache.php`~~ — usunięta w ver. 0.282 +- Zastąpiona przez `CacheHandler` (Redis) we wszystkich wywołaniach + #### Klasa S (pomocnicza) - **Plik:** `autoload/class.S.php` - **Metody cache:** @@ -100,9 +104,11 @@ shopPRO/ │ │ ├── controls/ # Kontrolery legacy (fallback) │ │ └── factory/ # Fabryki/helpery │ ├── Domain/ # Repozytoria/logika domenowa +│ ├── Shared/ # Wspoldzielone narzedzia +│ │ └── Cache/ # CacheHandler, RedisConnection │ ├── front/ # Klasy frontendu │ │ ├── Controllers/ # Nowe kontrolery DI (Newsletter) -│ │ ├── Views/ # Nowe widoki (Newsletter, Articles, Languages) +│ │ ├── Views/ # Nowe widoki (Newsletter, Articles, Languages, Banners) │ │ ├── controls/ # Kontrolery legacy (Site, ShopBasket, ...) │ │ ├── view/ # Widoki legacy (Site, ...) │ │ └── factory/ # Fabryki/helpery (fasady) @@ -388,10 +394,24 @@ Pelna dokumentacja testow: `TESTING.md` - Usunięta fasada `front\factory\Newsletter` — logika przeniesiona do `Domain\Newsletter\NewsletterRepository` (6 nowych metod frontendowych). - Usunięty stary kontroler `front\controls\Newsletter` i widok `front\view\Newsletter`. - Utworzony nowy namespace `front\Controllers\` — pierwszy frontowy kontroler z DI: `NewsletterController`. -- Utworzony nowy namespace `front\Views\` — czyste widoki statyczne: `Languages`, `Newsletter`. +- Utworzony nowy namespace `front\Views\` — czyste widoki statyczne: `Languages`, `Newsletter`, `Banners`. - Zaktualizowany routing w `front\controls\Site::route()` — `getControllerFactories()` (DI) z fallbackiem na stare `front\controls\`. - Przepięte 4 wywołania `Newsletter::get_template()` w `front\factory\ShopClient` na `NewsletterRepository::templateByName()`. - FIX: `newsletter_unsubscribe()` — błędna składnia medoo `delete()`. +## Aktualizacja 2026-02-16 (ver. 0.281) - Banners frontend migration +- NOWE METODY w `Domain/Banner/BannerRepository.php`: `banners()`, `mainBanner()` (Redis cache, filtrowanie dat). +- NOWY: `front\Views\Banners` — czysty VIEW (renderowanie szablonow banner/). +- USUNIETA: `front\factory\Banners` — logika przeniesiona do `BannerRepository`. +- USUNIETA: `front\view\Banners` — zastapiona przez `front\Views\Banners`. +- UPDATE: `front\view\Site::show()` — przepiecie na repo + Views. + +## Aktualizacja 2026-02-16 (ver. 0.282) - Cache cleanup, Shared namespace +- NOWY: `Shared\Cache\CacheHandler` + `Shared\Cache\RedisConnection` — namespace Shared. +- USUNIETA: `class.CacheHandler.php` — wrapper, 60 odwolan przepietych na `\Shared\Cache\CacheHandler`. +- USUNIETA: `class.RedisConnection.php` — wrapper, 12 odwolan przepietych na `\Shared\Cache\RedisConnection`. +- USUNIETA: `class.Cache.php` — legacy file-based cache. +- UPDATE: 6 plikow przepietych z `\Cache::fetch/store` na `CacheHandler` (ShopProduct, ShopPaymentMethod, ShopCategory, ShopTransport, ShopAttribute, DictionariesRepository). + --- *Dokument aktualizowany: 2026-02-16* diff --git a/docs/REFACTORING_PLAN.md b/docs/REFACTORING_PLAN.md index 10043ed..23c503f 100644 --- a/docs/REFACTORING_PLAN.md +++ b/docs/REFACTORING_PLAN.md @@ -129,9 +129,9 @@ grep -r "Product::getQuantity" . ### ✅ Zmigrowane moduły | # | Modul | Wersja | Zakres | |---|-------|--------|--------| -| 1 | Cache | 0.237 | CacheHandler, RedisConnection, clear_product_cache | +| 1 | Cache | 0.237, 0.282 | CacheHandler, RedisConnection, clear_product_cache, Shared\Cache namespace, eliminacja class.Cache.php | | 2 | Product | 0.238-0.252, 0.274, 0.277 | getQuantity, getPrice, getName, archive/unarchive, allProductsForMassEdit, getProductsByCategory, applyDiscountPercent, pelna migracja factory (CRUD, save, delete, duplicate, kombinacje, zdjecia/pliki, Google Feed XML) | -| 3 | Banner | 0.239 | find, delete, save, kontroler DI | +| 3 | Banner | 0.239, 0.281 | find, delete, save, kontroler DI, frontend: banners(), mainBanner() z Redis cache, usuniete fasady front\factory + front\view | | 4 | Settings | 0.240/0.250 | saveSettings, getSettings, kontroler DI | | 5 | Dictionaries | 0.251 | listForAdmin, find, save, delete, kontroler DI | | 6 | ProductArchive | 0.252 | kontroler DI, table-list | @@ -187,6 +187,7 @@ grep -r "Product::getQuantity" . ## Kolejność refaktoryzacji (priorytet) 1-33: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses, ShopPaymentMethod, ShopTransport, ShopAttribute, ShopProductSets, ShopProducer, ShopProduct (mass_edit), ShopClients, ShopCategory, ShopOrder, ShopProduct (factory), Dashboard, Update, Legacy cleanup, admin\App +34: ✅ Shared\Cache namespace (ver. 0.282) — CacheHandler + RedisConnection → Shared\Cache\, eliminacja class.Cache.php, przepiecie 6 plikow na CacheHandler ## Form Edit System @@ -287,7 +288,7 @@ tests/ │ └── UsersControllerTest.php └── Integration/ ``` -**Lacznie: 390 testow, 1278 asercji** +**Lacznie: 454 testow, 1449 asercji** Aktualizacja 2026-02-15 (ver. 0.273): - dodano testy `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` @@ -301,5 +302,5 @@ Pelna dokumentacja testow: `TESTING.md` --- *Rozpoczęto: 2025-02-05* -*Ostatnia aktualizacja: 2026-02-15* +*Ostatnia aktualizacja: 2026-02-16* *Changelog zmian: `docs/CHANGELOG.md`* diff --git a/docs/TESTING.md b/docs/TESTING.md index 0acf102..a32606b 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -36,7 +36,13 @@ Alternatywnie (Git Bash): Ostatnio zweryfikowano: 2026-02-16 ```text -OK (450 tests, 1431 assertions) +OK (454 tests, 1449 assertions) +``` + +Aktualizacja po migracji Banners frontend (2026-02-16, ver. 0.281): +```text +Pelny suite: OK (454 tests, 1449 assertions) +Nowe testy: BannerRepositoryTest (+4: banners flat languages, banners null, mainBanner flat languages, mainBanner null) ``` Aktualizacja po migracji Articles frontend (2026-02-16, ver. 0.280): diff --git a/docs/UPDATE_INSTRUCTIONS.md b/docs/UPDATE_INSTRUCTIONS.md index 3721175..d32e808 100644 --- a/docs/UPDATE_INSTRUCTIONS.md +++ b/docs/UPDATE_INSTRUCTIONS.md @@ -18,17 +18,17 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią ## Procedura tworzenia nowej aktualizacji -## Status biezacej aktualizacji (ver. 0.280) +## Status biezacej aktualizacji (ver. 0.282) -- Wersja udostepniona: `0.280` (data: 2026-02-16). +- Wersja udostepniona: `0.282` (data: 2026-02-16). - Pliki publikacyjne: - - `updates/0.20/ver_0.280.zip` - - `updates/0.20/ver_0.280_files.txt` + - `updates/0.20/ver_0.281.zip`, `ver_0.281_files.txt` + - `updates/0.20/ver_0.282.zip`, `ver_0.282_files.txt` - Pliki metadanych aktualizacji: - - `updates/changelog.php` (dodany wpis `ver. 0.280`) - - `updates/versions.php` (`$current_ver = 280`) + - `updates/changelog.php` (dodane wpisy `ver. 0.281`, `ver. 0.282`) + - `updates/versions.php` (`$current_ver = 282`) - Weryfikacja testow przed publikacja: - - `OK (450 tests, 1431 assertions)` + - `OK (454 tests, 1449 assertions)` ### 1. Określ numer wersji Sprawdź ostatnią wersję w `updates/` i zwiększ o 1. diff --git a/tests/Unit/Domain/Banner/BannerRepositoryTest.php b/tests/Unit/Domain/Banner/BannerRepositoryTest.php index cc7dbab..ce364bc 100644 --- a/tests/Unit/Domain/Banner/BannerRepositoryTest.php +++ b/tests/Unit/Domain/Banner/BannerRepositoryTest.php @@ -269,4 +269,119 @@ class BannerRepositoryTest extends TestCase $this->assertSame('/uploads/banner-a.jpg', $result['items'][0]['thumbnail_src']); $this->assertSame('', $result['items'][1]['thumbnail_src']); } + + // ─── Frontend methods ─────────────────────────────────────────── + + /** + * Test banners() — cache miss, zwraca aktywne banery z plaskim formatem languages + */ + public function testBannersReturnsActiveBannersWithFlatLanguages(): void + { + $mockDb = $this->createMock(\medoo::class); + + $queryStmt = $this->createMock(\PDOStatement::class); + $queryStmt->expects($this->once()) + ->method('fetchAll') + ->willReturn([ + ['id' => 1, 'name' => 'Baner A'], + ['id' => 2, 'name' => 'Baner B'], + ]); + + $mockDb->expects($this->once()) + ->method('query') + ->willReturn($queryStmt); + + $mockDb->expects($this->exactly(2)) + ->method('get') + ->with('pp_banners_langs', '*', $this->anything()) + ->willReturnOnConsecutiveCalls( + ['id_banner' => 1, 'id_lang' => 'pl', 'src' => 'a.jpg', 'url' => '/a', 'html' => '', 'text' => ''], + ['id_banner' => 2, 'id_lang' => 'pl', 'src' => 'b.jpg', 'url' => '/b', 'html' => '', 'text' => ''] + ); + + $repo = new BannerRepository($mockDb); + $result = $repo->banners('pl'); + + $this->assertIsArray($result); + $this->assertCount(2, $result); + // Format plaski — $banner['languages']['src'], nie $banner['languages']['pl']['src'] + $this->assertSame('a.jpg', $result[0]['languages']['src']); + $this->assertSame('b.jpg', $result[1]['languages']['src']); + } + + /** + * Test banners() — brak wynikow zwraca null + */ + public function testBannersReturnsNullWhenNoBanners(): void + { + $mockDb = $this->createMock(\medoo::class); + + $queryStmt = $this->createMock(\PDOStatement::class); + $queryStmt->expects($this->once()) + ->method('fetchAll') + ->willReturn([]); + + $mockDb->expects($this->once()) + ->method('query') + ->willReturn($queryStmt); + + $repo = new BannerRepository($mockDb); + $result = $repo->banners('pl'); + + $this->assertNull($result); + } + + /** + * Test mainBanner() — cache miss, zwraca baner z plaskim formatem languages + */ + public function testMainBannerReturnsActiveBannerWithFlatLanguages(): void + { + $mockDb = $this->createMock(\medoo::class); + + $queryStmt = $this->createMock(\PDOStatement::class); + $queryStmt->expects($this->once()) + ->method('fetchAll') + ->willReturn([ + ['id' => 5, 'name' => 'Main Banner', 'status' => 1, 'home_page' => 1], + ]); + + $mockDb->expects($this->once()) + ->method('query') + ->willReturn($queryStmt); + + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_banners_langs', '*', ['AND' => ['id_banner' => 5, 'id_lang' => 'pl']]) + ->willReturn(['id_banner' => 5, 'id_lang' => 'pl', 'src' => 'main.jpg', 'url' => '/main', 'html' => '', 'text' => '']); + + $repo = new BannerRepository($mockDb); + $result = $repo->mainBanner('pl'); + + $this->assertIsArray($result); + $this->assertSame(5, (int)$result['id']); + $this->assertSame('main.jpg', $result['languages']['src']); + $this->assertSame('/main', $result['languages']['url']); + } + + /** + * Test mainBanner() — brak wynikow zwraca null + */ + public function testMainBannerReturnsNullWhenNoBanner(): void + { + $mockDb = $this->createMock(\medoo::class); + + $queryStmt = $this->createMock(\PDOStatement::class); + $queryStmt->expects($this->once()) + ->method('fetchAll') + ->willReturn([]); + + $mockDb->expects($this->once()) + ->method('query') + ->willReturn($queryStmt); + + $repo = new BannerRepository($mockDb); + $result = $repo->mainBanner('pl'); + + $this->assertNull($result); + } } diff --git a/tests/Unit/Domain/Cache/CacheRepositoryTest.php b/tests/Unit/Domain/Cache/CacheRepositoryTest.php index 082ca3d..12d73d3 100644 --- a/tests/Unit/Domain/Cache/CacheRepositoryTest.php +++ b/tests/Unit/Domain/Cache/CacheRepositoryTest.php @@ -20,7 +20,7 @@ class CacheRepositoryTest extends TestCase $mockRedis = $this->createMock(\Redis::class); $mockRedis->expects($this->once())->method('flushAll')->willReturn(true); - $mockRedisConnection = $this->createMock(\RedisConnection::class); + $mockRedisConnection = $this->createMock(\Shared\Cache\RedisConnection::class); $mockRedisConnection->expects($this->once())->method('getConnection')->willReturn($mockRedis); $repository = new CacheRepository($mockRedisConnection); @@ -36,7 +36,7 @@ class CacheRepositoryTest extends TestCase */ public function testClearCacheRedisUnavailable(): void { - $mockRedisConnection = $this->createMock(\RedisConnection::class); + $mockRedisConnection = $this->createMock(\Shared\Cache\RedisConnection::class); $mockRedisConnection->expects($this->once())->method('getConnection')->willReturn(null); $repository = new CacheRepository($mockRedisConnection); diff --git a/tests/Unit/admin/Controllers/SettingsControllerTest.php b/tests/Unit/admin/Controllers/SettingsControllerTest.php index b93e4cc..e8f6e1d 100644 --- a/tests/Unit/admin/Controllers/SettingsControllerTest.php +++ b/tests/Unit/admin/Controllers/SettingsControllerTest.php @@ -10,7 +10,7 @@ use Domain\Settings\SettingsRepository; * Testy dla SettingsController * * Kontroler używa SettingsRepository (DI). - * Cache czyszczony bezpośrednio przez \S::delete_dir() i \RedisConnection. + * Cache czyszczony bezpośrednio przez \S::delete_dir() i \Shared\Cache\RedisConnection. */ class SettingsControllerTest extends TestCase { diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 47d9eff..a1d1008 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -59,7 +59,7 @@ if (!class_exists('S')) { } } -if (!class_exists('RedisConnection')) { +if (!class_exists('Shared\\Cache\\RedisConnection')) { class RedisConnection { private static $instance; public static function getInstance() { @@ -68,6 +68,7 @@ if (!class_exists('RedisConnection')) { } public function getConnection() { return null; } } + class_alias('RedisConnection', 'Shared\\Cache\\RedisConnection'); } if (!class_exists('Redis')) { @@ -80,7 +81,7 @@ if (!class_exists('Redis')) { } } -if (!class_exists('CacheHandler')) { +if (!class_exists('Shared\\Cache\\CacheHandler')) { class CacheHandler { public function get($key) { return null; } public function set($key, $value, $ttl = 86400) {} @@ -88,4 +89,5 @@ if (!class_exists('CacheHandler')) { public function delete($key) {} public function deletePattern($pattern) {} } + class_alias('CacheHandler', 'Shared\\Cache\\CacheHandler'); } diff --git a/updates/0.20/ver_0.281.zip b/updates/0.20/ver_0.281.zip new file mode 100644 index 0000000000000000000000000000000000000000..6e56d4651c6b2d6bd5a78d53d03bba8dd7a77809 GIT binary patch literal 7732 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 HpKt#KS66;U literal 0 HcmV?d00001 diff --git a/updates/0.20/ver_0.281_files.txt b/updates/0.20/ver_0.281_files.txt new file mode 100644 index 0000000..77eb73f --- /dev/null +++ b/updates/0.20/ver_0.281_files.txt @@ -0,0 +1,2 @@ +F: ../autoload/front/factory/class.Banners.php +F: ../autoload/front/view/class.Banners.php diff --git a/updates/0.20/ver_0.282.zip b/updates/0.20/ver_0.282.zip new file mode 100644 index 0000000000000000000000000000000000000000..a37cf443f6ce5f00538f7433cf4194348ee9a9da GIT binary patch literal 80768 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 Kc3=VnefuAVJ{`~i literal 0 HcmV?d00001 diff --git a/updates/0.20/ver_0.282_files.txt b/updates/0.20/ver_0.282_files.txt new file mode 100644 index 0000000..f269ed9 --- /dev/null +++ b/updates/0.20/ver_0.282_files.txt @@ -0,0 +1,3 @@ +F: ../autoload/class.Cache.php +F: ../autoload/class.CacheHandler.php +F: ../autoload/class.RedisConnection.php diff --git a/updates/changelog.php b/updates/changelog.php index db859ec..bf0d478 100644 --- a/updates/changelog.php +++ b/updates/changelog.php @@ -1,3 +1,19 @@ +ver. 0.282 - 16.02.2026
+- UPDATE - Cache cleanup: eliminacja legacy class.Cache.php, migracja CacheHandler i RedisConnection do Shared\Cache namespace +- UPDATE - 60 odwolan CacheHandler i 12 odwolan RedisConnection przepietych na Shared\Cache\ +- UPDATE - 13 metod front\factory przepietych z \Cache::fetch/store na CacheHandler (ShopProduct, ShopPaymentMethod, ShopCategory, ShopTransport, ShopAttribute) +- FIX - naprawione rozbieznosci kluczy cache (random_products, category_name) +- CLEANUP - usuniete: class.Cache.php, class.CacheHandler.php, class.RedisConnection.php +- UPDATE - testy: OK (454 tests, 1449 assertions) +
+ver. 0.281 - 16.02.2026
+- UPDATE - migracja Banners frontend: factory + view do Domain/Views (DI) +- NEW - `front\Views\Banners` — czysty VIEW (banners, mainBanner) +- UPDATE - `BannerRepository` rozszerzony o 2 metody frontendowe (banners, mainBanner) z Redis cache +- UPDATE - `front\view\Site::show()` przepiety na repo + Views +- CLEANUP - usuniete: front\factory\Banners, front\view\Banners +- UPDATE - testy: OK (454 tests, 1449 assertions) +
ver. 0.280 - 16.02.2026
- UPDATE - migracja Articles frontend: factory + view + encja do Domain/Views (DI) - NEW - `front\Views\Articles` — czysty VIEW + utility (renderowanie, generateTableOfContents, generateHeadersIds, getImage) diff --git a/updates/versions.php b/updates/versions.php index ca227de..ba671d1 100644 --- a/updates/versions.php +++ b/updates/versions.php @@ -1,5 +1,5 @@