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