From dd39587f95cb2fa74a8fb67e1da992801ba37315 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Thu, 19 Feb 2026 01:07:39 +0100 Subject: [PATCH] =?UTF-8?q?ver.=200.293:=20Code=20review=20fixes=20?= =?UTF-8?q?=E2=80=94=206=20repositories,=2016=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ArticleRepository: SQL injection fix (addslashes→parameterized), DRY refactor topArticles/newsListArticles - AttributeRepository: dead class_exists('\S') blocking cache/temp clear - CategoryRepository: dead class_exists('\S') blocking SEO link generation (critical) - BannerRepository: parameterize $today in SQL + null guard on query() - BasketCalculator: null guard checkProductQuantityInStock + optional DI params - PromotionRepository: null guard on $basket (production fatal) - OrderRepository/ShopBasketController/ajax.php: explicit DI in BasketCalculator callers 614 tests, 1821 assertions (+4 new) Co-Authored-By: Claude Opus 4.6 --- .phpunit.result.cache | 2 +- ajax.php | 2 +- autoload/Domain/Article/ArticleRepository.php | 132 +++++-------- .../Domain/Attribute/AttributeRepository.php | 10 +- autoload/Domain/Banner/BannerRepository.php | 24 ++- autoload/Domain/Basket/BasketCalculator.php | 32 +++- .../Domain/Category/CategoryRepository.php | 10 +- autoload/Domain/Order/OrderRepository.php | 6 +- .../Domain/Promotion/PromotionRepository.php | 3 + .../Controllers/ShopBasketController.php | 4 +- docs/CHANGELOG.md | 29 ++- docs/CLASS_CATALOG.md | 175 +++++++++++------- docs/TESTING.md | 4 +- docs/UPDATE_INSTRUCTIONS.md | 12 +- .../Domain/Article/ArticleRepositoryTest.php | 38 ++-- .../Domain/Basket/BasketCalculatorTest.php | 21 +++ updates/0.20/ver_0.293.zip | Bin 0 -> 36313 bytes updates/changelog.php | 9 + updates/versions.php | 2 +- 19 files changed, 297 insertions(+), 218 deletions(-) create mode 100644 updates/0.20/ver_0.293.zip diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 67be0ce..c367db5 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,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":3,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.005,"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.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.078,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.082,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.164,"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.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.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.001,"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.002,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusesReturnsMappedArray":0.002,"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.002,"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.002,"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.001,"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,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsNullWhenNone":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutFallsBackToDefault":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutReturnsNullWhenNothingFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsPageWithLanguage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdReturnsStartPage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdFallsBackToFirstActive":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageSortReturnsValue":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsMenuWithPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsNullForInvalidMenu":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuPagesReturnsEmptyForNoPages":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsAttributeWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsValueWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpCalculatesTotal":0.001,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsSumsQuantities":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextSingular":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural2to4":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural5Plus":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextCastsToInt":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsSortType":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsTitle":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsCategoryWithLanguage":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyWhenCategoryNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoriesTreeReturnsEmptyWhenNoCategories":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsZeroForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsCount":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsProductIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsClampsPage":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsRowOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsStringOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesHandlesFalseFromDb":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsRow":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveReturnsFalseForInvalidClientId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveInsertsNewAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveUpdatesExistingAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentReturnsFalseForInvalidIds":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentResetsAndSets":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorWhenClientNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsInactiveForUnconfirmedAccount":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnWrongPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsOkOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullWhenEmailTaken":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsIdAndHashOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationActivatesAndReturnsEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsEmailAndPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullOnEmptyEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoverySetsRecoveryFlagAndReturnsHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientOrdersReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsObjectWhenFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullForEmptyName":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsTrueForActiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForUsedCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForInactiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForNullCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableWorksWithArray":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedSkipsInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountSkipsInvalidId":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsIdWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullForEmptyHash":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsHashWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByIdReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByHashReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberFormatsCorrectly":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberStartsAt001":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsFullData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsProducerWithLanguage":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderRepository":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorRequiresProducerRepository":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsOneForActivePayment":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNormalizedData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsSku":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackReturnsEan":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsOneForActive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInactive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsCategories":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontUsesParentId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testTopProductIdsReturnsActiveProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsProductIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsEmptyWhenNone":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeWholeBasketAppliesDiscountToAll":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesOrAppliesDiscountToMatchingCategories":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionAppliesWhenConditionMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionNoDiscountWhenConditionNotMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesAndAppliesWhenBothConditionsMet":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportCostCachedReturnsCost":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsTransport":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsNullForInvalid":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsTransports":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsEmptyForInvalidId":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,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":3,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.005,"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.001,"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.004,"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,"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.075,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.076,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.151,"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.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.002,"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.002,"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.002,"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.002,"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,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsNullWhenNone":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutFallsBackToDefault":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutReturnsNullWhenNothingFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsPageWithLanguage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdReturnsStartPage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdFallsBackToFirstActive":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageSortReturnsValue":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsMenuWithPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsNullForInvalidMenu":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuPagesReturnsEmptyForNoPages":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsAttributeWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsValueWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpCalculatesTotal":0.001,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsSumsQuantities":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextSingular":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural2to4":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural5Plus":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextCastsToInt":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsSortType":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsTitle":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsCategoryWithLanguage":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyWhenCategoryNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoriesTreeReturnsEmptyWhenNoCategories":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsZeroForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsCount":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsProductIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsClampsPage":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsRowOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsStringOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesHandlesFalseFromDb":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsRow":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveReturnsFalseForInvalidClientId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveInsertsNewAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveUpdatesExistingAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentReturnsFalseForInvalidIds":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentResetsAndSets":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorWhenClientNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsInactiveForUnconfirmedAccount":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnWrongPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsOkOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullWhenEmailTaken":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsIdAndHashOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationActivatesAndReturnsEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsEmailAndPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullOnEmptyEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoverySetsRecoveryFlagAndReturnsHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientOrdersReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsObjectWhenFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullForEmptyName":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsTrueForActiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForUsedCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForInactiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForNullCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableWorksWithArray":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedSkipsInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountSkipsInvalidId":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsIdWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullForEmptyHash":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsHashWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByIdReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByHashReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberFormatsCorrectly":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberStartsAt001":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsFullData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsProducerWithLanguage":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderRepository":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorRequiresProducerRepository":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsOneForActivePayment":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNormalizedData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsSku":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackReturnsEan":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsOneForActive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInactive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsCategories":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontUsesParentId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testTopProductIdsReturnsActiveProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsProductIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsEmptyWhenNone":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeWholeBasketAppliesDiscountToAll":0.001,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesOrAppliesDiscountToMatchingCategories":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionAppliesWhenConditionMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionNoDiscountWhenConditionNotMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesAndAppliesWhenBothConditionsMet":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportCostCachedReturnsCost":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsTransport":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsNullForInvalid":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsTransports":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCheckProductQuantityInStockReturnsFalseOnNullBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCheckProductQuantityInStockReturnsFalseOnEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testValidateBasketReturnsEmptyArrayOnNull":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testValidateBasketReturnsBasketArrayAsIs":0}} \ No newline at end of file diff --git a/ajax.php b/ajax.php index 2e52a48..dfdbd16 100644 --- a/ajax.php +++ b/ajax.php @@ -63,7 +63,7 @@ if ( $a == 'basket_change_transport' ) \Shared\Helpers\Helpers::set_session( 'transport_id', \Shared\Helpers\Helpers::get( 'transport_id' ) ); $basket = \Shared\Helpers\Helpers::get_session( 'basket' ); - $basket_summary = \Domain\Basket\BasketCalculator::summaryPrice( $basket, null ); + $basket_summary = \Domain\Basket\BasketCalculator::summaryPrice( $basket, null, $lang_id ); $transport_cost = ( new \Domain\Transport\TransportRepository( $mdb ) )->transportCostCached( \Shared\Helpers\Helpers::get( 'transport_id' ) ); echo json_encode( [ 'summary' => \Shared\Helpers\Helpers::decimal( $basket_summary + $transport_cost ) . ' zł' ] ); diff --git a/autoload/Domain/Article/ArticleRepository.php b/autoload/Domain/Article/ArticleRepository.php index 8ef49f9..2c47203 100644 --- a/autoload/Domain/Article/ArticleRepository.php +++ b/autoload/Domain/Article/ArticleRepository.php @@ -844,13 +844,14 @@ class ArticleRepository /** * Pobiera artykuly opublikowane w podanym zakresie dat. */ - public function articlesByDateAdd( string $dateStart, string $dateEnd ): array + public function articlesByDateAdd( string $dateStart, string $dateEnd, string $langId = 'pl' ): array { $stmt = $this->db->query( 'SELECT id FROM pp_articles ' . 'WHERE status = 1 ' - . 'AND date_add BETWEEN \'' . addslashes( $dateStart ) . '\' AND \'' . addslashes( $dateEnd ) . '\' ' - . 'ORDER BY date_add DESC' + . 'AND date_add BETWEEN :date_start AND :date_end ' + . 'ORDER BY date_add DESC', + [':date_start' => $dateStart, ':date_end' => $dateEnd] ); $articles = []; @@ -858,7 +859,7 @@ class ArticleRepository if ( is_array( $rows ) ) { foreach ( $rows as $row ) { - $articles[] = $this->articleDetailsFrontend( $row['id'], 'pl' ); + $articles[] = $this->articleDetailsFrontend( $row['id'], $langId ); } } @@ -889,24 +890,18 @@ class ArticleRepository return null; } - $results = $this->db->select('pp_articles_langs', '*', [ + $langRow = $this->db->get('pp_articles_langs', '*', [ 'AND' => ['article_id' => $articleId, 'lang_id' => $langId] ]); - if (is_array($results)) { - foreach ($results as $row) { - if ($row['copy_from']) { - $results2 = $this->db->select('pp_articles_langs', '*', [ - 'AND' => ['article_id' => $articleId, 'lang_id' => $row['copy_from']] - ]); - if (is_array($results2)) { - foreach ($results2 as $row2) { - $article['language'] = $row2; - } - } - } else { - $article['language'] = $row; - } + if ($langRow) { + if ($langRow['copy_from']) { + $copyRow = $this->db->get('pp_articles_langs', '*', [ + 'AND' => ['article_id' => $articleId, 'lang_id' => $langRow['copy_from']] + ]); + $article['language'] = $copyRow ? $copyRow : $langRow; + } else { + $article['language'] = $langRow; } } @@ -955,7 +950,7 @@ class ArticleRepository return unserialize($objectData); } - $results = $this->db->query( + $stmt = $this->db->query( 'SELECT * FROM ( ' . 'SELECT ' . 'a.id, date_modify, date_add, o, ' @@ -971,12 +966,14 @@ class ArticleRepository . 'INNER JOIN pp_articles AS a ON a.id = ap.article_id ' . 'INNER JOIN pp_articles_langs AS al ON al.article_id = ap.article_id ' . 'WHERE ' - . 'status = 1 AND page_id = ' . (int)$pageId . ' AND lang_id = \'' . $langId . '\' ' + . 'status = 1 AND page_id = ' . (int)$pageId . ' AND lang_id = :lang_id ' . ') AS q1 ' . 'WHERE q1.title IS NOT NULL ' . 'ORDER BY q1.' . $order . ' ' - . 'LIMIT ' . (int)$from . ',' . (int)$limit - )->fetchAll(); + . 'LIMIT ' . (int)$from . ',' . (int)$limit, + [':lang_id' => $langId] + ); + $results = $stmt ? $stmt->fetchAll() : []; if (is_array($results) && !empty($results)) { foreach ($results as $row) { @@ -1003,7 +1000,7 @@ class ArticleRepository return (int)unserialize($objectData); } - $results = $this->db->query( + $stmt = $this->db->query( 'SELECT COUNT(0) FROM ( ' . 'SELECT ' . 'a.id, ' @@ -1019,10 +1016,12 @@ class ArticleRepository . 'INNER JOIN pp_articles AS a ON a.id = ap.article_id ' . 'INNER JOIN pp_articles_langs AS al ON al.article_id = ap.article_id ' . 'WHERE ' - . 'status = 1 AND page_id = ' . (int)$pageId . ' AND lang_id = \'' . $langId . '\' ' + . 'status = 1 AND page_id = ' . (int)$pageId . ' AND lang_id = :lang_id ' . ') AS q1 ' - . 'WHERE q1.title IS NOT NULL' - )->fetchAll(); + . 'WHERE q1.title IS NOT NULL', + [':lang_id' => $langId] + ); + $results = $stmt ? $stmt->fetchAll() : []; $count = isset($results[0][0]) ? (int)$results[0][0] : 0; @@ -1106,14 +1105,30 @@ class ArticleRepository * Pobiera najpopularniejsze artykuly ze strony (wg views DESC, z Redis cache). */ public function topArticles(int $pageId, int $limit, string $langId): ?array + { + return $this->fetchArticlesByPage('topArticles', $pageId, $limit, $langId, 'views DESC'); + } + + /** + * Pobiera najnowsze artykuly ze strony (wg date_add DESC, z Redis cache). + */ + public function newsListArticles(int $pageId, int $limit, string $langId): ?array + { + return $this->fetchArticlesByPage('newsListArticles', $pageId, $limit, $langId, 'date_add DESC'); + } + + /** + * Wspolna logika dla topArticles/newsListArticles (z Redis cache). + */ + private function fetchArticlesByPage(string $cachePrefix, int $pageId, int $limit, string $langId, string $orderBy): ?array { $cacheHandler = new \Shared\Cache\CacheHandler(); - $cacheKey = "ArticleRepository::topArticles:{$pageId}:{$limit}:{$langId}"; + $cacheKey = "ArticleRepository::{$cachePrefix}:{$pageId}:{$limit}:{$langId}"; $objectData = $cacheHandler->get($cacheKey); if (!$objectData) { - $articlesData = $this->db->query( + $stmt = $this->db->query( 'SELECT * FROM ( ' . 'SELECT ' . 'a.id, date_add, views, ' @@ -1129,61 +1144,14 @@ class ArticleRepository . 'INNER JOIN pp_articles AS a ON a.id = ap.article_id ' . 'INNER JOIN pp_articles_langs AS al ON al.article_id = ap.article_id ' . 'WHERE ' - . 'status = 1 AND page_id = ' . (int)$pageId . ' AND lang_id = \'' . $langId . '\' ' + . 'status = 1 AND page_id = ' . (int)$pageId . ' AND lang_id = :lang_id ' . ') AS q1 ' . 'WHERE q1.title IS NOT NULL ' - . 'ORDER BY q1.views DESC ' - . 'LIMIT 0, ' . (int)$limit - )->fetchAll(\PDO::FETCH_ASSOC); - - $cacheHandler->set($cacheKey, $articlesData); - } else { - $articlesData = unserialize($objectData); - } - - $articles = null; - if (\Shared\Helpers\Helpers::is_array_fix($articlesData)) { - foreach ($articlesData as $row) { - $articles[] = $this->articleDetailsFrontend((int)$row['id'], $langId); - } - } - - return $articles; - } - - /** - * Pobiera najnowsze artykuly ze strony (wg date_add DESC, z Redis cache). - */ - public function newsListArticles(int $pageId, int $limit, string $langId): ?array - { - $cacheHandler = new \Shared\Cache\CacheHandler(); - $cacheKey = "ArticleRepository::newsListArticles:{$pageId}:{$limit}:{$langId}"; - - $objectData = $cacheHandler->get($cacheKey); - - if (!$objectData) { - $articlesData = $this->db->query( - 'SELECT * FROM ( ' - . 'SELECT ' - . 'a.id, date_add, ' - . '( CASE ' - . 'WHEN copy_from IS NULL THEN title ' - . 'WHEN copy_from IS NOT NULL THEN ( ' - . 'SELECT title FROM pp_articles_langs ' - . 'WHERE lang_id = al.copy_from AND article_id = a.id ' - . ') ' - . 'END ) AS title ' - . 'FROM ' - . 'pp_articles_pages AS ap ' - . 'INNER JOIN pp_articles AS a ON a.id = ap.article_id ' - . 'INNER JOIN pp_articles_langs AS al ON al.article_id = ap.article_id ' - . 'WHERE ' - . 'status = 1 AND page_id = ' . (int)$pageId . ' AND lang_id = \'' . $langId . '\' ' - . ') AS q1 ' - . 'WHERE q1.title IS NOT NULL ' - . 'ORDER BY q1.date_add DESC ' - . 'LIMIT 0, ' . (int)$limit - )->fetchAll(\PDO::FETCH_ASSOC); + . 'ORDER BY q1.' . $orderBy . ' ' + . 'LIMIT 0, ' . (int)$limit, + [':lang_id' => $langId] + ); + $articlesData = $stmt ? $stmt->fetchAll(\PDO::FETCH_ASSOC) : []; $cacheHandler->set($cacheKey, $articlesData); } else { diff --git a/autoload/Domain/Attribute/AttributeRepository.php b/autoload/Domain/Attribute/AttributeRepository.php index d0571ca..96bc36e 100644 --- a/autoload/Domain/Attribute/AttributeRepository.php +++ b/autoload/Domain/Attribute/AttributeRepository.php @@ -938,14 +938,8 @@ class AttributeRepository private function clearTempAndCache(): void { - if (class_exists('\S')) { - if (method_exists('\S', 'delete_dir')) { - \Shared\Helpers\Helpers::delete_dir('../temp/'); - } - if (method_exists('\S', 'delete_cache')) { - \Shared\Helpers\Helpers::delete_cache(); - } - } + \Shared\Helpers\Helpers::delete_dir('../temp/'); + \Shared\Helpers\Helpers::delete_cache(); } private function normalizeDecimal(float $value, int $precision = 2): float diff --git a/autoload/Domain/Banner/BannerRepository.php b/autoload/Domain/Banner/BannerRepository.php index 4cee78d..eb3b2eb 100644 --- a/autoload/Domain/Banner/BannerRepository.php +++ b/autoload/Domain/Banner/BannerRepository.php @@ -331,13 +331,15 @@ class BannerRepository } $today = date('Y-m-d'); - $results = $this->db->query( + $stmt = $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(); + . "AND (date_start <= :today1 OR date_start IS NULL) " + . "AND (date_end >= :today2 OR date_end IS NULL) " + . "AND home_page = 0", + [':today1' => $today, ':today2' => $today] + ); + $results = $stmt ? $stmt->fetchAll() : []; $banners = null; if (is_array($results) && !empty($results)) { @@ -370,15 +372,17 @@ class BannerRepository } $today = date('Y-m-d'); - $results = $this->db->query( + $stmt = $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 (date_start <= :today1 OR date_start IS NULL) " + . "AND (date_end >= :today2 OR date_end IS NULL) " . "AND home_page = 1 " . "ORDER BY date_end ASC " - . "LIMIT 1" - )->fetchAll(); + . "LIMIT 1", + [':today1' => $today, ':today2' => $today] + ); + $results = $stmt ? $stmt->fetchAll() : []; $banner = null; if (is_array($results) && !empty($results)) { diff --git a/autoload/Domain/Basket/BasketCalculator.php b/autoload/Domain/Basket/BasketCalculator.php index f19c41b..d6d2737 100644 --- a/autoload/Domain/Basket/BasketCalculator.php +++ b/autoload/Domain/Basket/BasketCalculator.php @@ -26,22 +26,32 @@ class BasketCalculator return $count . ' produktów'; } - public static function summaryPrice($basket, $coupon = null) + /** + * @param string|null $langId Language ID (falls back to global $lang_id if null) + * @param \Domain\Product\ProductRepository|null $productRepo (falls back to $GLOBALS['mdb'] if null) + */ + public static function summaryPrice($basket, $coupon = null, $langId = null, $productRepo = null) { - global $lang_id; + if ($langId === null) { + global $lang_id; + $langId = $lang_id; + } + if ($productRepo === null) { + $productRepo = new \Domain\Product\ProductRepository($GLOBALS['mdb']); + } $summary = 0; - $productRepo = new \Domain\Product\ProductRepository($GLOBALS['mdb']); if (is_array($basket)) { foreach ($basket as $position) { - $product = $productRepo->findCached((int)$position['product-id'], $lang_id); + $product = $productRepo->findCached((int)$position['product-id'], $langId); $product_price_tmp = self::calculateBasketProductPrice( (float)($product['price_brutto_promo'] ?? 0), (float)($product['price_brutto'] ?? 0), $coupon, - $position + $position, + $productRepo ); $summary += $product_price_tmp['price_new'] * $position['quantity']; } @@ -71,6 +81,9 @@ class BasketCalculator public static function checkProductQuantityInStock($basket, bool $message = false) { + if ( !is_array( $basket ) || empty( $basket ) ) + return false; + $result = false; $productRepo = new \Domain\Product\ProductRepository($GLOBALS['mdb']); @@ -115,9 +128,14 @@ class BasketCalculator * Calculate product price in basket (with coupon + promotion discounts). * Migrated from \shop\Product::calculate_basket_product_price() */ - public static function calculateBasketProductPrice( float $price_brutto_promo, float $price_brutto, $coupon, $basket_position ) + /** + * @param \Domain\Product\ProductRepository|null $productRepo (falls back to $GLOBALS['mdb'] if null) + */ + public static function calculateBasketProductPrice( float $price_brutto_promo, float $price_brutto, $coupon, $basket_position, $productRepo = null ) { - $productRepo = new \Domain\Product\ProductRepository($GLOBALS['mdb']); + if ($productRepo === null) { + $productRepo = new \Domain\Product\ProductRepository($GLOBALS['mdb']); + } // Produkty przecenione if ( $price_brutto_promo ) diff --git a/autoload/Domain/Category/CategoryRepository.php b/autoload/Domain/Category/CategoryRepository.php index ec74a88..b67257e 100644 --- a/autoload/Domain/Category/CategoryRepository.php +++ b/autoload/Domain/Category/CategoryRepository.php @@ -668,18 +668,12 @@ class CategoryRepository private function refreshCategoryArtifacts(): void { - if (class_exists('\\S')) { - \Shared\Helpers\Helpers::htacces(); - \Shared\Helpers\Helpers::delete_dir('../temp/'); - } + \Shared\Helpers\Helpers::htacces(); + \Shared\Helpers\Helpers::delete_dir('../temp/'); } private function normalizeSeoLink($value): ?string { - if (!class_exists('\\S')) { - return $this->toNullableString($value); - } - $seo = \Shared\Helpers\Helpers::seo((string)$value); $seo = trim((string)$seo); diff --git a/autoload/Domain/Order/OrderRepository.php b/autoload/Domain/Order/OrderRepository.php index 065da53..66cf7ae 100644 --- a/autoload/Domain/Order/OrderRepository.php +++ b/autoload/Domain/Order/OrderRepository.php @@ -560,7 +560,8 @@ class OrderRepository $transport = ( new \Domain\Transport\TransportRepository( $this->db ) )->findActiveByIdCached( $transport_id ); $payment_method = ( new \Domain\PaymentMethod\PaymentMethodRepository( $this->db ) )->findActiveById( (int)$payment_id ); - $basket_summary = \Domain\Basket\BasketCalculator::summaryPrice($basket, $coupon); + $productRepo = new \Domain\Product\ProductRepository($this->db); + $basket_summary = \Domain\Basket\BasketCalculator::summaryPrice($basket, $coupon, $lang_id, $productRepo); $order_number = $this->generateOrderNumber(); $order_date = date('Y-m-d H:i:s'); $hash = md5($order_number . time()); @@ -619,7 +620,6 @@ class OrderRepository if (is_array($basket)) { foreach ($basket as $basket_position) { $attributes = ''; - $productRepo = new \Domain\Product\ProductRepository($this->db); $product = $productRepo->findCached($basket_position['product-id'], $lang_id); if (is_array($basket_position['attributes'])) { @@ -649,7 +649,7 @@ class OrderRepository } } - $product_price_tmp = \Domain\Basket\BasketCalculator::calculateBasketProductPrice((float)$product['price_brutto_promo'], (float)$product['price_brutto'], $coupon, $basket_position); + $product_price_tmp = \Domain\Basket\BasketCalculator::calculateBasketProductPrice((float)$product['price_brutto_promo'], (float)$product['price_brutto'], $coupon, $basket_position, $productRepo); $this->db->insert('pp_shop_order_products', [ 'order_id' => $order_id, diff --git a/autoload/Domain/Promotion/PromotionRepository.php b/autoload/Domain/Promotion/PromotionRepository.php index 784d934..f838a32 100644 --- a/autoload/Domain/Promotion/PromotionRepository.php +++ b/autoload/Domain/Promotion/PromotionRepository.php @@ -453,6 +453,9 @@ class PromotionRepository public function findPromotion( $basket ) { + if ( !is_array( $basket ) || empty( $basket ) ) + return is_array( $basket ) ? $basket : []; + foreach ( $basket as $key => $val ) { unset( $basket[$key]['discount_type'] ); diff --git a/autoload/front/Controllers/ShopBasketController.php b/autoload/front/Controllers/ShopBasketController.php index ac96eb4..3bc7fe4 100644 --- a/autoload/front/Controllers/ShopBasketController.php +++ b/autoload/front/Controllers/ShopBasketController.php @@ -173,7 +173,7 @@ class ShopBasketController echo json_encode( [ 'result' => 'ok', 'basket_mini_count' => \Domain\Basket\BasketCalculator::countProductsText( \Domain\Basket\BasketCalculator::countProducts( $basket ) ), - 'basket_mini_value' => \Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon ), + 'basket_mini_value' => \Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon, $lang_id ), 'product_sets' => ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->productSetsWhenAddToBasket( (int)$values['product-id'] ) ] ); exit; @@ -393,7 +393,7 @@ class ShopBasketController 'coupon' => $coupon ] ), 'basket_mini_count' => \Domain\Basket\BasketCalculator::countProductsText( \Domain\Basket\BasketCalculator::countProducts( $basket ) ), - 'basket_mini_value' => \Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon ), + 'basket_mini_value' => \Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon, $lang_id ), 'products_count' => count( $basket ), 'transport_methods' => \Shared\Tpl\Tpl::view( 'shop-basket/basket-transport-methods', [ 'transports_methods' => ( new \Domain\Transport\TransportRepository( $GLOBALS['mdb'] ) )->transportMethodsFront( $basket, $coupon ), diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e8308a5..51aaf27 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,7 +4,34 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. --- -## ver. 0.294 (2026-02-18) - Usuniecie autoload/shop/ — 12 legacy klas +## ver. 0.293 (2026-02-19) - Code review: fixes ArticleRepository, AttributeRepository, BannerRepository, BasketCalculator, CategoryRepository, PromotionRepository + +- **ArticleRepository** (7 fixes): + - FIX: `articlesByDateAdd()` — SQL injection (addslashes→parameterized queries), dodano parametr `$langId` + - FIX: `articleDetailsFrontend()` — uproszczono select()+foreach→get() + - FIX: `articlesIds()`, `pageArticlesCount()` — parametryzacja `$langId` + - FIX: `topArticles()`, `newsListArticles()` — DRY refactor via `fetchArticlesByPage()`, parametryzacja +- **AttributeRepository** (1 fix): + - FIX: `clearTempAndCache()` — martwy `class_exists('\S')` blokował czyszczenie cache/temp +- **CategoryRepository** (2 fixes): + - FIX: `refreshCategoryArtifacts()` — martwy `class_exists('\S')` blokował czyszczenie htaccess/temp + - FIX: `normalizeSeoLink()` — **krytyczny bug** — linki SEO kategorii nigdy nie były generowane od usunięcia `\S` +- **BannerRepository** (2 fixes): + - FIX: `banners()`, `mainBanner()` — parametryzacja `$today` w SQL + null guard na `query()` +- **BasketCalculator** (3 fixes): + - FIX: `checkProductQuantityInStock()` — dodano `is_array()` guard (foreach na null→fatal) + - FIX: `summaryPrice()` — dodano opcjonalne DI params `$langId`, `$productRepo` z fallbackiem do globals + - FIX: `calculateBasketProductPrice()` — dodano opcjonalny `$productRepo` z fallbackiem do globals +- **PromotionRepository** (1 fix): + - FIX: `findPromotion()` — null guard na `$basket` (produkcyjny fatal error) +- **OrderRepository** — zaktualizowano callery BasketCalculator (jawne DI zamiast globals), usunięto redundantne tworzenie ProductRepository w pętli +- **ShopBasketController**, **ajax.php** — zaktualizowano callery summaryPrice (jawne `$lang_id`) +- **CLASS_CATALOG.md** — zaktualizowano katalog dla 5 klas (rzeczywiste metody + znaczniki przeglądu) +- Testy: 614 OK, 1821 asercji (+4 nowe testy BasketCalculator) + +--- + +## ver. 0.292 (2026-02-18) - Usuniecie autoload/shop/ — 12 legacy klas - **Faza 5.1: class.Order.php (~562 linii) USUNIETA** - Logika Apilo sync przeniesiona do `OrderAdminService::processApiloSyncQueue()` diff --git a/docs/CLASS_CATALOG.md b/docs/CLASS_CATALOG.md index d394ac3..6bf95cd 100644 --- a/docs/CLASS_CATALOG.md +++ b/docs/CLASS_CATALOG.md @@ -17,101 +17,140 @@ Wygenerowano: 2026-02-18 ## 1. Domain/ — Warstwa domenowa -### Domain\Article\ArticleRepository +### Domain\Article\ArticleRepository ✅ REVIEWED File: `autoload/Domain/Article/ArticleRepository.php` Properties: - `private $db` - `private const MAX_PER_PAGE = 100` -Methods: -- `public function __construct($db)` -- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` -- `public function find(int $articleId): array` -- `public function save(array $data): ?int` -- `public function delete(int $articleId): bool` -- `public function toggleStatus(int $articleId): bool` -- `public function detailsForLanguage(int $articleId, string $langId): ?array` -- `public function frontArticleDetails(int $articleId, string $langId): array` -- `public function frontArticleDetailsBySeoLink(string $seoLink, string $langId): ?array` -- `public function frontArticleList(string $langId, int $page = 1, int $perPage = 12): array` -- `public function frontArticleListAll(string $langId): array` -- `public function getFirstImageCached(int $articleId)` -- `private function baseListSelect(): string` -- `private function translationsMap(int $articleId): array` -- `private function extractTranslations(array $data): array` -- `private function toSwitchValue($value): int` -- `private function defaultArticle(): array` -- `private function clearFrontCache(int $articleId): void` -- `private function saveSeoRedirects(int $articleId, string $langId, string $newSeoLink, string $currentSeoLink): void` +Public Methods: +- ✅ `public function __construct($db)` +- ✅ `public function find(int $articleId): ?array` — try/catch na brak kolumny `o` (kompatybilność) +- ✅ `public function save(int $articleId, array $data, int $userId): int` +- ✅ `public function archive(int $articleId): bool` +- ✅ `public function restore(int $articleId): bool` +- ✅ `public function deletePermanently(int $articleId): bool` +- ✅ `public function listForAdmin(array $filters, string $sortColumn = 'date_add', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- ✅ `public function listArchivedForAdmin(array $filters, string $sortColumn = 'date_add', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- ✅ `public function saveGalleryOrder(int $articleId, string $order): bool` +- ✅ `public function saveFilesOrder(int $articleId, string $order): bool` +- ✅ `public function pagesSummaryForArticles(array $articleIds): array` +- ✅ `public function updateImageAlt(int $imageId, string $imageAlt): bool` +- ✅ `public function updateFileName(int $fileId, string $fileName): bool` +- ✅ `public function markFileToDelete(int $fileId): bool` +- ✅ `public function markImageToDelete(int $imageId): bool` +- ✅ `public function deleteNonassignedFiles(): void` +- ✅ `public function deleteNonassignedImages(): void` +- 🔧 `public function articlesByDateAdd(string $dateStart, string $dateEnd, string $langId = 'pl'): array` — naprawiono SQL injection (addslashes→parameterized), dodano parametr $langId +- 🔧 `public function articleDetailsFrontend(int $articleId, string $langId): ?array` — select+foreach → get() (uproszczono) +- 🔧 `public function articlesIds(int $pageId, string $langId, int $limit, int $sortType, int $from): ?array` — parametryzacja $langId +- 🔧 `public function pageArticlesCount(int $pageId, string $langId): int` — parametryzacja $langId +- ✅ `public function pageArticles(array $page, string $langId, int $bs): array` +- ✅ `public function news(int $pageId, int $limit, string $langId): ?array` +- ✅ `public function articleNoindex(int $articleId, string $langId): bool` +- 🔧 `public function topArticles(int $pageId, int $limit, string $langId): ?array` — parametryzacja $langId + DRY via fetchArticlesByPage() +- 🔧 `public function newsListArticles(int $pageId, int $limit, string $langId): ?array` — parametryzacja $langId + DRY via fetchArticlesByPage() + +Private Methods: +- ✅ `private function createArticle(array $data, int $userId): int` +- ✅ `private function updateArticle(int $articleId, array $data, int $userId): int` +- ✅ `private function buildArticleRow(array $data, int $userId, bool $isNew): array` +- ✅ `private function buildLangRow($langId, array $data): array` +- ✅ `private function applyGalleryOrderIfProvided(int $articleId, array $data): void` +- ✅ `private function applyFilesOrderIfProvided(int $articleId, array $data): void` +- ✅ `private function saveTranslations(int $articleId, array $data, bool $isNew): void` +- ✅ `private function savePages(int $articleId, $pages, bool $isNew): void` +- ✅ `private function assignTempFiles(int $articleId): void` +- ✅ `private function assignTempImages(int $articleId): void` +- ✅ `private function deleteMarkedImages(int $articleId): void` +- ✅ `private function deleteMarkedFiles(int $articleId): void` +- ✅ `private function maxPageOrder(): int` +- ✅ `private function isCheckedValue($value): bool` +- ✅ `private function appendDateRangeFilter(array &$where, array &$params, string $column, string $fromKey, string $toKey, array $filters): void` +- 🔧 `private function fetchArticlesByPage(string $cachePrefix, int $pageId, int $limit, string $langId, string $orderBy): ?array` — NOWA, wspólna logika topArticles/newsListArticles --- -### Domain\Attribute\AttributeRepository +### Domain\Attribute\AttributeRepository ✅ REVIEWED File: `autoload/Domain/Attribute/AttributeRepository.php` Properties: - `private $db` +- `private ?string $defaultLangId = null` - `private const MAX_PER_PAGE = 100` -Methods: -- `public function __construct($db)` -- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` -- `public function find(int $attributeId): array` -- `public function save(array $data): ?int` -- `public function delete(int $attributeId): bool` -- `public function detailsForLanguage(int $attributeId, string $langId): ?array` -- `public function getAttributeValues(int $attributeId): array` -- `public function findValue(int $valueId): array` -- `public function saveValue(array $data): ?int` -- `public function deleteValue(int $valueId): bool` -- `public function detailsForValueLanguage(int $valueId, string $langId): ?array` -- `public function allForAdmin(): array` -- `public function allValuesForAttribute(int $attributeId): array` -- `public function productAttributes(int $productId): array` -- `public function valueDetails(int $valueId): ?array` -- `public function isValueDefault(int $valueId): int` -- `public function getAttributeOrder(int $attributeId): int` -- `public function getAttributeNameById(int $attributeId, string $langId): string` -- `public function getAttributeValueById(int $valueId, string $langId): string` -- `private function baseListSelect(): string` -- `private function translationsMap(int $attributeId): array` -- `private function extractTranslations(array $data): array` -- `private function valueTranslationsMap(int $valueId): array` -- `private function extractValueTranslations(array $data): array` -- `private function toSwitchValue($value): int` -- `private function defaultAttribute(): array` -- `private function defaultValue(): array` +Public Methods: +- ✅ `public function __construct($db)` +- ✅ `public function listForAdmin(array $filters, string $sortColumn = 'o', string $sortDir = 'ASC', int $page = 1, int $perPage = 15): array` +- ✅ `public function findAttribute(int $attributeId): array` +- ✅ `public function saveAttribute(array $data): ?int` +- ✅ `public function deleteAttribute(int $attributeId): bool` +- ✅ `public function findValues(int $attributeId): array` +- ✅ `public function saveValues(int $attributeId, array $payload): bool` +- ✅ `public function saveLegacyValues(int $attributeId, array $names, array $values, array $ids, $defaultValue, array $impactOnThePrice): ?int` +- ✅ `public function valueDetails(int $valueId): array` +- ✅ `public function getAttributeNameById(int $attributeId, ?string $langId = null): string` +- ✅ `public function getAttributeValueById(int $valueId, ?string $langId = null): string` — z cache +- ✅ `public function getAttributesListForCombinations(): array` +- ✅ `public function frontAttributeDetails(int $attributeId, string $langId): array` — z cache +- ✅ `public function frontValueDetails(int $valueId, string $langId): array` — z cache +- ✅ `public function isValueDefault(int $valueId)` +- ✅ `public function getAttributeOrder(int $attributeId)` +- ✅ `public function getAttributeNameByValue(int $valueId, string $langId)` — parametryzowane query + +Private Methods: +- ✅ `private function buildAdminWhere(array $filters): array` +- ✅ `private function saveAttributeTranslations(int $attributeId, array $names): void` +- ✅ `private function saveValueTranslations(int $valueId, array $translations): void` +- ✅ `private function valueBelongsToAttribute(int $valueId, int $attributeId): bool` +- ✅ `private function normalizeValueRows(array $rows): array` +- ✅ `private function refreshCombinationPricesForValue(int $valueId, ?string $impactOnThePrice): void` +- ✅ `private function toSwitchValue($value): int` +- ✅ `private function toTypeValue($value): int` +- ✅ `private function toNullableNumeric($value): ?string` +- ✅ `private function defaultAttribute(): array` +- ✅ `private function nextOrder(): int` +- ✅ `private function defaultLanguageId(): string` +- ✅ `private function clearFrontCache(int $id, string $type): void` +- 🔧 `private function clearTempAndCache(): void` — usunięto martwy check `class_exists('\S')` +- ✅ `private function normalizeDecimal(float $value, int $precision = 2): float` --- -### Domain\Banner\BannerRepository +### Domain\Banner\BannerRepository ✅ REVIEWED File: `autoload/Domain/Banner/BannerRepository.php` Properties: - `private $db` - `private const MAX_PER_PAGE = 100` -Methods: -- `public function __construct($db)` -- `public function listForAdmin(array $filters = [], string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` -- `public function find(int $bannerId): ?array` -- `public function save(array $data): ?int` -- `public function delete(int $bannerId): bool` -- `public function allActiveCached(): array` -- `private function toSwitchValue($value): int` +Public Methods: +- ✅ `public function __construct($db)` +- ✅ `public function find(int $bannerId): ?array` — pobiera baner + tłumaczenia +- ✅ `public function delete(int $bannerId): bool` +- ✅ `public function save(array $data)` — insert/update, obsługuje nowy i legacy format +- ✅ `public function listForAdmin(array $filters, string $sortColumn = 'name', string $sortDir = 'ASC', int $page = 1, int $perPage = 15): array` — z thumbnailami +- 🔧 `public function banners(string $langId): ?array` — parametryzacja $today + null guard na query() +- 🔧 `public function mainBanner(string $langId): ?array` — parametryzacja $today + null guard na query() + +Private Methods: +- ✅ `private function fetchThumbnailsByBannerIds(array $bannerIds): array` — parametryzowane IN +- ✅ `private function saveTranslations(int $bannerId, array $src, array $url, array $html, array $text): void` — legacy format +- ✅ `private function saveTranslationsFromArray(int $bannerId, array $translations): void` — nowy format +- ✅ `private function upsertTranslation(int $bannerId, $langId, array $fields): void` — count+insert/update --- -### Domain\Basket\BasketCalculator +### Domain\Basket\BasketCalculator ✅ REVIEWED File: `autoload/Domain/Basket/BasketCalculator.php` -Properties: (brak) +Properties: (brak — klasa statyczna) Methods: -- `public static function summaryPrice($basket, $coupon): float` -- `public static function summaryPriceWithTransport($basket, $coupon, $transport_cost): float` -- `public static function summaryQty($basket): int` -- `public static function summaryWp($basket): float` -- `public static function summaryCoupon($basket, $coupon): float` -- `public static function summaryNetto($basket): float` -- `public static function summaryBrutto($basket): float` +- ✅ `public static function summaryWp($basket)` — suma wag, is_array guard +- ✅ `public static function countProductsText($count)` — polska pluralizacja +- 🔧 `public static function summaryPrice($basket, $coupon = null, $langId = null, $productRepo = null)` — dodano opcjonalne DI params z fallbackiem do globals +- ✅ `public static function countProducts($basket)` — suma ilości, is_array guard +- ✅ `public static function validateBasket($basket)` — null guard +- 🔧 `public static function checkProductQuantityInStock($basket, bool $message = false)` — dodano is_array guard (bug: foreach na null) +- 🔧 `public static function calculateBasketProductPrice(float $price_brutto_promo, float $price_brutto, $coupon, $basket_position, $productRepo = null)` — dodano opcjonalny $productRepo z fallbackiem do globals --- diff --git a/docs/TESTING.md b/docs/TESTING.md index 95be14b..cb519ce 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -23,10 +23,10 @@ composer test # standard ## Aktualny stan ```text -OK (610 tests, 1817 assertions) +OK (614 tests, 1821 assertions) ``` -Zweryfikowano: 2026-02-18 (ver. 0.294) +Zweryfikowano: 2026-02-19 (ver. 0.293) ## Konfiguracja diff --git a/docs/UPDATE_INSTRUCTIONS.md b/docs/UPDATE_INSTRUCTIONS.md index e16b414..cf4ef0d 100644 --- a/docs/UPDATE_INSTRUCTIONS.md +++ b/docs/UPDATE_INSTRUCTIONS.md @@ -18,16 +18,16 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią ## Procedura tworzenia nowej aktualizacji -## Status biezacej aktualizacji (ver. 0.292) +## Status biezacej aktualizacji (ver. 0.293) -- Wersja udostepniona: `0.292` (data: 2026-02-18). +- Wersja udostepniona: `0.293` (data: 2026-02-19). - Pliki publikacyjne: - - `updates/0.20/ver_0.292.zip`, `ver_0.292_files.txt` + - `updates/0.20/ver_0.293.zip` - Pliki metadanych aktualizacji: - - `updates/changelog.php` (skonsolidowany wpis `ver. 0.292` z 0.292+0.293+0.294) - - `updates/versions.php` (`$current_ver = 292`) + - `updates/changelog.php` + - `updates/versions.php` (`$current_ver = 293`) - Weryfikacja testow przed publikacja: - - `OK (610 tests, 1817 assertions)` + - `OK (614 tests, 1821 assertions)` ### 1. Określ numer wersji Sprawdź ostatnią wersję w `updates/` i zwiększ o 1. diff --git a/tests/Unit/Domain/Article/ArticleRepositoryTest.php b/tests/Unit/Domain/Article/ArticleRepositoryTest.php index f56c13b..1cfd039 100644 --- a/tests/Unit/Domain/Article/ArticleRepositoryTest.php +++ b/tests/Unit/Domain/Article/ArticleRepositoryTest.php @@ -691,15 +691,16 @@ class ArticleRepositoryTest extends TestCase { $mockDb = $this->createMock(\medoo::class); - $mockDb->expects($this->once()) + $mockDb->expects($this->exactly(2)) ->method('get') - ->with('pp_articles', '*', ['id' => 5]) - ->willReturn(['id' => 5, 'status' => 1, 'show_title' => 1]); + ->willReturnOnConsecutiveCalls( + ['id' => 5, 'status' => 1, 'show_title' => 1], + ['lang_id' => 'pl', 'title' => 'Testowy', 'copy_from' => null] + ); - $mockDb->expects($this->exactly(4)) + $mockDb->expects($this->exactly(3)) ->method('select') ->willReturnOnConsecutiveCalls( - [['lang_id' => 'pl', 'title' => 'Testowy', 'copy_from' => null]], [['id' => 10, 'src' => '/img/a.jpg']], [['id' => 20, 'src' => '/files/a.pdf']], [1, 2] @@ -732,20 +733,17 @@ class ArticleRepositoryTest extends TestCase $mockDb = $this->createMock(\medoo::class); $mockDb->method('get') - ->willReturn(['id' => 7, 'status' => 1]); + ->willReturnOnConsecutiveCalls( + ['id' => 7, 'status' => 1], + ['lang_id' => 'en', 'title' => 'English', 'copy_from' => 'pl'], + ['lang_id' => 'pl', 'title' => 'Polski'] + ); - $mockDb->expects($this->exactly(5)) + $mockDb->expects($this->exactly(3)) ->method('select') ->willReturnOnConsecutiveCalls( - // First call: langs with copy_from - [['lang_id' => 'en', 'title' => 'English', 'copy_from' => 'pl']], - // Second call: copy_from fallback - [['lang_id' => 'pl', 'title' => 'Polski']], - // images [], - // files [], - // pages [] ); @@ -917,11 +915,13 @@ class ArticleRepositoryTest extends TestCase }); $mockDb->method('get') - ->willReturn(['id' => 5, 'status' => 1]); + ->willReturnOnConsecutiveCalls( + ['id' => 5, 'status' => 1], + ['lang_id' => 'pl', 'title' => 'Popular', 'copy_from' => null] + ); $mockDb->method('select') ->willReturnOnConsecutiveCalls( - [['lang_id' => 'pl', 'title' => 'Popular', 'copy_from' => null]], [], [], [] @@ -952,11 +952,13 @@ class ArticleRepositoryTest extends TestCase }); $mockDb->method('get') - ->willReturn(['id' => 8, 'status' => 1]); + ->willReturnOnConsecutiveCalls( + ['id' => 8, 'status' => 1], + ['lang_id' => 'pl', 'title' => 'Newest', 'copy_from' => null] + ); $mockDb->method('select') ->willReturnOnConsecutiveCalls( - [['lang_id' => 'pl', 'title' => 'Newest', 'copy_from' => null]], [], [], [] diff --git a/tests/Unit/Domain/Basket/BasketCalculatorTest.php b/tests/Unit/Domain/Basket/BasketCalculatorTest.php index fba9cde..9ca5266 100644 --- a/tests/Unit/Domain/Basket/BasketCalculatorTest.php +++ b/tests/Unit/Domain/Basket/BasketCalculatorTest.php @@ -64,4 +64,25 @@ class BasketCalculatorTest extends TestCase $this->assertSame('3 produkty', BasketCalculator::countProductsText('3')); $this->assertSame('0 produktów', BasketCalculator::countProductsText('abc')); } + + public function testCheckProductQuantityInStockReturnsFalseOnNullBasket(): void + { + $this->assertFalse(BasketCalculator::checkProductQuantityInStock(null)); + } + + public function testCheckProductQuantityInStockReturnsFalseOnEmptyBasket(): void + { + $this->assertFalse(BasketCalculator::checkProductQuantityInStock([])); + } + + public function testValidateBasketReturnsEmptyArrayOnNull(): void + { + $this->assertSame([], BasketCalculator::validateBasket(null)); + } + + public function testValidateBasketReturnsBasketArrayAsIs(): void + { + $basket = [['product-id' => 1, 'quantity' => 2]]; + $this->assertSame($basket, BasketCalculator::validateBasket($basket)); + } } diff --git a/updates/0.20/ver_0.293.zip b/updates/0.20/ver_0.293.zip new file mode 100644 index 0000000000000000000000000000000000000000..faa0fd346041053a2323000039132c2ae6d34854 GIT binary patch literal 36313 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 CsUd~{ literal 0 HcmV?d00001 diff --git a/updates/changelog.php b/updates/changelog.php index c20dc15..e2cda01 100644 --- a/updates/changelog.php +++ b/updates/changelog.php @@ -1,3 +1,12 @@ +ver. 0.293 - 19.02.2026
+- FIX - ArticleRepository: SQL injection fix (addslashes→parameterized), uproszczenie articleDetailsFrontend +- FIX - AttributeRepository: martwy class_exists('\S') blokowal czyszczenie cache/temp +- FIX - CategoryRepository: martwy class_exists('\S') blokowal generowanie linkow SEO kategorii +- FIX - BannerRepository: parametryzacja dat w SQL + null guard na query() +- FIX - BasketCalculator: null guard checkProductQuantityInStock + opcjonalne DI params summaryPrice/calculateBasketProductPrice +- FIX - PromotionRepository: null guard na $basket (produkcyjny fatal error) +- UPDATE - OrderRepository, ShopBasketController, ajax.php: jawne DI zamiast globals w callerach BasketCalculator +


ver. 0.292 - 18.02.2026
- UPDATE - pelna migracja front\factory\ do Domain (5 ostatnich klas: ShopProduct, ShopPaymentMethod, ShopPromotion, ShopStatuses, ShopTransport) - UPDATE - ProductRepository: ~20 nowych metod frontendowych (cache Redis, lazy loading, SKU/EAN fallback) diff --git a/updates/versions.php b/updates/versions.php index 07c8c55..861e4e9 100644 --- a/updates/versions.php +++ b/updates/versions.php @@ -1,5 +1,5 @@