diff --git a/.phpunit.result.cache b/.phpunit.result.cache index c367db5..78b8078 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":1,"defects":{"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":4,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":3,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.005,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsPromoPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularWhenPromoIsHigher":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsProductName":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveReturnsBool":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveReturnsBool":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorAcceptsRepository":0.004,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasUnarchiveMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testUnarchiveMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorRequiresProductRepository":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsBannerWithTranslations":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testDeleteReturnsTrue":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveInsertsNewBanner":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithRedis":0.001,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheRedisUnavailable":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithoutRedis":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheReturnStructure":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testCanBeInstantiated":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasSaveSettingsMethod":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasGetSettingsMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheAjaxMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasViewMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testIsNotAbstract":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testCanCreateController":0.004,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testEditMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsArticleWithRelations":0.004,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsNullWhenArticleDoesNotExist":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedFilesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedImagesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveReturnsZeroWhenInsertFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveSetsStatusToMinusOne":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveReturnsFalseWhenUpdateFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderUpdatesImageOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderSkipsEmptyValues":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasBrowseListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasGalleryOrderSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testBrowseListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testGalleryOrderSaveMethodReturnType":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminUsesBoundParamsForTitleFilter":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveWithLegacyFormat":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveUpdatesExistingTranslationsByBannerAndLang":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testListForAdminIncludesThumbnailSrc":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testRestoreSetsStatusToZero":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeletePermanentlyRemovesArticleAndRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListArchivedForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsUnitWithTranslations":0.001,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsNullWhenUnitNotFound":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testSaveInsertsNewUnitAndTranslationsForStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testDeleteRemovesUnitAndTranslations":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdReturnsTextFromDatabase":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdSupportsStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testAllUnitsReturnsArrayIndexedById":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsArrayOrNull":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveLanguageRejectsInvalidLanguageId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveTranslationInsertsNewTranslationAndReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDeleteTranslationReturnsBoolean":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdReturnsLanguageWithStartFlag":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdFallsBackToFirstLanguageOrPl":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsLayoutWithRelations":0.001,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testDeleteReturnsFalseWhenOnlyOneLayoutExists":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsDefaultLayoutWhenRecordDoesNotExist":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testSaveInsertsNewLayoutAndReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testListAllReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsNullForInvalidId":0.002,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSaveSettingsUpdatesHeaderAndFooter":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testDeleteTemplateReturnsFalseForAdminTemplate":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateByNameReturnsText":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsDefaultContainerForInvalidId":0.001,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsContainerWithTranslations":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDetailsForLanguageReturnsNullForInvalidData":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserWhenExists":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsErrorWhenLoginIsTaken":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsOkWhenAvailable":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveCreatesUserWithNormalizedSwitches":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithoutPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrue":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsFalseOnFailure":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsUserByLogin":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsSuccessForValidCredentials":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsNegativeOneForBlockedUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseAfterMaxAttempts":0.075,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.076,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.151,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseWhen2FADisabled":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseForInvalidEmail":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testUpdateByIdCallsDbUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testConstructorRequiresDictionariesRepository":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testConstructorRequiresLanguagesRepository":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testConstructorRequiresLayoutsRepository":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testConstructorRequiresRepositoryAndRenderer":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testConstructorAcceptsDependencies":0.001,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testConstructorRequiresRepositoryAndLanguagesRepository":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasViewListMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserEditMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasTwofaMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasLoginFormMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testConstructorRequiresUserRepository":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserReturnsDefaultsForNull":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserCastsTypes":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserHandlesPartialData":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveFilesOrderUpdatesFilesOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveFilesOrderSkipsEmptyValues":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPagesSummaryForArticlesBuildsLabels":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testUpdateImageAltDelegatesToDatabase":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testMarkFileToDeleteDelegatesToDatabase":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindReturnsDefaultCouponForInvalidId":0.001,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindNormalizesCouponData":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testSaveInsertsCouponAndReturnsId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testSaveUpdatesCouponAndReturnsId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingsReturnsArray":0.002,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingUpdatesExistingValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingInsertsNewValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testInvalidProviderThrowsException":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testLinkProductUpdatesDatabase":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testUnlinkProductClearsFields":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloGetAccessTokenReturnsNullWithoutSettings":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListThrowsForInvalidType":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testAllPublicMethodsExist":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSettingsTableMapping":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShopproProviderWorks":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenusListReturnsArray":0.002,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenuDeleteReturnsFalseWhenMenuHasPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testGenerateSeoLinkAddsSuffixWhenBaseSlugExists":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testPageUrlPreviewBuildsLanguagePrefixedUrlForNonDefaultLanguage":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testFindReturnsDefaultPromotionForInvalidId":0.002,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testSaveInsertsPromotionAndReturnsId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageAltChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileNameChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageAltChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileNameChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloSettingsMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloDataFetchMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloProductMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllShopproMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testApiloSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testShopproSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testVoidReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveSellasistMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveBaselinkerMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorRequiresPagesLanguagesAndLayoutsRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorRequiresPromotionRepository":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullForNegativeId":0.001,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsStatusWithIdZero":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindNormalizesNullApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveUpdatesColorAndApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithIdZeroWorks":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithEmptyApiloStatusIdSetsNull":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveRejectsNegativeId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsValue":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsNullWhenNotSet":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdForApilo":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdReturnsNullForUnknownIntegration":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testAllStatusesReturnsOrderedList":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorRequiresShopStatusRepository":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsFalseForFarFutureDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsTrueForNearExpiryDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloIntegrationStatusReturnsMissingConfigMessage":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListRejectsErrorPayload":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListAcceptsIdNameList":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullForInvalidId":0.001,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindNormalizesData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveUpdatesRowAndReturnsId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSavePreservesNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllForAdminReturnsRowsIncludingInactive":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindKeepsNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveNormalizesStatusValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdHandlesNullAndInt":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsStringForNonNumericValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullForInvalidId":0.001,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindNormalizesDataAndIncludesPaymentMethods":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindHandlesNullMaxWpAndApiloId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveInsertReturnsNewId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveUpdateReturnsExistingId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveInsertReturnsNullOnFailure":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveResetsDefaultWhenSettingNew":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveSwitchValuesNormalization":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testListForAdminWhitelistsSortColumn":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetApiloCarrierAccountIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetApiloCarrierAccountIdReturnsIntOrNull":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetTransportCostReturnsFloatOrNull":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testAllForAdminReturnsAllTransports":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testConstructorRequiresPaymentMethodRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFindAttributeReturnsDefaultAttributeForInvalidId":0.002,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testListForAdminWhitelistsSortDirectionAndPerPage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesRemovesObsoleteRowsAndSetsDefault":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesDeletesTranslationWhenNameIsEmpty":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testGetAttributeValueByIdUsesDefaultLanguageWhenNotProvided":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSortTypesReturnsExpectedKeys":0.002,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsReturnsDefaultForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsLoadsTranslations":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderReturnsFalseForNonArray":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderUpdatesOrderAndParent":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderReturnsFalseForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderUpdatesCategoryProductOrder":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsFalseWhenHasChildren":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsTrueWhenDeleted":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsFirstAvailableTitle":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testListForAdminWhitelistsSortAndPagination":0.001,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientReturnsEmptyOnMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientNormalizesRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsZeroForMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsAggregatedValues":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testConstructorAcceptsDb":0.001,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testHasAllPublicMethods":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testSalesGridReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testLastOrdersReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testMostViewedProductsReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testBestSalesProductsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsFallbackWhenEmpty":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsList":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsEmptyArrayWhenNone":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsDefaultsToPl":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsForDifferentLanguage":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionUpdatesStatus":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailReturnsFalseForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSignupReturnsFalseForExistingEmail":0.001,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConstructorAcceptsOptionalDependencies":0.002,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusesReturnsMappedArray":0.001,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testNextAndPrevOrderIdReturnNullForInvalidInput":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindReturnsDefaultProducerForInvalidId":0.001,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindNormalizesProducerData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveInsertsNewProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveUpdatesExistingProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllProducersReturnsFormattedList":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testProducerProductsReturnsPaginatedResults":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindReturnsDefaultSetForInvalidId":0.001,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindNormalizesSetData":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveInsertsNewSetAndSyncsProducts":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveUpdatesExistingSet":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testAllSetsReturnsFormattedList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditReturnsMap":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsEmptyArray":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsNullForInvalidProduct":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsCorrectPrices":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentZeroPercentNullsPromo":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsAssociativeArray":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenNoSettings":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsHandlesNullFromDb":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsCorrectParam":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueUsesParamNotHardcoded":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsEmptyStringWhenNotFound":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testConstructorAcceptsDb":0.001,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasUpdateMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testUpdateReturnsArray":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasRunPendingMigrationsMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testRunPendingMigrationsWithNoResults":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasPrivateHelperMethods":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsErrorsForMissingDefaultLanguageAndDefaultSelection":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsEmptyArrayForValidRows":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorAcceptsDependencies":0.001,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorRequiresCategoryAndLanguagesRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorRequiresClientRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsService":0.003,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderAdminService":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasMassEditActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasViewListMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasEditAndSaveMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasOperationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasCombinationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasImageAndFileMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditReturnsString":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditSaveReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testGetProductsByCategoryReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasFormBuildingHelpers":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testSaveMethodReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorRequiresProductSetRepository":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateAllMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsSortedIds":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsNullForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsInt":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsZeroForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesPagination":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsBool":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsFalseForNonNoindex":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsReturnsArticlesArray":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testBannersReturnsActiveBannersWithFlatLanguages":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testBannersReturnsNullWhenNoBanners":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testMainBannerReturnsActiveBannerWithFlatLanguages":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testMainBannerReturnsNullWhenNoBanner":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsNullWhenNone":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutFallsBackToDefault":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutReturnsNullWhenNothingFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsPageWithLanguage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdReturnsStartPage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdFallsBackToFirstActive":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageSortReturnsValue":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsMenuWithPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsNullForInvalidMenu":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuPagesReturnsEmptyForNoPages":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsAttributeWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsValueWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpCalculatesTotal":0.001,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsSumsQuantities":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextSingular":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural2to4":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural5Plus":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextCastsToInt":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsSortType":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsTitle":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsCategoryWithLanguage":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyWhenCategoryNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoriesTreeReturnsEmptyWhenNoCategories":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsZeroForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsCount":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsProductIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsClampsPage":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsRowOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsStringOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesHandlesFalseFromDb":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsRow":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveReturnsFalseForInvalidClientId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveInsertsNewAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveUpdatesExistingAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentReturnsFalseForInvalidIds":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentResetsAndSets":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorWhenClientNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsInactiveForUnconfirmedAccount":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnWrongPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsOkOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullWhenEmailTaken":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsIdAndHashOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationActivatesAndReturnsEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsEmailAndPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullOnEmptyEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoverySetsRecoveryFlagAndReturnsHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientOrdersReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsObjectWhenFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullForEmptyName":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsTrueForActiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForUsedCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForInactiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForNullCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableWorksWithArray":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedSkipsInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountSkipsInvalidId":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsIdWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullForEmptyHash":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsHashWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByIdReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByHashReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberFormatsCorrectly":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberStartsAt001":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsFullData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsProducerWithLanguage":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderRepository":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorRequiresProducerRepository":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsOneForActivePayment":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNormalizedData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsSku":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackReturnsEan":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsOneForActive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInactive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsCategories":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontUsesParentId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testTopProductIdsReturnsActiveProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsProductIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsEmptyWhenNone":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeWholeBasketAppliesDiscountToAll":0.001,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesOrAppliesDiscountToMatchingCategories":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionAppliesWhenConditionMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionNoDiscountWhenConditionNotMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesAndAppliesWhenBothConditionsMet":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportCostCachedReturnsCost":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsTransport":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsNullForInvalid":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsTransports":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCheckProductQuantityInStockReturnsFalseOnNullBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCheckProductQuantityInStockReturnsFalseOnEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testValidateBasketReturnsEmptyArrayOnNull":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testValidateBasketReturnsBasketArrayAsIs":0}} \ No newline at end of file +{"version":1,"defects":{"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":4,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":3,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0,"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.001,"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,"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.002,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testDeleteReturnsFalseWhenOnlyOneLayoutExists":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsDefaultLayoutWhenRecordDoesNotExist":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testSaveInsertsNewLayoutAndReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testListAllReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsNullForInvalidId":0.002,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSaveSettingsUpdatesHeaderAndFooter":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testDeleteTemplateReturnsFalseForAdminTemplate":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateByNameReturnsText":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsDefaultContainerForInvalidId":0.001,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsContainerWithTranslations":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDetailsForLanguageReturnsNullForInvalidData":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserWhenExists":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsErrorWhenLoginIsTaken":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsOkWhenAvailable":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveCreatesUserWithNormalizedSwitches":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithoutPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrue":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsFalseOnFailure":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsUserByLogin":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsSuccessForValidCredentials":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsNegativeOneForBlockedUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseAfterMaxAttempts":0.078,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.079,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.157,"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.002,"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.002,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorRequiresPromotionRepository":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullForNegativeId":0.001,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsStatusWithIdZero":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindNormalizesNullApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveUpdatesColorAndApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithIdZeroWorks":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithEmptyApiloStatusIdSetsNull":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveRejectsNegativeId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsValue":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsNullWhenNotSet":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdForApilo":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdReturnsNullForUnknownIntegration":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testAllStatusesReturnsOrderedList":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorRequiresShopStatusRepository":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsFalseForFarFutureDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsTrueForNearExpiryDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloIntegrationStatusReturnsMissingConfigMessage":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListRejectsErrorPayload":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListAcceptsIdNameList":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullForInvalidId":0.001,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindNormalizesData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveUpdatesRowAndReturnsId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSavePreservesNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllForAdminReturnsRowsIncludingInactive":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindKeepsNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveNormalizesStatusValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdHandlesNullAndInt":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsStringForNonNumericValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullForInvalidId":0,"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.001,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFindAttributeReturnsDefaultAttributeForInvalidId":0.002,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testListForAdminWhitelistsSortDirectionAndPerPage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesRemovesObsoleteRowsAndSetsDefault":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesDeletesTranslationWhenNameIsEmpty":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testGetAttributeValueByIdUsesDefaultLanguageWhenNotProvided":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSortTypesReturnsExpectedKeys":0.002,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsReturnsDefaultForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsLoadsTranslations":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderReturnsFalseForNonArray":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderUpdatesOrderAndParent":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderReturnsFalseForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderUpdatesCategoryProductOrder":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsFalseWhenHasChildren":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsTrueWhenDeleted":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsFirstAvailableTitle":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testListForAdminWhitelistsSortAndPagination":0.001,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientReturnsEmptyOnMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientNormalizesRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsZeroForMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsAggregatedValues":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testConstructorAcceptsDb":0.001,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testHasAllPublicMethods":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testSalesGridReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testLastOrdersReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testMostViewedProductsReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testBestSalesProductsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsFallbackWhenEmpty":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsList":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsEmptyArrayWhenNone":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsDefaultsToPl":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsForDifferentLanguage":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionUpdatesStatus":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailReturnsFalseForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSignupReturnsFalseForExistingEmail":0.001,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConstructorAcceptsOptionalDependencies":0.003,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusesReturnsMappedArray":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testNextAndPrevOrderIdReturnNullForInvalidInput":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindReturnsDefaultProducerForInvalidId":0.001,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindNormalizesProducerData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveInsertsNewProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveUpdatesExistingProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllProducersReturnsFormattedList":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testProducerProductsReturnsPaginatedResults":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindReturnsDefaultSetForInvalidId":0.001,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindNormalizesSetData":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveInsertsNewSetAndSyncsProducts":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveUpdatesExistingSet":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testAllSetsReturnsFormattedList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditReturnsMap":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsEmptyArray":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsNullForInvalidProduct":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsCorrectPrices":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentZeroPercentNullsPromo":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsAssociativeArray":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenNoSettings":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsHandlesNullFromDb":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsCorrectParam":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueUsesParamNotHardcoded":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsEmptyStringWhenNotFound":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testConstructorAcceptsDb":0.001,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasUpdateMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testUpdateReturnsArray":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasRunPendingMigrationsMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testRunPendingMigrationsWithNoResults":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasPrivateHelperMethods":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsErrorsForMissingDefaultLanguageAndDefaultSelection":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsEmptyArrayForValidRows":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorRequiresCategoryAndLanguagesRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorRequiresClientRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsService":0.002,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderAdminService":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasMassEditActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasViewListMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasEditAndSaveMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasOperationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasCombinationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasImageAndFileMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditReturnsString":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditSaveReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testGetProductsByCategoryReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasFormBuildingHelpers":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testSaveMethodReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorRequiresProductSetRepository":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateAllMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsSortedIds":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsNullForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsInt":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsZeroForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesPagination":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsBool":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsFalseForNonNoindex":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsReturnsArticlesArray":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testBannersReturnsActiveBannersWithFlatLanguages":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testBannersReturnsNullWhenNoBanners":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testMainBannerReturnsActiveBannerWithFlatLanguages":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testMainBannerReturnsNullWhenNoBanner":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsNullWhenNone":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutFallsBackToDefault":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutReturnsNullWhenNothingFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsPageWithLanguage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdReturnsStartPage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdFallsBackToFirstActive":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageSortReturnsValue":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsMenuWithPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsNullForInvalidMenu":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuPagesReturnsEmptyForNoPages":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsAttributeWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsValueWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpCalculatesTotal":0.001,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsSumsQuantities":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextSingular":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural2to4":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural5Plus":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextCastsToInt":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsSortType":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsTitle":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsCategoryWithLanguage":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyWhenCategoryNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoriesTreeReturnsEmptyWhenNoCategories":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsZeroForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsCount":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsProductIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsClampsPage":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsRowOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsStringOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesHandlesFalseFromDb":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsRow":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveReturnsFalseForInvalidClientId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveInsertsNewAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveUpdatesExistingAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentReturnsFalseForInvalidIds":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentResetsAndSets":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorWhenClientNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsInactiveForUnconfirmedAccount":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnWrongPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsOkOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullWhenEmailTaken":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsIdAndHashOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationActivatesAndReturnsEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsEmailAndPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullOnEmptyEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoverySetsRecoveryFlagAndReturnsHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientOrdersReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsObjectWhenFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullForEmptyName":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsTrueForActiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForUsedCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForInactiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForNullCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableWorksWithArray":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedSkipsInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountSkipsInvalidId":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsIdWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullForEmptyHash":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsHashWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByIdReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByHashReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberFormatsCorrectly":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberStartsAt001":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsFullData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsProducerWithLanguage":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderRepository":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorRequiresProducerRepository":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsOneForActivePayment":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNormalizedData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsSku":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackReturnsEan":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsOneForActive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInactive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsCategories":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontUsesParentId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testTopProductIdsReturnsActiveProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsProductIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsEmptyWhenNone":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeWholeBasketAppliesDiscountToAll":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesOrAppliesDiscountToMatchingCategories":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionAppliesWhenConditionMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionNoDiscountWhenConditionNotMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesAndAppliesWhenBothConditionsMet":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportCostCachedReturnsCost":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsTransport":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsNullForInvalid":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsTransports":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCheckProductQuantityInStockReturnsFalseOnNullBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCheckProductQuantityInStockReturnsFalseOnEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testValidateBasketReturnsEmptyArrayOnNull":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testValidateBasketReturnsBasketArrayAsIs":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testConstructorAcceptsOnlyOrderRepository":0.006,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testConstructorAcceptsAllDependencies":0.013,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSearchProductsReturnsEmptyForEmptyQuery":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSearchProductsReturnsEmptyWithoutProductRepo":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSearchProductsReturnsFormattedResults":0.001,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsReturnsFalseForInvalidOrderId":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsDeletesRemovedProducts":0.001,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsUpdatesQuantityAndAdjustsStock":0.001,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsAddsNewProductAndDecreasesStock":0.001,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testGetFreeDeliveryThresholdReturnsZeroWithoutSettingsRepo":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testGetFreeDeliveryThresholdReturnsValue":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGetOrderProductReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGetOrderProductReturnsArray":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testAddOrderProductReturnsNullForInvalidOrderId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testAddOrderProductInsertsAndReturnsId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateOrderProductReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateOrderProductUpdatesFields":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateOrderProductReturnsFalseForEmptyData":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testDeleteOrderProductReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testDeleteOrderProductCallsDelete":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateTransportCostDoesNothingForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateTransportCostUpdatesOrder":0}} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 15cd384..976fc21 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ composer test PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`. -Current suite: **610 tests, 1817 assertions**. +Current suite: **636 tests, 1868 assertions**. ### Creating Updates See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packages in `updates/0.XX/`. Never include `*.md` files, `updates/changelog.php`, or root `.htaccess` in update ZIPs. diff --git a/admin/templates/shop-order/order-edit-custom-script.php b/admin/templates/shop-order/order-edit-custom-script.php index 0f2e99a..bea0136 100644 --- a/admin/templates/shop-order/order-edit-custom-script.php +++ b/admin/templates/shop-order/order-edit-custom-script.php @@ -1,5 +1,10 @@ diff --git a/admin/templates/shop-order/order-edit.php b/admin/templates/shop-order/order-edit.php index d478d43..46b6fec 100644 --- a/admin/templates/shop-order/order-edit.php +++ b/admin/templates/shop-order/order-edit.php @@ -84,7 +84,12 @@ $orderId = (int)($this -> order['id'] ?? 0);
-
Kwota zamówienia order[ 'summary' ];?> zł
+
Kwota zamówienia order[ 'summary' ];?> zł
+
+ Produkty: 0,00 +  |  + Dostawa: order['transport_cost'] ?? 0), 2, ',', '');?> +

@@ -160,46 +165,75 @@ $orderId = (int)($this -> order['id'] ?? 0);
+
Produkty zamówienia:
- +
- + - - - - + + + + + - - order[ 'products' ] ) ): foreach ( $this -> order[ 'products' ] as $product ):?> - + + + order[ 'products' ] ) ): foreach ( $this -> order[ 'products' ] as $i => $product ):?> + + + + + + + + + + + - - - -
ZdjęcieZdjęcie NazwaIlośćCena / szt:Cena / szt (po rabacie):Suma (po rabacie):IlośćCena / szt:Cena promo:Suma:
- + - -
-
- -
-
-
- Wiadomość: ' . $product['message'] : '';?> -
+ + +
+ + +
Wiadomość:
+ +
+ + + + + + + 0 ? (float)$product['price_brutto_promo'] : (float)$product['price_brutto']) * (int)$product['quantity'], 2, ',', '');?> zł + +
-
+
+ +
+
Dodaj produkt:
+
+
+
+ + +
+
+
+
@@ -222,4 +256,11 @@ $orderId = (int)($this -> order['id'] ?? 0);
+ \ No newline at end of file diff --git a/autoload/Domain/Order/OrderAdminService.php b/autoload/Domain/Order/OrderAdminService.php index 350db31..c6ac433 100644 --- a/autoload/Domain/Order/OrderAdminService.php +++ b/autoload/Domain/Order/OrderAdminService.php @@ -4,10 +4,20 @@ namespace Domain\Order; class OrderAdminService { private OrderRepository $orders; + private $productRepo; + private $settingsRepo; + private $transportRepo; - public function __construct(OrderRepository $orders) - { + public function __construct( + OrderRepository $orders, + $productRepo = null, + $settingsRepo = null, + $transportRepo = null + ) { $this->orders = $orders; + $this->productRepo = $productRepo; + $this->settingsRepo = $settingsRepo; + $this->transportRepo = $transportRepo; } public function details(int $orderId): array @@ -71,6 +81,215 @@ class OrderAdminService return $saved; } + // ========================================================================= + // Order products management (admin) + // ========================================================================= + + public function searchProducts(string $query, string $langId): array + { + if (!$this->productRepo || trim($query) === '') { + return []; + } + + $rows = $this->productRepo->searchProductByNameAjax($query, $langId); + $results = []; + + foreach ($rows as $row) { + $productId = (int)($row['product_id'] ?? 0); + if ($productId <= 0) { + continue; + } + + $product = $this->productRepo->findCached($productId, $langId); + if (!is_array($product)) { + continue; + } + + $name = isset($product['language']['name']) ? (string)$product['language']['name'] : ''; + $img = $this->productRepo->getProductImg($productId); + + $results[] = [ + 'product_id' => $productId, + 'parent_product_id' => (int)($product['parent_id'] ?? 0), + 'name' => $name, + 'sku' => (string)($product['sku'] ?? ''), + 'ean' => (string)($product['ean'] ?? ''), + 'price_brutto' => (float)($product['price_brutto'] ?? 0), + 'price_brutto_promo' => (float)($product['price_brutto_promo'] ?? 0), + 'vat' => (float)($product['vat'] ?? 0), + 'quantity' => (int)($product['quantity'] ?? 0), + 'image' => $img, + ]; + } + + return $results; + } + + public function saveOrderProducts(int $orderId, array $productsData): bool + { + if ($orderId <= 0) { + return false; + } + + $currentProducts = $this->orders->orderProducts($orderId); + $currentById = []; + foreach ($currentProducts as $cp) { + $currentById[(int)$cp['id']] = $cp; + } + + $submittedIds = []; + + foreach ($productsData as $item) { + $orderProductId = (int)($item['order_product_id'] ?? 0); + $deleted = !empty($item['delete']); + + if ($deleted && $orderProductId > 0) { + // Usunięcie — zwrot na stan + $existing = isset($currentById[$orderProductId]) ? $currentById[$orderProductId] : null; + if ($existing) { + $this->adjustStock((int)$existing['product_id'], (int)$existing['quantity']); + } + $this->orders->deleteOrderProduct($orderProductId); + $submittedIds[] = $orderProductId; + continue; + } + + if ($deleted) { + continue; + } + + if ($orderProductId > 0 && isset($currentById[$orderProductId])) { + // Istniejący produkt — aktualizacja + $existing = $currentById[$orderProductId]; + $newQty = max(1, (int)($item['quantity'] ?? 1)); + $oldQty = (int)$existing['quantity']; + $qtyDiff = $oldQty - $newQty; + + $update = [ + 'quantity' => $newQty, + 'price_brutto' => (float)($item['price_brutto'] ?? $existing['price_brutto']), + 'price_brutto_promo' => (float)($item['price_brutto_promo'] ?? $existing['price_brutto_promo']), + ]; + + $this->orders->updateOrderProduct($orderProductId, $update); + + // Korekta stanu: qtyDiff > 0 = zmniejszono ilość = zwrot na stan + if ($qtyDiff !== 0) { + $this->adjustStock((int)$existing['product_id'], $qtyDiff); + } + + $submittedIds[] = $orderProductId; + } elseif ($orderProductId === 0) { + // Nowy produkt + $productId = (int)($item['product_id'] ?? 0); + $qty = max(1, (int)($item['quantity'] ?? 1)); + + $this->orders->addOrderProduct($orderId, [ + 'product_id' => $productId, + 'parent_product_id' => (int)($item['parent_product_id'] ?? $productId), + 'name' => (string)($item['name'] ?? ''), + 'attributes' => '', + 'vat' => (float)($item['vat'] ?? 0), + 'price_brutto' => (float)($item['price_brutto'] ?? 0), + 'price_brutto_promo' => (float)($item['price_brutto_promo'] ?? 0), + 'quantity' => $qty, + 'message' => '', + 'custom_fields' => '', + ]); + + // Zmniejsz stan magazynowy + $this->adjustStock($productId, -$qty); + } + } + + // Usunięte z formularza (nie przesłane) — zwrot na stan + foreach ($currentById as $cpId => $cp) { + if (!in_array($cpId, $submittedIds)) { + $this->adjustStock((int)$cp['product_id'], (int)$cp['quantity']); + $this->orders->deleteOrderProduct($cpId); + } + } + + // Przelicz koszt dostawy (próg darmowej dostawy) + $this->recalculateTransportCost($orderId); + + return true; + } + + public function getFreeDeliveryThreshold(): float + { + if (!$this->settingsRepo) { + return 0.0; + } + + return (float)$this->settingsRepo->getSingleValue('free_delivery'); + } + + private function adjustStock(int $productId, int $delta): void + { + if (!$this->productRepo || $productId <= 0 || $delta === 0) { + return; + } + + $currentQty = $this->productRepo->getQuantity($productId); + if ($currentQty === null) { + return; + } + + $newQty = max(0, $currentQty + $delta); + $this->productRepo->updateQuantity($productId, $newQty); + } + + private function recalculateTransportCost(int $orderId): void + { + $order = $this->orders->findRawById($orderId); + if (!$order) { + return; + } + + $transportId = (int)($order['transport_id'] ?? 0); + if ($transportId <= 0 || !$this->transportRepo || !$this->settingsRepo) { + return; + } + + $transport = $this->transportRepo->findActiveById($transportId); + if (!is_array($transport)) { + return; + } + + // Oblicz sumę produktów (bez dostawy) + $productsSummary = $this->calculateProductsTotal($orderId); + $freeDelivery = (float)$this->settingsRepo->getSingleValue('free_delivery'); + + if ((int)($transport['delivery_free'] ?? 0) === 1 && $freeDelivery > 0 && $productsSummary >= $freeDelivery) { + $transportCost = 0.0; + } else { + $transportCost = (float)($transport['cost'] ?? 0); + } + + $this->orders->updateTransportCost($orderId, $transportCost); + } + + private function calculateProductsTotal(int $orderId): float + { + $products = $this->orders->orderProducts($orderId); + $summary = 0.0; + + foreach ($products as $row) { + $pricePromo = (float)($row['price_brutto_promo'] ?? 0); + $price = (float)($row['price_brutto'] ?? 0); + $quantity = (float)($row['quantity'] ?? 0); + + if ($pricePromo > 0) { + $summary += $pricePromo * $quantity; + } else { + $summary += $price * $quantity; + } + } + + return $summary; + } + public function changeStatus(int $orderId, int $status, bool $sendEmail): array { $order = $this->orders->findRawById($orderId); diff --git a/autoload/Domain/Order/OrderRepository.php b/autoload/Domain/Order/OrderRepository.php index 63dc4ca..3c8c2a8 100644 --- a/autoload/Domain/Order/OrderRepository.php +++ b/autoload/Domain/Order/OrderRepository.php @@ -435,6 +435,91 @@ class OrderRepository return true; } + // --- Order product CRUD (admin) --- + + public function getOrderProduct(int $orderProductId): ?array + { + if ($orderProductId <= 0) { + return null; + } + + $row = $this->db->get('pp_shop_order_products', '*', ['id' => $orderProductId]); + + return is_array($row) ? $row : null; + } + + public function addOrderProduct(int $orderId, array $data): ?int + { + if ($orderId <= 0) { + return null; + } + + $this->db->insert('pp_shop_order_products', [ + 'order_id' => $orderId, + 'product_id' => (int)($data['product_id'] ?? 0), + 'parent_product_id' => (int)($data['parent_product_id'] ?? 0), + 'name' => (string)($data['name'] ?? ''), + 'attributes' => (string)($data['attributes'] ?? ''), + 'vat' => (float)($data['vat'] ?? 0), + 'price_brutto' => (float)($data['price_brutto'] ?? 0), + 'price_brutto_promo' => (float)($data['price_brutto_promo'] ?? 0), + 'quantity' => max(1, (int)($data['quantity'] ?? 1)), + 'message' => (string)($data['message'] ?? ''), + 'custom_fields' => (string)($data['custom_fields'] ?? ''), + ]); + + $id = $this->db->id(); + + return $id ? (int)$id : null; + } + + public function updateOrderProduct(int $orderProductId, array $data): bool + { + if ($orderProductId <= 0) { + return false; + } + + $update = []; + + if (array_key_exists('quantity', $data)) { + $update['quantity'] = max(1, (int)$data['quantity']); + } + if (array_key_exists('price_brutto', $data)) { + $update['price_brutto'] = (float)$data['price_brutto']; + } + if (array_key_exists('price_brutto_promo', $data)) { + $update['price_brutto_promo'] = (float)$data['price_brutto_promo']; + } + + if (empty($update)) { + return false; + } + + $this->db->update('pp_shop_order_products', $update, ['id' => $orderProductId]); + + return true; + } + + public function deleteOrderProduct(int $orderProductId): bool + { + if ($orderProductId <= 0) { + return false; + } + + $this->db->delete('pp_shop_order_products', ['id' => $orderProductId]); + + return true; + } + + public function updateTransportCost(int $orderId, float $cost): void + { + if ($orderId <= 0) { + return; + } + + $this->db->update('pp_shop_orders', ['transport_cost' => $cost], ['id' => $orderId]); + } + // --- Frontend methods --- public function findIdByHash(string $hash) @@ -652,6 +737,11 @@ class OrderRepository $product_price_tmp = \Domain\Basket\BasketCalculator::calculateBasketProductPrice((float)$product['price_brutto_promo'], (float)$product['price_brutto'], $coupon, $basket_position, $productRepo); + // Cena promo = 0 gdy taka sama jak cena bazowa (brak realnej promocji/kuponu) + $effectivePromoPrice = (float)$product_price_tmp['price_new']; + $effectiveBasePrice = (float)$product_price_tmp['price']; + $promoPrice = ($effectivePromoPrice != $effectiveBasePrice) ? $effectivePromoPrice : 0; + $this->db->insert('pp_shop_order_products', [ 'order_id' => $order_id, 'product_id' => $basket_position['product-id'], @@ -659,8 +749,8 @@ class OrderRepository 'name' => $product['language']['name'], 'attributes' => $attributes, 'vat' => $product['vat'], - 'price_brutto' => $product_price_tmp['price'], - 'price_brutto_promo' => $product_price_tmp['price_new'], + 'price_brutto' => $effectiveBasePrice, + 'price_brutto_promo' => $promoPrice, 'quantity' => $basket_position['quantity'], 'message' => $basket_position['message'], 'custom_fields' => $product_custom_fields, diff --git a/autoload/admin/App.php b/autoload/admin/App.php index 29e7e5f..a89b6ca 100644 --- a/autoload/admin/App.php +++ b/autoload/admin/App.php @@ -417,10 +417,15 @@ class App }, 'ShopOrder' => function() { global $mdb; + $productRepo = new \Domain\Product\ProductRepository( $mdb ); return new \admin\Controllers\ShopOrderController( new \Domain\Order\OrderAdminService( - new \Domain\Order\OrderRepository( $mdb ) - ) + new \Domain\Order\OrderRepository( $mdb ), + $productRepo, + new \Domain\Settings\SettingsRepository( $mdb ), + new \Domain\Transport\TransportRepository( $mdb ) + ), + $productRepo ); }, 'Update' => function() { diff --git a/autoload/admin/Controllers/ShopOrderController.php b/autoload/admin/Controllers/ShopOrderController.php index 015c35a..0f0bdc0 100644 --- a/autoload/admin/Controllers/ShopOrderController.php +++ b/autoload/admin/Controllers/ShopOrderController.php @@ -2,15 +2,18 @@ namespace admin\Controllers; use Domain\Order\OrderAdminService; +use Domain\Product\ProductRepository; use admin\ViewModels\Common\PaginatedTableViewModel; class ShopOrderController { private OrderAdminService $service; + private $productRepo; - public function __construct(OrderAdminService $service) + public function __construct(OrderAdminService $service, ProductRepository $productRepo = null) { $this->service = $service; + $this->productRepo = $productRepo; } public function list(): string @@ -187,12 +190,27 @@ class ShopOrderController public function order_edit(): string { $orderId = (int)\Shared\Helpers\Helpers::get('order_id'); + $transports = ( new \Domain\Transport\TransportRepository( $GLOBALS['mdb'] ) )->allActive(); + + // Dane transportów do JS (id, cost, delivery_free) + $transportsJson = []; + if (is_array($transports)) { + foreach ($transports as $t) { + $transportsJson[] = [ + 'id' => (int)$t['id'], + 'cost' => (float)$t['cost'], + 'delivery_free' => (int)($t['delivery_free'] ?? 0), + ]; + } + } return \Shared\Tpl\Tpl::view('shop-order/order-edit', [ 'order' => $this->service->details($orderId), 'order_statuses' => $this->service->statuses(), - 'transport' => ( new \Domain\Transport\TransportRepository( $GLOBALS['mdb'] ) )->allActive(), + 'transport' => $transports, 'payment_methods' => ( new \Domain\PaymentMethod\PaymentMethodRepository( $GLOBALS['mdb'] ) )->allActive(), + 'free_delivery' => $this->service->getFreeDeliveryThreshold(), + 'transports_json' => json_encode($transportsJson), ]); } @@ -203,8 +221,16 @@ class ShopOrderController public function order_save(): void { + $orderId = (int)\Shared\Helpers\Helpers::get('order_id'); + + // Zapisz produkty PRZED zapisem zamówienia (bo saveOrderByAdmin przelicza summary) + $productsData = \Shared\Helpers\Helpers::get('products'); + if (is_array($productsData)) { + $this->service->saveOrderProducts($orderId, $productsData); + } + $saved = $this->service->saveOrderByAdmin([ - 'order_id' => (int)\Shared\Helpers\Helpers::get('order_id'), + 'order_id' => $orderId, 'client_name' => (string)\Shared\Helpers\Helpers::get('client_name'), 'client_surname' => (string)\Shared\Helpers\Helpers::get('client_surname'), 'client_street' => (string)\Shared\Helpers\Helpers::get('client_street'), @@ -225,7 +251,22 @@ class ShopOrderController \Shared\Helpers\Helpers::alert('Zamówienie zostało zapisane.'); } - header('Location: /admin/shop_order/order_details/order_id=' . (int)\Shared\Helpers\Helpers::get('order_id')); + header('Location: /admin/shop_order/order_details/order_id=' . $orderId); + exit; + } + + public function search_products_ajax(): void + { + $query = trim((string)\Shared\Helpers\Helpers::get('query')); + $langId = trim((string)\Shared\Helpers\Helpers::get('lang_id')); + if ($langId === '') { + $langId = isset($_SESSION['lang_id']) ? (string)$_SESSION['lang_id'] : 'pl'; + } + + $results = $this->service->searchProducts($query, $langId); + + header('Content-Type: application/json; charset=utf-8'); + echo json_encode(['status' => 'ok', 'products' => $results]); exit; } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 223149c..ab41816 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,23 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. --- +## ver. 0.295 (2026-02-19) - Admin: edycja produktów w zamówieniu + wyszukiwanie AJAX + korekta stanów magazynowych + +- **NEW**: Edycja produktów w zamówieniu z panelu admina (dodawanie, usuwanie, zmiana ilości/cen) +- **NEW**: Wyszukiwarka produktów AJAX w formularzu edycji zamówienia (`search_products_ajax`) +- **NEW**: Automatyczna korekta stanów magazynowych przy edycji produktów zamówienia +- **NEW**: Automatyczne przeliczanie kosztu dostawy (próg darmowej dostawy) po zmianie produktów +- **NEW**: `OrderRepository` — CRUD: `addOrderProduct()`, `updateOrderProduct()`, `deleteOrderProduct()`, `getOrderProduct()`, `updateTransportCost()` +- **NEW**: `OrderAdminService` — `searchProducts()`, `saveOrderProducts()`, `getFreeDeliveryThreshold()`, `adjustStock()`, `recalculateTransportCost()` +- **NEW**: `ShopOrderController` — endpoint `search_products_ajax`, rozszerzony `order_save` o zapis produktów +- **UPDATE**: `admin\App` — rozszerzone DI wiring (ProductRepository, SettingsRepository, TransportRepository) +- **FIX**: `OrderRepository::createOrder()` — promo price = 0 gdy taka sama jak cena bazowa (brak realnej promocji) +- **NEW**: Template `order-edit-custom-script.php` — interaktywny JS do zarządzania produktami zamówienia +- **UPDATE**: Template `order-edit.php` — sekcja edycji produktów z dynamicznym formularzem +- **Tests**: `OrderAdminServiceTest` (nowy), rozszerzony `OrderRepositoryTest` (+132 linii), zaktualizowany `ShopOrderControllerTest` + +--- + ## ver. 0.294 (2026-02-19) - Code review: full codebase review complete (96/96 classes), 27 fixes across all layers **Code review zakończony — 96 klas, ~1144 metody przejrzane.** diff --git a/docs/TESTING.md b/docs/TESTING.md index cb519ce..5313b49 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -23,10 +23,10 @@ composer test # standard ## Aktualny stan ```text -OK (614 tests, 1821 assertions) +OK (636 tests, 1868 assertions) ``` -Zweryfikowano: 2026-02-19 (ver. 0.293) +Zweryfikowano: 2026-02-19 (ver. 0.295) ## Konfiguracja diff --git a/docs/UPDATE_INSTRUCTIONS.md b/docs/UPDATE_INSTRUCTIONS.md index 2c0eefd..1e6abe3 100644 --- a/docs/UPDATE_INSTRUCTIONS.md +++ b/docs/UPDATE_INSTRUCTIONS.md @@ -18,16 +18,16 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią ## Procedura tworzenia nowej aktualizacji -## Status biezacej aktualizacji (ver. 0.294) +## Status biezacej aktualizacji (ver. 0.295) -- Wersja udostepniona: `0.294` (data: 2026-02-19). +- Wersja udostepniona: `0.295` (data: 2026-02-19). - Pliki publikacyjne: - - `updates/0.20/ver_0.294.zip` + - `updates/0.20/ver_0.295.zip` - Pliki metadanych aktualizacji: - `updates/changelog.php` - - `updates/versions.php` (`$current_ver = 294`) + - `updates/versions.php` (`$current_ver = 295`) - Weryfikacja testow przed publikacja: - - `OK (614 tests, 1821 assertions)` + - `OK (636 tests, 1868 assertions)` ### 1. Określ numer wersji Sprawdź ostatnią wersję w `updates/` i zwiększ o 1. diff --git a/temp/build_294.sh b/temp/build_294.sh deleted file mode 100644 index e8af1d9..0000000 --- a/temp/build_294.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -set -e -cd "c:/visual studio code/projekty/shopPRO" - -# Files to include in update package (modified, excluding .md, tests, .phpunit) -FILES=( - admin/templates/dashboard/main-view.php - admin/templates/shop-order/order-details.php - admin/templates/shop-order/order-edit.php - admin/templates/shop-product/product-combination.php - autoload/Domain/Attribute/AttributeRepository.php - autoload/Domain/Basket/BasketCalculator.php - autoload/Domain/Category/CategoryRepository.php - autoload/Domain/Integrations/IntegrationsRepository.php - autoload/Domain/Order/OrderAdminService.php - autoload/Domain/Order/OrderRepository.php - autoload/Domain/Product/ProductRepository.php - autoload/Domain/Promotion/PromotionRepository.php - autoload/Shared/Helpers/Helpers.php - autoload/admin/Controllers/ShopOrderController.php - autoload/admin/Controllers/ShopProductController.php - autoload/admin/Controllers/ShopPromotionController.php - autoload/front/App.php - autoload/front/Controllers/ShopBasketController.php - autoload/front/Controllers/ShopClientController.php - autoload/front/Controllers/ShopOrderController.php - autoload/front/Controllers/ShopProductController.php - autoload/front/LayoutEngine.php - autoload/front/Controllers/SearchController.php - autoload/front/Views/ShopSearch.php - cron-turstmate.php - cron.php - index.php - templates/controls/alert-product-sets.php - templates/shop-basket/_partials/product-custom-fields.php - templates/shop-basket/alert-product-sets.php - templates/shop-basket/basket-details.php - templates/shop-basket/summary-view.php - templates/shop-category/blog-category-products.php - templates/shop-category/category-infinitescroll.php - templates/shop-category/category.php - templates/shop-category/products.php - templates/shop-client/client-orders.php - templates/shop-order/mail-summary.php - templates/shop-order/order-details.php - templates/shop-order/order-simple.php - templates/shop-producer/products.php - templates/shop-product/_partial/product-attribute.php - templates/shop-product/_partial/product-meta.php - templates/shop-product/_partial/product-warehouse-message.php - templates/shop-product/product-mini.php - templates/shop-product/product.php - templates/shop-product/products-box.php - templates/shop-product/products-new.php - templates/shop-product/products-top.php - templates/shop-product/promoted-products.php - templates/shop-search/product-search.php - templates/shop-search/products.php - updates/changelog.php - updates/versions.php -) - -# Create directory structure and copy files -for f in "${FILES[@]}"; do - dir=$(dirname "$f") - mkdir -p "temp/temp_294/$dir" - cp "$f" "temp/temp_294/$f" -done - -echo "Copied ${#FILES[@]} files to temp/temp_294/" diff --git a/temp/update_build/delete_files_0.273.txt b/temp/update_build/delete_files_0.273.txt deleted file mode 100644 index 2f1a8af..0000000 --- a/temp/update_build/delete_files_0.273.txt +++ /dev/null @@ -1,9 +0,0 @@ -admin/templates/shop-producer/edit.php -admin/templates/shop-producer/list.php -autoload/admin/controls/class.ShopProducer.php -autoload/admin/factory/class.Languages.php -autoload/admin/factory/class.Layouts.php -autoload/admin/factory/class.Newsletter.php -autoload/admin/factory/class.Scontainers.php -autoload/admin/factory/class.ShopProducer.php -autoload/admin/factory/class.ShopTransport.php diff --git a/temp/update_build/delete_files_0.274.txt b/temp/update_build/delete_files_0.274.txt deleted file mode 100644 index cac62ca..0000000 --- a/temp/update_build/delete_files_0.274.txt +++ /dev/null @@ -1 +0,0 @@ -# brak plikow do usuniecia w ver. 0.274 diff --git a/temp/update_build/delete_files_0.275.txt b/temp/update_build/delete_files_0.275.txt deleted file mode 100644 index 98ab009..0000000 --- a/temp/update_build/delete_files_0.275.txt +++ /dev/null @@ -1,3 +0,0 @@ -autoload/admin/controls/class.ShopCategory.php -autoload/admin/factory/class.ShopCategory.php -autoload/admin/view/class.ShopCategory.php diff --git a/temp/update_build/tmp_0.275/admin/ajax/shop-category.php b/temp/update_build/tmp_0.275/admin/ajax/shop-category.php deleted file mode 100644 index a16c6b6..0000000 --- a/temp/update_build/tmp_0.275/admin/ajax/shop-category.php +++ /dev/null @@ -1,15 +0,0 @@ - 'error', 'msg' => 'Podczas zapisywania kolejności wyświetlania produktów wystąpił błąd. Proszę spróbować ponownie.' ]; - - $categoryRepository = new \Domain\Category\CategoryRepository( $mdb ); - - if ( $categoryRepository->saveProductOrder( \S::get( 'category_id' ), \S::get( 'products' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; -} diff --git a/temp/update_build/tmp_0.275/admin/ajax/shop.php b/temp/update_build/tmp_0.275/admin/ajax/shop.php deleted file mode 100644 index 48af10e..0000000 --- a/temp/update_build/tmp_0.275/admin/ajax/shop.php +++ /dev/null @@ -1,60 +0,0 @@ - 'error', 'msg' => 'Podczas zapisywania kolejności kategorii wystąpił błąd. Proszę spróbować ponownie.' ]; - - $categoryRepository = new \Domain\Category\CategoryRepository( $mdb ); - - if ( $categoryRepository->saveCategoriesOrder( \S::get( 'categories' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; -} - -if ( $a == 'product_file_delete' ) -{ - $response = [ 'status' => 'error', 'msg' => 'Podczas usuwania załącznika wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::delete_file( \S::get( 'file_id' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; -} - -if ( $a == 'product_file_name_change' ) -{ - $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany nazwy załącznika wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::file_name_change( \S::get( 'file_id' ), \S::get( 'file_name' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; -} - -if ( $a == 'product_image_delete' ) -{ - $response = [ 'status' => 'error', 'msg' => 'Podczas usuwania zdjecia wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::delete_img( \S::get( 'image_id' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; -} diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/categories-list-custom-script.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/categories-list-custom-script.php deleted file mode 100644 index 16b4953..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-category/categories-list-custom-script.php +++ /dev/null @@ -1,153 +0,0 @@ - - - diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/categories-list.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/categories-list.php deleted file mode 100644 index 3a4ef4c..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-category/categories-list.php +++ /dev/null @@ -1,61 +0,0 @@ - - - id = 'pages-list'; -$grid -> gdb_opt = $gdb; -$grid -> include_plugins = true; -$grid -> title = 'Lista kategorii'; -$grid -> default_buttons = false; -$grid -> buttons = [ - [ - 'label' => 'Dodaj kategorię', - 'url' => '/admin/shop_category/category_edit/', - 'icon' => 'fa-plus-circle', - 'class' => 'btn-success' - ] - ]; -$grid -> external_code = $out; - -echo $grid -> draw(); -?> - \ No newline at end of file diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-browse-custom-script.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/category-browse-custom-script.php deleted file mode 100644 index a27a2e0..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-browse-custom-script.php +++ /dev/null @@ -1,106 +0,0 @@ - - - diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-browse-list.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/category-browse-list.php deleted file mode 100644 index 0ad148f..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-browse-list.php +++ /dev/null @@ -1,59 +0,0 @@ - - - id = 'pages-list'; -$grid -> gdb_opt = $gdb; -$grid -> include_plugins = false; -$grid -> title = 'Lista kategorii'; -$grid -> default_buttons = false; -$grid -> external_code = $out; - -echo $grid -> draw(); -?> - - \ No newline at end of file diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-edit-custom-script.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/category-edit-custom-script.php deleted file mode 100644 index bd2a72d..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-edit-custom-script.php +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-edit.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/category-edit.php deleted file mode 100644 index 9053b6c..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-edit.php +++ /dev/null @@ -1,209 +0,0 @@ - - - -
- -
-
-
-
    - languages ) ): foreach ( $this -> languages as $lg ):?> - -
  • ';?>
  • - - -
-
- languages ) ): foreach ( $this -> languages as $lg ):?> - -
- 'Nazwa kategorii', - 'name' => 'title[' . $lg['id'] . ']', - 'id' => 'title_' . $lg['id'], - 'value' => $this -> category[ 'languages' ][ $lg['id'] ]['title'], - 'inline' => true - ) - );?> - 'Opis kategorii', - 'name' => 'text[' . $lg['id'] . ']', - 'id' => 'text_' . $lg['id'], - 'value' => $this -> category['languages'][ $lg['id'] ]['text'], - 'inline' => true - ) - );?> - 'Opis kategorii (rozwinięcie)', - 'name' => 'text_hidden[' . $lg['id'] . ']', - 'id' => 'text_hidden_' . $lg['id'], - 'value' => $this -> category['languages'][ $lg['id'] ]['text_hidden'], - 'inline' => true - ) - );?> - 'Dodatkowy tekst (nad produktami)', - 'name' => 'additional_text[' . $lg['id'] . ']', - 'id' => 'additional_text_' . $lg['id'], - 'value' => $this -> category['languages'][ $lg['id'] ]['additional_text'], - 'inline' => true - ] );?> - -
- - -
-
-
-
-
- 'Aktywna', - 'name' => 'status', - 'checked' => $this -> category['status'] == 1 or !$this -> category['id'] ? true : false - ) - );?> - 'Sortowanie produktów', - 'name' => 'sort_type', - 'id' => 'sort_type', - 'values' => is_array( $this -> sort_types ) ? $this -> sort_types : [], - 'value' => $this -> category['sort_type'] - ] - );?> - 'Wyświetlić podkategorie', - 'name' => 'view_subcategories', - 'checked' => $this -> category['view_subcategories'] == 1 ? true : false - ) - );?> -
-
-
-
    - languages ) ): foreach ( $this -> languages as $lg ):?> - -
  • ';?>
  • - - -
-
- languages ) ): foreach ( $this -> languages as $lg ):?> - -
- 'Link SEO', - 'name' => 'seo_link[' . $lg['id'] . ']', - 'id' => 'seo_link_' . $lg['id'], - 'value' => $this -> category['languages' ][ $lg['id'] ]['seo_link'], - 'icon_content' => 'generuj', - 'icon_js' => 'generate_seo_links( "' . $lg['id'] . '", $( "#title_' . $lg['id'] . '" ).val(), ' . (int)$this -> category['id'] . ' );' - ) - );?> - 'Tytuł kategorii (h1)', - 'name' => 'category_title[' . $lg['id'] . ']', - 'id' => 'category_title_' . $lg['id'], - 'value' => $this -> category['languages' ][ $lg['id'] ]['category_title'] - ) - );?> - 'Meta title', - 'name' => 'meta_title[' . $lg['id'] . ']', - 'id' => 'meta_title_' . $lg['id'], - 'value' => $this -> category['languages'][ $lg['id'] ]['meta_title'] - ) - );?> - 'Meta description', - 'name' => 'meta_description[' . $lg['id'] . ']', - 'id' => 'meta_description_' . $lg['id'], - 'value' => $this -> category['languages'][ $lg['id'] ]['meta_description'] - ) - );?> - 'Meta keywords', - 'name' => 'meta_keywords[' . $lg['id'] . ']', - 'id' => 'meta_keywords_' . $lg['id'], - 'value' => $this -> category['languages'][ $lg['id'] ]['meta_keywords'] - ) - );?> - 'Blokuj indeksację', - 'name' => 'noindex[' . $lg['id'] . ']', - 'id' => 'noindex_' . $lg['id'], - 'values' => array( - 0 => 'nie', 1 => 'tak' - ), - 'value' => $this -> category['languages'][ $lg['id'] ]['noindex'] == 1 ? 1 : 0 - ) - );?> -
- - -
-
-
-
-
-
- id = 'category-edit'; -$grid -> gdb_opt = $gdb; -$grid -> include_plugins = true; -$grid -> title = 'Edycja kategorii'; -$grid -> fields = [ - [ - 'db' => 'id', - 'type' => 'hidden', - 'value' => $this -> category['id'] - ], - [ - 'db' => 'parent_id', - 'type' => 'hidden', - 'value' => $this -> category['id'] ? $this -> category['parent_id'] : $this -> pid - ] - ]; -$grid -> actions = [ - 'save' => [ 'url' => '/admin/shop_category/save/', 'back_url' => '/admin/shop_category/view_list/' ], - 'cancel' => [ 'url' => '/admin/shop_category/view_list/' ] - ]; -$grid -> external_code = $out; -$grid -> persist_edit = true; -$grid -> id_param = 'id'; - -echo $grid -> draw(); -?> - - - diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products-custom-script.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products-custom-script.php deleted file mode 100644 index 3a43fa0..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products-custom-script.php +++ /dev/null @@ -1,62 +0,0 @@ - - - diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products.php deleted file mode 100644 index 7885fd8..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products.php +++ /dev/null @@ -1,37 +0,0 @@ - -
    - products ) ) foreach ( $this -> products as $product ) - { - ?> -
  1. -
    -
  2. - -
- gdb_opt = $gdb; -$grid -> include_plugins = true; -$grid -> default_buttons = false; -$grid -> external_code = $out; -$grid -> title = 'Lista produktów'; -$grid -> buttons = [ - [ - 'label' => 'Wstecz', - 'url' => '/admin/shop_category/view_list/', - 'icon' => 'fa-reply', - 'class' => 'btn-dark' - ] - ]; -echo $grid -> draw(); -?> - $this -> category_id ] ); ?> \ No newline at end of file diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategories-list.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategories-list.php deleted file mode 100644 index 739fe25..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategories-list.php +++ /dev/null @@ -1,36 +0,0 @@ - categories ) ):?> -
    - categories as $category ):?> -
  1. -
    - - ';?> - dlang]['title'];?> - -
    - ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->subcategories( $category['id'] ), - 'level' => $this -> level + 1, - 'dlang' => $this -> dlang - ] );?> -
  2. - -
- \ No newline at end of file diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategory-browse-list.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategory-browse-list.php deleted file mode 100644 index 1fafb54..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategory-browse-list.php +++ /dev/null @@ -1,25 +0,0 @@ - categories ) ):?> -
    - categories as $category ):?> -
  1. -
    - - ';?> - dlang]['title'];?> - -
    - ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->subcategories( $category['id'] ), - 'level' => $this -> level + 1, - 'dlang' => $this -> dlang - ] );?> -
  2. - -
- \ No newline at end of file diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-product/mass-edit.php b/temp/update_build/tmp_0.275/admin/templates/shop-product/mass-edit.php deleted file mode 100644 index 0823132..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-product/mass-edit.php +++ /dev/null @@ -1,114 +0,0 @@ - - - - - -
-
- Masowa edycja produktów -
-
- - -
-
-
- -
-
-
- - - - -
-
-
-
-
-
-
-
- products ) ): foreach ( $this->products as $key => $product ): ?> -
- - -
- -
-
- -
-
-
-
- - diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-product/product-edit.php b/temp/update_build/tmp_0.275/admin/templates/shop-product/product-edit.php deleted file mode 100644 index 8d1519c..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-product/product-edit.php +++ /dev/null @@ -1,1348 +0,0 @@ - - - - $this -> user['id'], - 'expires' => time() + 60*20 -]; - -$_SESSION['rfm_akey'] = bin2hex(random_bytes(16)); -$_SESSION['rfm_akey_expires'] = time() + 20*60; -$_SESSION['can_use_rfm'] = true; -$rfmAkeyJS = $_SESSION['rfm_akey']; - -ob_start(); -?> - -
-
    -
  • Opis
  • -
  • Zakładki
  • -
  • Cena
  • -
  • Magazyn
  • -
  • Ustawienia
  • -
  • SEO
  • -
  • Wyświetlanie
  • -
  • Galeria
  • -
  • Załączniki
  • -
  • Produkty powiązane
  • -
  • XML
  • -
  • Dodatkowe pola
  • -
  • GPSR
  • -
-
-
-
-
    - languages)) : foreach ($this->languages as $lg) : ?> - -
  • '; ?>
  • - - -
-
- languages)) : foreach ($this->languages as $lg) : ?> - languages)) - foreach ($this->languages as $lg_tmp) - { - if ($lg_tmp['id'] != $lg['id']) - $languages[$lg_tmp['id']] = $lg_tmp['name']; - } - ?> - -
- 'Wyświetlaj treść z wersji', - 'name' => 'copy_from[' . $lg['id'] . ']', - 'values' => $languages, - 'value' => $this->product['languages'][$lg['id']]['copy_from'], - ) - ); - ?> - 'Nazwa', - 'name' => 'name[' . $lg['id'] . ']', - 'id' => 'name_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['name'], - 'inline' => true - ) - ); - ?> - 'Komunikat gdy stan magazynowy równy 0', - 'name' => 'warehouse_message_zero[' . $lg['id'] . ']', - 'id' => 'warehouse_message_zero_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['warehouse_message_zero'], - 'inline' => true - ) - ); - ?> - 'Komunikat gdy stan magazynowy większy niż 0', - 'name' => 'warehouse_message_nonzero[' . $lg['id'] . ']', - 'id' => 'warehouse_message_nonzero_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['warehouse_message_nonzero'], - 'inline' => true - ) - ); - ?> - 'Krótki opis', - 'name' => 'short_description[' . $lg['id'] . ']', - 'id' => 'short_description_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['short_description'], - 'inline' => true - ) - ); - ?> - 'Opis', - 'name' => 'description[' . $lg['id'] . ']', - 'id' => 'description_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['description'], - 'inline' => true - ) - ); - ?> - -
- - -
-
-
-
-
-
-
    - languages)) : foreach ($this->languages as $lg) : ?> - -
  • '; ?>
  • - - -
-
- languages)) : foreach ($this->languages as $lg) : ?> - languages)) - foreach ($this->languages as $lg_tmp) - { - if ($lg_tmp['id'] != $lg['id']) - $languages[$lg_tmp['id']] = $lg_tmp['name']; - } - ?> - -
- 'Nazwa zakładki (1)', - 'name' => 'tab_name_1[' . $lg['id'] . ']', - 'id' => 'tab_name_1_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['tab_name_1'], - 'inline' => true - ) - ); - ?> - 'Zawartość zakładki (1)', - 'name' => 'tab_description_1[' . $lg['id'] . ']', - 'id' => 'tab_description_1_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['tab_description_1'], - 'inline' => true - ) - ); - ?> - 'Nazwa zakładki (2)', - 'name' => 'tab_name_2[' . $lg['id'] . ']', - 'id' => 'tab_name_2_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['tab_name_2'], - 'inline' => true - ) - ); - ?> - 'Zawartość zakładki (2)', - 'name' => 'tab_description_2[' . $lg['id'] . ']', - 'id' => 'tab_description_2_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['tab_description_2'], - 'inline' => true - ) - ); - ?> - -
- - -
-
-
-
-
- 'VAT (%)', - 'name' => 'vat', - 'id' => 'vat', - 'class' => 'int-format', - 'value' => $this->product['id'] ? $this->product['vat'] : 23, - 'onchange' => 'calculate_price_brutto(); return false;' - ) - ); - ?> - 'Cena netto (PLN)', - 'name' => 'price_netto', - 'id' => 'price_netto', - 'class' => 'number-format', - 'value' => $this->product['price_netto'], - 'onchange' => 'calculate_price_brutto(); return false;' - ) - ); - ?> - 'Cena brutto (PLN)', - 'name' => 'price_brutto', - 'id' => 'price_brutto', - 'class' => 'number-format', - 'value' => $this->product['price_brutto'], - 'onchange' => 'calculate_price_netto(); return false;' - ) - ); - ?> - 'Promocyjna cena netto (PLN)', - 'name' => 'price_netto_promo', - 'id' => 'price_netto_promo', - 'class' => 'number-format', - 'value' => $this->product['price_netto_promo'], - 'onchange' => 'calculate_price_brutto_promo(); return false;' - ) - ); - ?> - 'Promocyjna cena brutto (PLN)', - 'name' => 'price_brutto_promo', - 'id' => 'price_brutto_promo', - 'class' => 'number-format', - 'value' => $this->product['price_brutto_promo'], - 'onchange' => 'calculate_price_netto_promo(); return false;' - ) - ); - ?> - - units as $unit) - $units[$unit['id']] = $unit['text']; - ?> - 'Jednostka miary', - 'name' => 'product_unit', - 'id' => 'product_unit', - 'values' => $units, - 'value' => $this->product['product_unit_id'] - ]); ?> - 'Waga/pojemność', - 'name' => 'weight', - 'id' => 'weight', - 'class' => 'number-format', - 'value' => $this->product['weight'] - ]); ?> -
-
- 'Stan magazynowy', - 'name' => 'quantity', - 'id' => 'quantity', - 'class' => 'int-format', - 'value' => $this->product['quantity'] - ]); - ?> - 'Pozwól zamawiać gdy stan 0', - 'name' => 'stock_0_buy', - 'checked' => $this->product['stock_0_buy'] == 1 ? true : false - ]); - ?> - 'Współczynnik WP', - 'name' => 'wp', - 'id' => 'wp', - 'class' => 'number-format', - 'value' => $this->product['wp'] - ]); - ?> - 'Kod SKU', - 'name' => 'sku', - 'id' => 'sku', - 'value' => $this->product['sku'], - 'icon_content' => 'generuj', - 'icon_js' => 'generate_sku_code( ' . (int)$this->product['id'] . ' );' - ]); ?> - 'EAN', - 'name' => 'ean', - 'id' => 'ean', - 'value' => $this->product['ean'] - ]); ?> -
-
- 'Widoczny', - 'name' => 'status', - 'checked' => $this->product['status'] == 1 or !$this->product['id'] ? true : false - ]); - ?> - 'Promowany', - 'name' => 'promoted', - 'checked' => $this->product['promoted'] == 1 ? true : false - ]); - ?> - 'Nowość do dnia', - 'name' => 'new_to_date', - 'id' => 'new_to_date', - 'class' => 'date', - 'value' => $this->product['new_to_date'] - ]); - ?> - 'Wyświetlaj pole na dodatkową wiadomość', - 'name' => 'additional_message', - 'checked' => $this->product['additional_message'] == 1 ? true : false - ]); - ?> - 'Dodatkowa wiadomość jest wymagana', - 'name' => 'additional_message_required', - 'checked' => $this->product['additional_message_required'] == 1 ? true : false - ]); - ?> - 'Dodatkowa wiadomość (treść komunikatu)', - 'name' => 'additional_message_text', - 'id' => 'additional_message_text', - 'value' => $this->product['additional_message_text'] - ]); - ?> -
-
-
-
    - languages)) : foreach ($this->languages as $lg) : ?> - -
  • '; ?>
  • - - -
-
- languages)) : foreach ($this->languages as $lg) : ?> - -
- 'Link SEO', - 'name' => 'seo_link[' . $lg['id'] . ']', - 'id' => 'seo_link_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['seo_link'], - 'icon_content' => 'generuj', - 'icon_js' => 'generate_seo_links( "' . $lg['id'] . '", $( "#name_' . $lg['id'] . '" ).val(), ' . (int)$this->product['id'] . ' );' - ) - ); - ?> - 'Meta title', - 'name' => 'meta_title[' . $lg['id'] . ']', - 'id' => 'meta_title_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['meta_title'] - ]); ?> - 'Meta description', - 'name' => 'meta_description[' . $lg['id'] . ']', - 'id' => 'meta_description_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['meta_description'] - ) - ); - ?> - 'Meta keywords', - 'name' => 'meta_keywords[' . $lg['id'] . ']', - 'id' => 'meta_keywords_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['meta_keywords'] - ) - ); - ?> - 'Canonical', - 'name' => 'canonical[' . $lg['id'] . ']', - 'id' => 'canonical_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['canonical'] - ]); ?> -
- - -
-
-
-
-
- layouts)) : foreach ($this->layouts as $layout) : - $layouts[$layout['id']] = $layout['name']; - endforeach; - endif; - ?> - 'Szablon', - 'name' => 'layout_id', - 'id' => 'layout_id', - 'values' => $layouts, - 'value' => $this->product['layout_id'] - ]); - ?> -
- -
- -
-
-
-
-
-
    - product['images'])) : foreach ($this->product['images'] as $img) : - ?> -
  • - - - - - -
  • - -
-
You browser doesn't have Flash installed.
-
-
-
    - product['files'])) : foreach ($this->product['files'] as $file) : - - if ($file['name']) - $name = $file['name']; - else - { - $name = explode('/', $file['src']); - $name = $name[count($name) - 1]; - } - ?> -
  • -
    - - - - -
    -
  • - -
-
You browser doesn't have Flash installed.
-
-
-
- -
- -
-
-
- -
- -
-
-
-
-
- -
- - languages)) : foreach ($this->languages as $lg) : ?> - -
- -
- -
-
- - -
-
- 'Custom label 0', - 'name' => 'custom_label_0', - 'id' => 'custom_label_0', - 'value' => $this->product['custom_label_0'] - ]); - ?> - 'Custom label 1', - 'name' => 'custom_label_1', - 'id' => 'custom_label_1', - 'value' => $this->product['custom_label_1'] - ]); - ?> - 'Custom label 2', - 'name' => 'custom_label_2', - 'id' => 'custom_label_2', - 'value' => $this->product['custom_label_2'] - ]); - ?> - 'Custom label 3', - 'name' => 'custom_label_3', - 'id' => 'custom_label_3', - 'value' => $this->product['custom_label_3'] - ]); - ?> - 'Custom label 4', - 'name' => 'custom_label_4', - 'id' => 'custom_label_4', - 'value' => $this->product['custom_label_4'] - ]); - ?> -
-
- dodaj niestandardowe pole -
- product['custom_fields'] ) ) : foreach ( $this->product['custom_fields'] as $field ):?> - -
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
- usuń -
-
-
- -
-
-
- producers as $producer) - $producers[$producer['id']] = $producer['name']; - ?> - 'Producent', - 'name' => 'producer_id', - 'id' => 'producer_id', - 'values' => $producers, - 'value' => $this->product['producer_id'] - ]); ?> -
-
    - languages ) ): foreach ( $this -> languages as $lg ):?> - -
  • '; ?>
  • - - -
-
- languages ) ): foreach ( $this -> languages as $lg ):?> - -
- 'Informacje o bezpieczeństwie ('.$lg['name'].')', - 'name' => 'security_information[' . $lg['id'] . ']', - 'id' => 'security_information_' . $lg['id'], - 'value' => $this->product['languages'][$lg['id']]['security_information'] - ] );?> -
- - - -
-
-
-
-
-
-id = 'product-edit'; -$grid->gdb_opt = $gdb; -$grid->include_plugins = true; -$grid->title = $this->product['id'] ? 'Edycja produktu: ' . $this->product['languages'][\front\factory\Languages::default_language()]['name'] . '' : 'Edycja produktu'; -$grid->fields = [ - [ - 'db' => 'id', - 'type' => 'hidden', - 'value' => $this->product['id'] - ] -]; -$grid->actions = [ - 'save' => ['url' => '/admin/shop_product/save/', 'back_url' => '/admin/shop_product/view_list/'], - 'cancel' => ['url' => '/admin/shop_product/view_list/'] -]; -$grid->buttons = [ - [ - 'label' => 'Podgląd', - 'id' => 'product-preview', - 'url' => '#', - 'icon' => 'fa-search', - 'class' => 'btn-primary' - ] -]; -$grid->external_code = $out; -$grid->persist_edit = true; -$grid->id_param = 'id'; - -echo $grid->draw(); -?> - - - - - - - - - - - - - - - - - - diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-product/subcategories-list.php b/temp/update_build/tmp_0.275/admin/templates/shop-product/subcategories-list.php deleted file mode 100644 index 5c7f796..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/shop-product/subcategories-list.php +++ /dev/null @@ -1,24 +0,0 @@ - categories ) ):?> -
    - categories as $category ):?> -
  1. -
    - - ';?> - product_categories ) and in_array( $category[ 'id' ], $this -> product_categories ) ):?>checked="checked" /> - dlang ][ 'title' ];?> -
    - ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->subcategories( $category[ 'id' ] ), - 'product_categories' => $this -> product_categories, - 'dlang' => $this -> dlang, - 'name' => $this -> name - ] ); - ?> -
  2. - -
- \ No newline at end of file diff --git a/temp/update_build/tmp_0.275/admin/templates/site/main-layout.php b/temp/update_build/tmp_0.275/admin/templates/site/main-layout.php deleted file mode 100644 index e090e08..0000000 --- a/temp/update_build/tmp_0.275/admin/templates/site/main-layout.php +++ /dev/null @@ -1,273 +0,0 @@ - - - - - shopPro - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
-
-
-
-
- -
-
- -
-
- -
-
-
- - - -
-
-
- -
-
- content;?> -
-
-
-
-
- - - diff --git a/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopCategoryController.php b/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopCategoryController.php deleted file mode 100644 index 61d70b5..0000000 --- a/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopCategoryController.php +++ /dev/null @@ -1,163 +0,0 @@ -repository = $repository; - $this->languagesRepository = $languagesRepository; - } - - public function view_list(): string - { - return \Tpl::view('shop-category/categories-list', [ - 'categories' => $this->repository->subcategories(null), - 'level' => 0, - 'dlang' => \front\factory\Languages::default_language(), - ]); - } - - public function list(): string - { - return $this->view_list(); - } - - public function category_edit(): string - { - return \Tpl::view('shop-category/category-edit', [ - 'category' => $this->repository->categoryDetails(\S::get('id')), - 'pid' => \S::get('pid'), - 'languages' => $this->languagesRepository->languagesList(), - 'sort_types' => $this->repository->sortTypes(), - ]); - } - - public function edit(): string - { - return $this->category_edit(); - } - - public function save(): void - { - $response = [ - 'status' => 'error', - 'msg' => 'Podczas zapisywania kategorii wystąpił błąd. Proszę spróbować ponownie.', - ]; - - $values = json_decode((string)\S::get('values'), true); - if (is_array($values)) { - $savedId = $this->repository->save($values); - if (!empty($savedId)) { - $response = [ - 'status' => 'ok', - 'msg' => 'Kategoria została zapisana.', - 'id' => (int)$savedId, - ]; - } - } - - echo json_encode($response); - exit; - } - - public function category_delete(): void - { - if ($this->repository->categoryDelete(\S::get('id'))) { - \S::set_message('Kategoria została usunięta.'); - } else { - \S::alert('Podczas usuwania kategorii wystąpił błąd. Aby usunąć kategorię nie może ona posiadać przypiętych podkategorii.'); - } - - header('Location: /admin/shop_category/view_list/'); - exit; - } - - public function delete(): void - { - $this->category_delete(); - } - - public function category_products(): string - { - return \Tpl::view('shop-category/category-products', [ - 'category_id' => \S::get('id'), - 'products' => $this->repository->categoryProducts((int)\S::get('id')), - ]); - } - - public function products(): string - { - return $this->category_products(); - } - - public function category_url_browser(): void - { - echo \Tpl::view('shop-category/category-browse-list', [ - 'categories' => $this->repository->subcategories(null), - 'level' => 0, - 'dlang' => \front\factory\Languages::default_language(), - ]); - exit; - } - - public function save_categories_order(): void - { - $response = [ - 'status' => 'error', - 'msg' => 'Podczas zapisywania kolejności kategorii wystąpił błąd. Proszę spróbować ponownie.', - ]; - - if ( $this->repository->saveCategoriesOrder( \S::get( 'categories' ) ) ) { - $response = [ 'status' => 'ok' ]; - } - - echo json_encode( $response ); - exit; - } - - public function save_products_order(): void - { - $response = [ - 'status' => 'error', - 'msg' => 'Podczas zapisywania kolejności wyświetlania produktów wystąpił błąd. Proszę spróbować ponownie.', - ]; - - if ( $this->repository->saveProductOrder( \S::get( 'category_id' ), \S::get( 'products' ) ) ) { - $response = [ 'status' => 'ok' ]; - } - - echo json_encode( $response ); - exit; - } - - public function cookie_categories(): void - { - $categoryId = (string) \S::get( 'category_id' ); - if ( $categoryId === '' ) { - echo json_encode( [ 'status' => 'error' ] ); - exit; - } - - $array = []; - if ( isset( $_COOKIE['cookie_categories'] ) ) { - $tmp = @unserialize( (string) $_COOKIE['cookie_categories'] ); - if ( is_array( $tmp ) ) { - $array = $tmp; - } - } - - $array[$categoryId] = isset( $array[$categoryId] ) && (int) $array[$categoryId] === 1 ? 0 : 1; - - setcookie( 'cookie_categories', serialize( $array ), time() + 3600 * 24 * 365, '/' ); - - echo json_encode( [ 'status' => 'ok' ] ); - exit; - } -} diff --git a/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopProductController.php b/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopProductController.php deleted file mode 100644 index 3cb3372..0000000 --- a/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopProductController.php +++ /dev/null @@ -1,73 +0,0 @@ -repository = $repository; - } - - /** - * Widok masowej edycji produktów. - */ - public function mass_edit(): string - { - $categoryRepository = new CategoryRepository( $GLOBALS['mdb'] ); - - return \Tpl::view( 'shop-product/mass-edit', [ - 'products' => $this->repository->allProductsForMassEdit(), - 'categories' => $categoryRepository->subcategories( null ), - 'dlang' => \front\factory\Languages::default_language() - ] ); - } - - /** - * AJAX: zastosowanie rabatu procentowego na zaznaczonych produktach. - */ - public function mass_edit_save(): void - { - $discountPercent = \S::get( 'discount_percent' ); - $products = \S::get( 'products' ); - - if ( $discountPercent != '' && $products && is_array( $products ) && count( $products ) > 0 ) { - $productId = (int) $products[0]; - $result = $this->repository->applyDiscountPercent( $productId, (float) $discountPercent ); - - if ( $result !== null ) { - echo json_encode( [ - 'status' => 'ok', - 'price_brutto_promo' => $result['price_brutto_promo'], - 'price_brutto' => $result['price_brutto'] - ] ); - exit; - } - } - - echo json_encode( [ 'status' => 'error' ] ); - exit; - } - - /** - * AJAX: pobranie ID produktów z danej kategorii. - */ - public function get_products_by_category(): void - { - $categoryId = (int) \S::get( 'category_id' ); - $products = $this->repository->getProductsByCategory( $categoryId ); - - echo json_encode( [ 'status' => 'ok', 'products' => $products ] ); - exit; - } -} diff --git a/temp/update_build/tmp_0.275/autoload/admin/class.Site.php b/temp/update_build/tmp_0.275/autoload/admin/class.Site.php deleted file mode 100644 index 2d6c1ad..0000000 --- a/temp/update_build/tmp_0.275/autoload/admin/class.Site.php +++ /dev/null @@ -1,493 +0,0 @@ - $user['login'], - 'ts' => time() - ]; - - $json = json_encode($payloadArr, JSON_UNESCAPED_SLASHES); - $sig = hash_hmac('sha256', $json, self::APP_SECRET_KEY); - $payload = base64_encode($json . '.' . $sig); - - setcookie( $cookie_name, $payload, [ - 'expires' => time() + (86400 * 14), - 'path' => '/', - 'domain' => $domain, - 'secure' => true, - 'httponly' => true, - 'samesite' => 'Lax', - ]); - } - } - - public static function special_actions() - { - global $mdb; - - $sa = \S::get('s-action'); - $domain = preg_replace('/^www\./', '', $_SERVER['SERVER_NAME']); - $cookie_name = 'admin_remember_' . str_replace( '.', '-', $domain ); - $users = new \Domain\User\UserRepository($mdb); - - switch ($sa) - { - case 'user-logon': - { - $login = \S::get('login'); - $pass = \S::get('password'); - - $result = $users->logon($login, $pass); - - if ( $result == 1 ) - { - $user = $users->details($login); - - if ( $user['twofa_enabled'] == 1 ) - { - \S::set_session( 'twofa_pending', [ - 'uid' => (int)$user['id'], - 'login' => $login, - 'remember' => (bool)\S::get('remember'), - 'started' => time(), - ] ); - - if ( !$users->sendTwofaCode( (int)$user['id'] ) ) - { - \S::alert('Nie udało się wysłać kodu 2FA. Spróbuj ponownie.'); - \S::delete_session('twofa_pending'); - header('Location: /admin/'); - exit; - } - - header('Location: /admin/user/twofa/'); - exit; - } - else - { - $user = $users->details($login); - - self::finalize_admin_login( - $user, - $domain, - $cookie_name, - (bool)\S::get('remember') - ); - - header('Location: /admin/articles/list/'); - exit; - } - } - else - { - if ($result == -1) - { - \S::alert('Z powodu 5 nieudanych prób Twoje konto zostało zablokowane.'); - } - else - { - \S::alert('Podane hasło jest nieprawidłowe lub użytkownik nie istnieje.'); - } - header('Location: /admin/'); - exit; - } - } - break; - - case 'user-2fa-verify': - { - $pending = \S::get_session('twofa_pending'); - if ( !$pending || empty( $pending['uid'] ) ) { - \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.'); - header('Location: /admin/'); - exit; - } - - $code = trim((string)\S::get('twofa')); - if (!preg_match('/^\d{6}$/', $code)) - { - \S::alert('Nieprawidłowy format kodu.'); - header('Location: /admin/user/twofa/'); - exit; - } - - $ok = $users->verifyTwofaCode((int)$pending['uid'], $code); - if (!$ok) - { - \S::alert('Błędny lub wygasły kod.'); - header('Location: /admin/user/twofa/'); - exit; - } - - // 2FA OK - finalna sesja - $user = $users->details($pending['login']); - - self::finalize_admin_login( - $user, - $domain, - $cookie_name, - $pending['remember'] ? true : false - ); - - header('Location: /admin/articles/list/'); - exit; - } - break; - - case 'user-2fa-resend': - { - $pending = \S::get_session('twofa_pending'); - if (!$pending || empty($pending['uid'])) - { - \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.'); - header('Location: /admin/'); - exit; - } - - if (!$users->sendTwofaCode((int)$pending['uid'], true)) - { - \S::alert('Kod można wysłać ponownie po krótkiej przerwie.'); - } - else - { - \S::alert('Nowy kod został wysłany.'); - } - header('Location: /admin/user/twofa/'); - exit; - } - break; - - case 'user-logout': - { - setcookie($cookie_name, "", [ - 'expires' => time() - 86400, - 'path' => '/', - 'domain' => $domain, - 'secure' => true, - 'httponly' => true, - 'samesite' => 'Lax', - ]); - \S::delete_session('twofa_pending'); - session_destroy(); - header('Location: /admin/'); - exit; - } - break; - } - } - - /** - * Mapa nowych kontrolerów: module => fabryka kontrolera (DI) - * Przy migracji kolejnego kontrolera - dodaj wpis tutaj - */ - private static $newControllers = []; - - /** - * Zwraca mapę fabryk kontrolerów (inicjalizacja runtime) - */ - private static function getControllerFactories(): array - { - if ( !empty( self::$newControllers ) ) - return self::$newControllers; - - self::$newControllers = [ - 'Articles' => function() { - global $mdb; - - return new \admin\Controllers\ArticlesController( - new \Domain\Article\ArticleRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ), - new \Domain\Layouts\LayoutsRepository( $mdb ), - new \Domain\Pages\PagesRepository( $mdb ) - ); - }, - 'ArticlesArchive' => function() { - global $mdb; - - return new \admin\Controllers\ArticlesArchiveController( - new \Domain\Article\ArticleRepository( $mdb ) - ); - }, - 'Banners' => function() { - global $mdb; - - return new \admin\Controllers\BannerController( - new \Domain\Banner\BannerRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ) - ); - }, - 'Settings' => function() { - global $mdb; - - return new \admin\Controllers\SettingsController( - new \Domain\Settings\SettingsRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ) - ); - }, - 'ProductArchive' => function() { - global $mdb; - - return new \admin\Controllers\ProductArchiveController( - new \Domain\Product\ProductRepository( $mdb ) - ); - }, - // Alias dla starego modułu /admin/archive/list/ - 'Archive' => function() { - global $mdb; - - return new \admin\Controllers\ProductArchiveController( - new \Domain\Product\ProductRepository( $mdb ) - ); - }, - 'Dictionaries' => function() { - global $mdb; - - return new \admin\Controllers\DictionariesController( - new \Domain\Dictionaries\DictionariesRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ) - ); - }, - 'Filemanager' => function() { - return new \admin\Controllers\FilemanagerController(); - }, - 'Users' => function() { - global $mdb; - - return new \admin\Controllers\UsersController( - new \Domain\User\UserRepository( $mdb ) - ); - }, - 'Languages' => function() { - global $mdb; - - return new \admin\Controllers\LanguagesController( - new \Domain\Languages\LanguagesRepository( $mdb ) - ); - }, - 'Layouts' => function() { - global $mdb; - - return new \admin\Controllers\LayoutsController( - new \Domain\Layouts\LayoutsRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ) - ); - }, - 'Newsletter' => function() { - global $mdb; - - return new \admin\Controllers\NewsletterController( - new \Domain\Newsletter\NewsletterRepository( - $mdb, - new \Domain\Settings\SettingsRepository( $mdb ) - ), - new \Domain\Newsletter\NewsletterPreviewRenderer() - ); - }, - 'Scontainers' => function() { - global $mdb; - - return new \admin\Controllers\ScontainersController( - new \Domain\Scontainers\ScontainersRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ) - ); - }, - 'ShopPromotion' => function() { - global $mdb; - - return new \admin\Controllers\ShopPromotionController( - new \Domain\Promotion\PromotionRepository( $mdb ) - ); - }, - 'ShopCoupon' => function() { - global $mdb; - - return new \admin\Controllers\ShopCouponController( - new \Domain\Coupon\CouponRepository( $mdb ) - ); - }, - 'ShopAttribute' => function() { - global $mdb; - - return new \admin\Controllers\ShopAttributeController( - new \Domain\Attribute\AttributeRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ) - ); - }, - 'ShopPaymentMethod' => function() { - global $mdb; - - return new \admin\Controllers\ShopPaymentMethodController( - new \Domain\PaymentMethod\PaymentMethodRepository( $mdb ) - ); - }, - 'ShopTransport' => function() { - global $mdb; - - return new \admin\Controllers\ShopTransportController( - new \Domain\Transport\TransportRepository( $mdb ), - new \Domain\PaymentMethod\PaymentMethodRepository( $mdb ) - ); - }, - 'Pages' => function() { - global $mdb; - - return new \admin\Controllers\PagesController( - new \Domain\Pages\PagesRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ), - new \Domain\Layouts\LayoutsRepository( $mdb ) - ); - }, - 'Integrations' => function() { - global $mdb; - - return new \admin\Controllers\IntegrationsController( - new \Domain\Integrations\IntegrationsRepository( $mdb ) - ); - }, - 'ShopStatuses' => function() { - global $mdb; - - return new \admin\Controllers\ShopStatusesController( - new \Domain\ShopStatus\ShopStatusRepository( $mdb ) - ); - }, - 'ShopProductSets' => function() { - global $mdb; - - return new \admin\Controllers\ShopProductSetsController( - new \Domain\ProductSet\ProductSetRepository( $mdb ) - ); - }, - 'ShopProducer' => function() { - global $mdb; - - return new \admin\Controllers\ShopProducerController( - new \Domain\Producer\ProducerRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ) - ); - }, - 'ShopCategory' => function() { - global $mdb; - - return new \admin\Controllers\ShopCategoryController( - new \Domain\Category\CategoryRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ) - ); - }, - 'ShopProduct' => function() { - global $mdb; - - return new \admin\Controllers\ShopProductController( - new \Domain\Product\ProductRepository( $mdb ) - ); - }, - 'ShopClients' => function() { - global $mdb; - - return new \admin\Controllers\ShopClientsController( - new \Domain\Client\ClientRepository( $mdb ) - ); - }, - ]; - - return self::$newControllers; - } - - /** - * Tworzy instancję nowego kontrolera z Dependency Injection - */ - private static function createController( string $moduleName ) - { - global $mdb; - - $factories = self::getControllerFactories(); - if ( !isset( $factories[$moduleName] ) ) - return null; - - $factory = $factories[$moduleName]; - if ( !is_callable( $factory ) ) - return null; - - return $factory(); - } - - - public static function route() - { - $_SESSION['admin'] = true; - - if ( \S::get( 'p' ) ) - \S::set_session( 'p' , \S::get( 'p' ) ); - - $page = \S::get_session( 'p' ); - - // Budowanie nazwy modułu - $moduleName = ''; - $results = explode( '_', \S::get( 'module' ) ); - if ( is_array( $results ) ) foreach ( $results as $row ) - $moduleName .= ucfirst( $row ); - - $action = \S::get( 'action' ); - - // 1. Sprawdź czy istnieje nowy kontroler - $factories = self::getControllerFactories(); - if ( isset( $factories[$moduleName] ) ) - { - $controller = self::createController( $moduleName ); - if ( $controller ) - { - if ( method_exists( $controller, $action ) ) - { - return $controller->$action(); - } - - if ( $moduleName === 'ShopAttribute' ) - { - \S::alert( 'Nieprawidłowy adres url.' ); - return false; - } - } - - } - - // 2. Fallback na stary kontroler - $class = '\admin\controls\\' . $moduleName; - - if ( class_exists( $class ) and method_exists( new $class, $action ) ) - return call_user_func_array( array( $class, $action ), array() ); - else - { - \S::alert( 'Nieprawidłowy adres url.' ); - return false; - } - } - - static public function update() - { - global $mdb; - - if ( $results = $mdb -> select( 'pp_updates', [ 'name' ], [ 'done' => 0 ] ) ) - { - foreach ( $results as $row ) - { - $class = '\admin\factory\Update'; - $method = $row['name']; - - if ( class_exists( $class ) and method_exists( new $class, $method ) ) - call_user_func_array( array( $class, $method ), array() ); - } - } - } -} - diff --git a/temp/update_build/tmp_0.275/autoload/admin/controls/class.ShopProduct.php b/temp/update_build/tmp_0.275/autoload/admin/controls/class.ShopProduct.php deleted file mode 100644 index c7113d4..0000000 --- a/temp/update_build/tmp_0.275/autoload/admin/controls/class.ShopProduct.php +++ /dev/null @@ -1,373 +0,0 @@ - $val ) - { - if ( strpos( $key, 'attribute_' ) !== false ) - { - $attribute = explode( 'attribute_', $key ); - $attributes[ $attribute[1] ] = $val; - } - } - - if ( \admin\factory\ShopProduct::generate_permutation( (int) \S::get( 'product_id' ), $attributes ) ) - \S::alert( 'Kombinacje produktu zostały wygenerowane.' ); - - header( 'Location: /admin/shop_product/product_combination/product_id=' . (int) \S::get( 'product_id' ) ); - exit; - } - - //usunięcie kombinacji produktu - static public function delete_combination() - { - if ( \admin\factory\ShopProduct::delete_combination( (int)\S::get( 'combination_id' ) ) ) - \S::alert( 'Kombinacja produktu została usunięta' ); - else - \S::alert( 'Podczas usuwania kombinacji produktu wystąpił błąd. Proszę spróbować ponownie' ); - - header( 'Location: /admin/shop_product/product_combination/product_id=' . \S::get( 'product_id' ) ); - exit; - } - - static public function duplicate_product() - { - if ( \admin\factory\ShopProduct::duplicate_product( (int)\S::get( 'product-id' ), (int)\S::get( 'combination' ) ) ) - \S::set_message( 'Produkt został zduplikowany.' ); - else - \S::alert( 'Podczas duplikowania produktu wystąpił błąd. Proszę spróbować ponownie' ); - - header( 'Location: /admin/shop_product/view_list/' ); - exit; - } - - public static function image_delete() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas usuwania zdjecia wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::delete_img( \S::get( 'image_id' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - public static function images_order_save() - { - if ( \admin\factory\ShopProduct::images_order_save( \S::get( 'product_id' ), \S::get( 'order' ) ) ) - echo json_encode( [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.' ] ); - - exit; - } - - public static function image_alt_change() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany atrybutu alt zdjęcia wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::image_alt_change( \S::get( 'image_id' ), \S::get( 'image_alt' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - // szybka zmiana statusu produktu - static public function change_product_status() { - - if ( \admin\factory\ShopProduct::change_product_status( (int)\S::get( 'product-id' ) ) ) - \S::set_message( 'Status produktu został zmieniony' ); - - header( 'Location: ' . $_SERVER['HTTP_REFERER'] ); - exit; - } - - // szybka zmiana google xml label - static public function product_change_custom_label() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany google xml label wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::product_change_custom_label( (int) \S::get( 'product_id' ), \S::get( 'custom_label' ), \S::get( 'value' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - // szybka zmiana ceny promocyjnej - static public function product_change_price_brutto_promo() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::product_change_price_brutto_promo( (int) \S::get( 'product_id' ), \S::get( 'price' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - // szybka zmiana ceny - static public function product_change_price_brutto() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::product_change_price_brutto( (int) \S::get( 'product_id' ), \S::get( 'price' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - // pobierz bezpośredni url produktu - static public function ajax_product_url() - { - echo json_encode( [ 'url' => \shop\Product::getProductUrl( \S::get( 'product_id' ) ) ] ); - exit; - } - - // zapisanie produktu - public static function save() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania produktu wystąpił błąd. Proszę spróbować ponownie.' ]; - $values = json_decode( \S::get( 'values' ), true ); - - if ( $id = \admin\factory\ShopProduct::save( - $values['id'], $values['name'], $values['short_description'], $values['description'], $values['status'], $values['meta_description'], $values['meta_keywords'], $values['seo_link'], - $values['copy_from'], $values['categories'], $values['price_netto'], $values['price_brutto'], $values['vat'], $values['promoted'], $values['warehouse_message_zero'], $values['warehouse_message_nonzero'], $values['tab_name_1'], - $values['tab_description_1'], $values['tab_name_2'], $values['tab_description_2'], $values['layout_id'], $values['products_related'], (int) $values['set'], $values['price_netto_promo'], $values['price_brutto_promo'], - $values['new_to_date'], $values['stock_0_buy'], $values['wp'], $values['custom_label_0'], $values['custom_label_1'], $values['custom_label_2'], $values['custom_label_3'], $values['custom_label_4'], $values['additional_message'], (int)$values['quantity'], $values['additional_message_text'], $values['additional_message_required'] == 'on' ? 1 : 0, $values['canonical'], $values['meta_title'], $values['producer_id'], $values['sku'], $values['ean'], $values['product_unit'], $values['weight'], $values['xml_name'], $values['custom_field_name'], $values['custom_field_required'], $values['security_information'], $values['custom_field_type'] - ) ) { - $response = [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.', 'id' => $id ]; - } - - echo json_encode( $response ); - exit; - } - - // product_unarchive - static public function product_unarchive() - { - if ( \admin\factory\ShopProduct::product_unarchive( (int) \S::get( 'product_id' ) ) ) - \S::alert( 'Produkt został przywrócony z archiwum.' ); - else - \S::alert( 'Podczas przywracania produktu z archiwum wystąpił błąd. Proszę spróbować ponownie' ); - - header( 'Location: /admin/product_archive/list/' ); - exit; - } - - static public function product_archive() - { - if ( \admin\factory\ShopProduct::product_archive( (int) \S::get( 'product_id' ) ) ) - \S::alert( 'Produkt został przeniesiony do archiwum.' ); - else - \S::alert( 'Podczas przenoszenia produktu do archiwum wystąpił błąd. Proszę spróbować ponownie' ); - - header( 'Location: /admin/shop_product/view_list/' ); - exit; - } - - public static function product_delete() - { - if ( \admin\factory\ShopProduct::product_delete( (int) \S::get( 'id' ) ) ) - \S::set_message( 'Produkt został usunięty.' ); - else - \S::alert( 'Podczas usuwania produktu wystąpił błąd. Proszę spróbować ponownie' ); - header( 'Location: /admin/shop_product/view_list/' ); - exit; - } - - // edycja produktu - public static function product_edit() { - global $user, $mdb; - - if ( !$user ) { - header( 'Location: /admin/' ); - exit; - } - - \admin\factory\ShopProduct::delete_nonassigned_images(); - \admin\factory\ShopProduct::delete_nonassigned_files(); - - return \Tpl::view( 'shop-product/product-edit', [ - 'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'id' ) ), - 'languages' => ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->languagesList(), - 'categories' => ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->subcategories( null ), - 'layouts' => self::layouts_for_product_edit( $mdb ), - 'products' => \admin\factory\ShopProduct::products_list(), - 'dlang' => \front\factory\Languages::default_language(), - 'sets' => \shop\ProductSet::sets_list(), - 'producers' => ( new \Domain\Producer\ProducerRepository( $mdb ) )->allProducers(), - 'units' => ( new \Domain\Dictionaries\DictionariesRepository( $mdb ) ) -> allUnits(), - 'user' => $user - ] ); - } - - private static function layouts_for_product_edit( $db ) - { - if ( class_exists( '\Domain\Layouts\LayoutsRepository' ) ) - { - $rows = ( new \Domain\Layouts\LayoutsRepository( $db ) ) -> listAll(); - return is_array( $rows ) ? $rows : []; - } - - return []; - } - - // ajax_load_products ARCHIVE - static public function ajax_load_products_archive() - { - echo json_encode( [ - 'status' => 'deprecated', - 'msg' => 'Endpoint nie jest juz wspierany. Uzyj /admin/product_archive/list/.', - 'redirect_url' => '/admin/product_archive/list/' - ] ); - exit; - } - - // ajax_load_products - static public function ajax_load_products() { - - $response = [ 'status' => 'error', 'msg' => 'Podczas ładowania produktów wystąpił błąd. Proszę spróbować ponownie.' ]; - - \S::set_session( 'products_list_current_page', \S::get( 'current_page' ) ); - \S::set_session( 'products_list_query', \S::get( 'query' ) ); - - if ( $products = \admin\factory\ShopProduct::ajax_products_list( \S::get_session( 'products_list_current_page' ), \S::get_session( 'products_list_query' ) ) ) { - $response = [ - 'status' => 'ok', - 'pagination_max' => ceil( $products['products_count'] / 10 ), - 'html' => \Tpl::view( 'shop-product/products-list-table', [ - 'products' => $products['products'], - 'current_page' => \S::get( 'current_page' ), - 'apilo_enabled' => \admin\factory\Integrations::apilo_settings( 'enabled' ), - 'show_xml_data' => \S::get_session( 'show_xml_data' ) - ] ) - ]; - } - - echo json_encode( $response ); - exit; - } - - static public function view_list() - { - $current_page = \S::get_session( 'products_list_current_page' ); - - if ( !$current_page ) { - $current_page = 1; - \S::set_session( 'products_list_current_page', $current_page ); - } - - $query = \S::get_session( 'products_list_query' ); - if ( $query ) { - $query_array = []; - parse_str( $query, $query_array ); - } - - if ( \S::get( 'show_xml_data' ) === 'true' ) { - \S::set_session( 'show_xml_data', true ); - } else if ( \S::get( 'show_xml_data' ) === 'false' ) { - \S::set_session( 'show_xml_data', false ); - } - - return \Tpl::view( 'shop-product/products-list', [ - 'current_page' => $current_page, - 'query_array' => $query_array, - 'pagination_max' => ceil( \admin\factory\ShopProduct::count_product() / 10 ), - 'apilo_enabled' => \admin\factory\Integrations::apilo_settings( 'enabled' ), - 'show_xml_data' => \S::get_session( 'show_xml_data' ), - 'shoppro_enabled' => \admin\factory\Integrations::shoppro_settings( 'enabled' ) - ] ); - } - - // - // KOMBINACJE PRODUKTU - // - - // zapisanie możliwości zakupu przy stanie 0 w kombinacji produktu - static public function product_combination_stock_0_buy_save() - { - \admin\factory\ShopProduct::product_combination_stock_0_buy_save( (int)\S::get( 'product_id' ), \S::get( 'stock_0_buy' ) ); - exit; - } - - // zapisanie sku w kombinacji produktu - static public function product_combination_sku_save() - { - \admin\factory\ShopProduct::product_combination_sku_save( (int)\S::get( 'product_id' ), \S::get( 'sku' ) ); - exit; - } - - // zapisanie ilości w kombinacji produktu - static public function product_combination_quantity_save() - { - \admin\factory\ShopProduct::product_combination_quantity_save( (int)\S::get( 'product_id' ), \S::get( 'quantity' ) ); - exit; - } - - // zapisanie ceny w kombinacji produktu - static public function product_combination_price_save() - { - \admin\factory\ShopProduct::product_combination_price_save( (int)\S::get( 'product_id' ), \S::get( 'price' ) ); - exit; - } - - //wyświetlenie kombinacji produktu - static public function product_combination() - { - global $mdb; - - return \Tpl::view( 'shop-product/product-combination', [ - 'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'product_id' ) ), - 'attributes' => ( new \Domain\Attribute\AttributeRepository( $mdb ) ) -> getAttributesListForCombinations(), - 'default_language' => \front\factory\Languages::default_language(), - 'product_permutations' => \admin\factory\ShopProduct::get_product_permutations( (int) \S::get( 'product_id' ) ) - ] ); - } - - // generate_sku_code - static public function generate_sku_code() { - $response = [ 'status' => 'error', 'msg' => 'Podczas generowania kodu sku wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( $sku = \shop\Product::generate_sku_code( \S::get( 'product_id' ) ) ) - $response = [ 'status' => 'ok', 'sku' => $sku ]; - - echo json_encode( $response ); - exit; - } - - // product_xml_name_save - static public function product_xml_name_save() { - $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania nazwy produktu wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \shop\Product::product_xml_name_save( \S::get( 'product_id' ), \S::get( 'product_xml_name' ), \S::get( 'lang_id' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - // product_custom_label_suggestions - static public function product_custom_label_suggestions() { - $response = [ 'status' => 'error', 'msg' => 'Podczas pobierania sugestii dla custom label wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( $suggestions = \shop\Product::product_custom_label_suggestions( \S::get( 'custom_label' ), \S::get( 'label_type' ) ) ) - $response = [ 'status' => 'ok', 'suggestions' => $suggestions ]; - - echo json_encode( $response ); - exit; - } - - // product_custom_label_save - static public function product_custom_label_save() { - $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania custom label wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \shop\Product::product_custom_label_save( \S::get( 'product_id' ), \S::get( 'custom_label' ), \S::get( 'label_type' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } -} diff --git a/temp/update_build/tmp_0.275/autoload/admin/factory/class.ShopProduct.php b/temp/update_build/tmp_0.275/autoload/admin/factory/class.ShopProduct.php deleted file mode 100644 index 633cf32..0000000 --- a/temp/update_build/tmp_0.275/autoload/admin/factory/class.ShopProduct.php +++ /dev/null @@ -1,1578 +0,0 @@ - count( 'pp_shop_products_langs', [ - 'AND' => [ - 'lang_id' => $lang_id, - 'seo_link' => $seo_link, - 'product_id[!]' => $product_id, - ], - ] ); - } - - private static function removeConflictingRedirectSources( int $product_id, string $lang_id, string $from ): void - { - global $mdb; - - if ( !$from ) - return; - - $mdb -> delete( 'pp_redirects', [ - 'AND' => [ - 'from' => $from, - 'lang_id' => $lang_id, - 'product_id[!]' => $product_id, - ], - ] ); - } - - // count_product - static public function count_product( $where = null ) - { - global $mdb; - - if ( $where ) - return $mdb -> count( 'pp_shop_products', $where ); - else - return $mdb -> count( 'pp_shop_products', [ 'archive' => 0 ] ); - } - - static public function update_product_combinations_prices( int $product_id, $price_brutto, $vat, $price_brutto_promo ) - { - global $mdb; - - $products = $mdb -> query( 'SELECT psp.id, parent_id ' - . 'FROM pp_shop_products AS psp ' - . 'INNER JOIN pp_shop_products_attributes AS pspa ON psp.id = pspa.product_id ' - . 'INNER JOIN pp_shop_attributes_values AS psav ON pspa.value_id = psav.id ' - . 'WHERE psav.impact_on_the_price > 0 AND psp.parent_id = :product_id', [ ':product_id' => $product_id ] ) -> fetchAll( \PDO::FETCH_ASSOC ); - foreach ( $products as $product ) - { - $price_brutto_combination = $price_brutto; - $price_brutto_promo_combination = $price_brutto_promo; - - $values = $mdb -> query( 'SELECT impact_on_the_price FROM pp_shop_attributes_values AS psav INNER JOIN pp_shop_products_attributes AS pspa ON pspa.value_id = psav.id WHERE impact_on_the_price IS NOT NULL AND product_id = :product_id', [ ':product_id' => $product['id'] ] ) -> fetchAll( \PDO::FETCH_ASSOC ); - foreach ( $values as $value ) - { - $price_brutto_combination += $value['impact_on_the_price']; - if ( $price_brutto_promo ) - $price_brutto_promo_combination += $value['impact_on_the_price']; - else - $price_brutto_promo_combination = null; - } - - $price_netto_combination = \S::normalize_decimal( $price_brutto_combination / ( 100 + $vat ) * 100, 2 ); - if ( $price_brutto_promo_combination ) - $price_netto_promo_combination = \S::normalize_decimal( $price_brutto_promo_combination / ( 100 + $vat ) * 100, 2 ); - else - $price_netto_promo_combination = null; - - $mdb -> update( 'pp_shop_products', [ 'price_netto' => $price_netto_combination, 'price_brutto' => $price_brutto_combination, 'price_netto_promo' => $price_netto_promo_combination, 'price_brutto_promo' => $price_brutto_promo_combination ], [ 'id' => $product['id'] ] ); - } - } - - // szybka zmiana statusu produktu - static public function change_product_status( int $product_id ) { - global $mdb; - - $status = $mdb -> get( 'pp_shop_products', 'status', [ 'id' => $product_id ] ); - $status = $status == 1 ? 0 : 1; - return $mdb -> update( 'pp_shop_products', [ 'status' => $status ], [ 'id' => $product_id ] ); - } - - // domyślna nazwa produktu - static public function product_default_name( int $product_id ) { - global $mdb; - - $default_lang = $mdb -> get( 'pp_langs', 'id', [ 'start' => 1 ] ); - return $mdb -> get( 'pp_shop_products_langs', 'name', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $default_lang ] ] ); - } - - // szybka zmiana google xml label - static public function product_change_custom_label( int $product_id, $custom_label, $value ) - { - global $mdb; - return $mdb -> update( 'pp_shop_products', [ 'custom_label_' . $custom_label => $value ? $value : null ], [ 'id' => $product_id ] ); - } - - // szybka zmiana ceny promocyjnej - static public function product_change_price_brutto_promo( int $product_id, $price ) - { - global $mdb; - - $vat = $mdb -> get( 'pp_shop_products', 'vat', [ 'id' => $product_id ] ); - $price_netto = \S::normalize_decimal( (float)$price / ( 100 + (float)$vat ) * 100, 2 ); - - return $mdb -> update( 'pp_shop_products', [ 'price_brutto_promo' => $price != 0.00 ? $price : null, 'price_netto_promo' => $price_netto != 0.00 ? $price : null ], [ 'id' => $product_id ] ); - } - - // szybka zmiana ceny - static public function product_change_price_brutto( int $product_id, $price ) - { - global $mdb; - - $vat = $mdb -> get( 'pp_shop_products', 'vat', [ 'id' => $product_id ] ); - $price_netto = \S::normalize_decimal( (float)$price / ( 100 + (float)$vat ) * 100, 2 ); - - return $mdb -> update( 'pp_shop_products', [ 'price_brutto' => $price != 0.00 ? $price : null, 'price_netto' => $price_netto != 0.00 ? $price : null ], [ 'id' => $product_id ] ); - } - - // pobierz id produktu głównego - static public function get_product_parent_id( int $product_id ) - { - global $mdb; - return $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $product_id ] ); - } - - // usunięcie kombinacji produktu - static public function delete_combination( int $combination_id ) - { - global $mdb; - - $mdb -> delete( 'pp_shop_products', [ 'id' => $combination_id ] ); - $mdb -> delete( 'pp_shop_products_attributes', [ 'product_id' => $combination_id ] ); - - return true; - } - - // pobranie permutacji produktu - static public function get_product_permutations( int $product_id ) - { - global $mdb; - - $results = $mdb -> select( 'pp_shop_products', 'id', [ 'parent_id' => $product_id ] ); - if ( \S::is_array_fix( $results ) ) foreach ( $results as $row ) - $products[] = \admin\factory\ShopProduct::product_details( $row ); - - return $products; - } - - // generowanie kombinacji produktu - static public function generate_permutation( int $product_id, $attributes ) - { - global $mdb; - - $vat = $mdb -> get( 'pp_shop_products', 'vat', [ 'id' => $product_id ] ); - $attributeRepository = new \Domain\Attribute\AttributeRepository( $mdb ); - - $permutations = \shop\Product::array_cartesian( $attributes ); - if ( \S::is_array_fix( $permutations ) ) foreach ( $permutations as $permutation ) - { - $product = null; - ksort( $permutation ); - - $permutation_hash = ''; - - if ( \S::is_array_fix( $permutation ) ) foreach ( $permutation as $key => $val ) - { - if ( $permutation_hash ) - $permutation_hash .= '|'; - - $permutation_hash .= $key . '-' . $val; - - // sprawdzenie czy atrybut ma wpływ na cenę - $value_details = $attributeRepository -> valueDetails( (int)$val ); - $impact_on_the_price = $value_details[ 'impact_on_the_price' ]; - - if ( $impact_on_the_price > 0 ) - { - if ( !$product ) - $product = \admin\factory\ShopProduct::product_details( $product_id ); - - $product_price_brutto = $product['price_brutto'] + $impact_on_the_price; - $product_price_netto = $product_price_brutto / ( 1 + ( $product['vat'] / 100 ) ); - - if ( $product['price_brutto_promo'] ) - { - $product_price_brutto_promo = $product['price_brutto_promo'] + $impact_on_the_price; - $product_price_netto_promo = $product_price_brutto_promo / ( 1 + ( $product['vat'] / 100 ) ); - } - else - { - $product_price_brutto_promo = null; - $product_price_netto_promo = null; - } - } - - if ( $permutation_hash and !$mdb -> count( 'pp_shop_products', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation_hash ] ] ) ) - { - if ( $mdb -> insert( 'pp_shop_products', [ 'parent_id' => $product_id, 'permutation_hash' => $permutation_hash, 'vat' => $vat ] ) ) - { - $combination_id = $mdb -> id(); - if ( $product ) - { - $mdb -> update( 'pp_shop_products', [ 'price_netto' => $product_price_netto, 'vat' => $product['vat'], 'price_brutto' => $product_price_brutto, 'price_netto_promo' => $product_price_netto_promo, 'price_brutto_promo' => $product_price_brutto_promo ], [ 'id' => $combination_id ] ); - } - - $permutation_hash_rev_rows = explode( '|', $permutation_hash ); - foreach ( $permutation_hash_rev_rows as $permutation_hash_rev ) - { - $attribute_rev = explode( '-', $permutation_hash_rev ); - $mdb -> insert( 'pp_shop_products_attributes', [ 'product_id' => $combination_id, 'attribute_id' => $attribute_rev[0], 'value_id' => $attribute_rev[1] ] ); - } - } - } - } - } - return true; - } - - public static function product_name($product_id) - { - global $mdb; - - $results = $mdb -> query("SELECT pspl.name FROM pp_shop_products_langs AS pspl, pp_langs AS pl WHERE lang_id = pl.id AND product_id = :product_id AND pspl.name != '' ORDER BY o ASC LIMIT 1", [':product_id' => $product_id]) -> fetchAll(); - - return $results[0]['name']; - } - - public static function get_product_images($product_id) - { - global $mdb; - - return $mdb -> select('pp_shop_products_images', 'src', ['product_id' => (int) $product_id, 'ORDER' => ['o' => 'ASC', 'id' => 'ASC']]); - } - - static public function generate_EAN( $number ) - { - $code = '200' . str_pad($number, 9, '0'); - $weightflag = true; - $sum = 0; - - for ($i = strlen($code) - 1; $i >= 0; $i--) - { - $sum += (int)$code[$i] * ($weightflag?3:1); - $weightflag = !$weightflag; - } - - $code .= (10 - ($sum % 10)) % 10; - return $code; - } - - static public function generate_google_feed_xml() - { - global $mdb, $lang_id; - - $settings = \front\factory\Settings::settings_details(true); - - $domain_prefix = 'https'; - $url = preg_replace('#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME']); - - $main_language = \front\factory\Languages::default_language(); - - $doc = new \DOMDocument('1.0', 'UTF-8'); - $xmlRoot = $doc -> createElement('rss'); - $xmlRoot = $doc -> appendChild($xmlRoot); - $xmlRoot -> setAttribute('version', '2.0'); - $xmlRoot -> setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:g', 'http://base.google.com/ns/1.0'); - $channelNode = $xmlRoot -> appendChild($doc -> createElement('channel')); - $channelNode -> appendChild( $doc -> createElement( 'title', $settings['firm_name'])); - $channelNode -> appendChild( $doc -> createElement( 'link', $domain_prefix . '://' . $url ) ); - - $rows = $mdb -> select( 'pp_shop_products', 'id', [ 'AND' => [ 'status' => '1', 'archive' => 0, 'parent_id' => null ] ] ); - if ( \S::is_array_fix( $rows ) ) foreach ( $rows as $product_id ) - { - $product = \shop\Product::getFromCache( $product_id, $lang_id ); - - if ( is_array( $product -> product_combinations ) and count( $product -> product_combinations ) ) - { - foreach ( $product -> product_combinations as $product_combination ) - { - if ( $product_combination -> quantity !== null or $product_combination -> stock_0_buy ) - { - $itemNode = $channelNode -> appendChild( $doc -> createElement( 'item' ) ); - $p_gid = $itemNode -> appendChild( $doc -> createElement('g:id', $product_combination -> id ) ); - $p_groupid = $itemNode -> appendChild( $doc -> createElement( 'g:item_group_id', $product -> id ) ); - - if ( $product -> custom_label_0 ) - $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_0', $product -> custom_label_0 ) ); - - if ( $product -> custom_label_1 ) - $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_1', $product -> custom_label_1 ) ); - - if ( $product -> custom_label_2 ) - $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_2', $product -> custom_label_2 ) ); - - if ( $product -> custom_label_3 ) - $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_3', $product -> custom_label_3 ) ); - - if ( $product -> custom_label_4 ) - $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_4', $product -> custom_label_4 ) ); - - if ( $product -> language['xml_name'] ) - $p_title = $itemNode -> appendChild( $doc -> createElement( 'title', str_replace( '&', '&', $product -> language['xml_name'] ) . ' - ' . $product -> generateSubtitleFromAttributes( $product_combination -> permutation_hash ) ) ); - else - $p_title = $itemNode -> appendChild( $doc -> createElement( 'title', str_replace( '&', '&', $product -> language['name'] ) . ' - ' . $product -> generateSubtitleFromAttributes( $product_combination -> permutation_hash ) ) ); - - if ( $product -> ean ) - $p_gtin = $itemNode -> appendChild( $doc -> createElement( 'g:gtin', $product -> ean ) ); - else - $p_gtin = $itemNode -> appendChild( $doc -> createElement( 'g:gtin', self::generate_EAN( $product -> id ) ) ); - - // opis produktu - if ( $product -> language['short_description'] ) - $p_description = $itemNode -> appendChild( $doc -> createElement( 'g:description', html_entity_decode( strip_tags( $product -> language['short_description'] ) ) ) ); - else - $p_description = $itemNode -> appendChild( $doc -> createElement( 'g:description', html_entity_decode( strip_tags( $product -> language['name'] ) ) ) ); - - if ( $product -> language['seo_link'] ) - $link = $domain_prefix . '://' . $url . '/' . \S::seo( $product -> language['seo_link'] ) . '/' . str_replace( '|', '/', $product_combination -> permutation_hash ); - else - $link = $domain_prefix . '://' . $url . '/' . 'p-' . $product -> id . '-' . \S::seo( $product -> language['name'] ) . '/' . str_replace( '|', '/', $product_combination -> permutation_hash ); - - $p_link = $itemNode -> appendChild( $doc -> createElement( 'link', $link ) ); - - if ( $product -> images[0] ) - $p_gimage_link = $itemNode -> appendChild( $doc -> createElement( 'g:image_link', $domain_prefix . '://' . $url . $product -> images[0]['src'] ) ); - - if ( count( $product -> images ) > 1 ) - { - for ( $i = 1; $i < count( $product -> images ); ++$i ) - $p_gimage_link = $itemNode -> appendChild( $doc -> createElement( 'g:additional_image_link', $domain_prefix . '://' . $url . $product -> images[$i]['src'] ) ); - } - - $p_gcondition = $itemNode -> appendChild( $doc -> createElement( 'g:condition', 'new' ) ); - - if ( $product_combination -> quantity !== null ) - { - if ( $product_combination -> quantity > 0 ) - { - $p_gavailability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) ); - $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', $product_combination -> quantity ) ); - } - else - { - if ( $product_combination -> stock_0_buy ) - $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) ); - else - $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'out of stock' ) ); - } - } - else - { - if ( $product -> quantity > 0 ) - { - $p_gavailability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) ); - $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', $product -> quantity ) ); - } - else - { - if ( $product -> stock_0_buy ) - { - $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) ); - $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', 999 ) ); - } - else - { - $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'out of stock' ) ); - $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', 0 ) ); - } - } - } - - if ( $product_combination -> price_brutto ) - { - $p_gprice = $itemNode -> appendChild( $doc -> createElement( 'g:price', $product_combination -> price_brutto . ' PLN' ) ); - - if ( $product_combination -> price_brutto_promo ) - $p_gsale_price = $itemNode -> appendChild( $doc -> createElement( 'g:sale_price', $product_combination -> price_brutto_promo . ' PLN' ) ); - } - else - { - $p_gprice = $itemNode -> appendChild( $doc -> createElement( 'g:price', $product -> price_brutto . ' PLN' ) ); - - if ( $product -> price_brutto_promo ) - $p_gsale_price = $itemNode -> appendChild( $doc -> createElement( 'g:sale_price', $product -> price_brutto_promo . ' PLN' ) ); - } - - $p_gshipping = $itemNode -> appendChild( $doc -> createElement( 'g:shipping' ) ); - $p_gcountry = $p_gshipping -> appendChild( $doc -> createElement( 'g:country', 'PL' ) ); - $p_gservice = $p_gshipping -> appendChild( $doc -> createElement( 'g:service', '1 dzień roboczy' ) ); - $p_gprice = $p_gshipping -> appendChild( $doc -> createElement( 'g:price', ( new \Domain\Transport\TransportRepository( $mdb ) )->lowestTransportPrice( (int) $product -> wp ) . ' PLN' ) ); - } - } - } - else - { - $itemNode = $channelNode -> appendChild( $doc -> createElement( 'item' ) ); - $p_gid = $itemNode -> appendChild( $doc -> createElement('g:id', $product -> id ) ); - $p_groupid = $itemNode -> appendChild( $doc -> createElement( 'g:item_group_id', $product -> id ) ); - - if ( $product -> google_xml_label ) - $p_label = $itemNode -> appendChild($doc -> createElement('g:custom_label_0', $product -> google_xml_label ) ); - - if ( $product -> language['xml_name'] ) - $p_title = $itemNode -> appendChild( $doc -> createElement( 'title', str_replace( '&', '&', $product -> language['xml_name'] ) ) ); - else - $p_title = $itemNode -> appendChild( $doc -> createElement( 'title', str_replace( '&', '&', $product -> language['name'] ) ) ); - - if ( $product -> ean ) - $p_gtin = $itemNode -> appendChild( $doc -> createElement( 'g:gtin', $product -> ean ) ); - else - $p_gtin = $itemNode -> appendChild( $doc -> createElement( 'g:gtin', self::generate_EAN( $product -> id ) ) ); - - // opis produktu - if ( $product -> language['short_description'] ) - $p_description = $itemNode -> appendChild( $doc -> createElement( 'g:description', html_entity_decode( strip_tags( $product -> language['short_description'] ) ) ) ); - else - $p_description = $itemNode -> appendChild( $doc -> createElement( 'g:description', html_entity_decode( strip_tags( $product -> language['name'] ) ) ) ); - - if ( $product -> language['seo_link'] ) - $link = $domain_prefix . '://' . $url . '/' . \S::seo( $product -> language['seo_link'] ); - else - $link = $domain_prefix . '://' . $url . '/' . 'p-' . $product -> id . '-' . \S::seo( $product -> language['name'] ); - - $p_link = $itemNode -> appendChild( $doc -> createElement( 'link', $link ) ); - - if ( $product -> custom_label_0 ) - $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_0', $product -> custom_label_0 ) ); - - if ( $product -> custom_label_1 ) - $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_1', $product -> custom_label_1 ) ); - - if ( $product -> custom_label_2 ) - $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_2', $product -> custom_label_2 ) ); - - if ( $product -> custom_label_3 ) - $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_3', $product -> custom_label_3 ) ); - - if ( $product -> custom_label_4 ) - $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_4', $product -> custom_label_4 ) ); - - if ( $product -> images[0] ) - $p_gimage_link = $itemNode -> appendChild( $doc -> createElement( 'g:image_link', $domain_prefix . '://' . $url . $product -> images[0]['src'] ) ); - - if ( count( $product -> images ) > 1 ) - { - for ( $i = 1; $i < count( $product -> images ); ++$i ) - $p_gimage_link = $itemNode -> appendChild( $doc -> createElement( 'g:additional_image_link', $domain_prefix . '://' . $url . $product -> images[$i]['src'] ) ); - } - - $p_gcondition = $itemNode -> appendChild( $doc -> createElement( 'g:condition', 'new' ) ); - - if ( $product -> quantity ) - { - $p_gavailability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) ); - $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', $product -> quantity ) ); - } - else - { - if ( $product -> stock_0_buy ) { - $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) ); - $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', 999 ) ); - } - else { - $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'out of stock' ) ); - $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', 0 ) ); - } - } - - $p_gprice = $itemNode -> appendChild( $doc -> createElement( 'g:price', $product -> price_brutto . ' PLN' ) ); - - if ( $product -> price_brutto_promo ) - $p_gsale_price = $itemNode -> appendChild( $doc -> createElement( 'g:sale_price', $product -> price_brutto_promo . ' PLN' ) ); - - $p_gshipping = $itemNode -> appendChild( $doc -> createElement( 'g:shipping' ) ); - $p_gcountry = $p_gshipping -> appendChild( $doc -> createElement( 'g:country', 'PL' ) ); - $p_gservice = $p_gshipping -> appendChild( $doc -> createElement( 'g:service', '1 dzień roboczy' ) ); - $p_gprice = $p_gshipping -> appendChild( $doc -> createElement( 'g:price', ( new \Domain\Transport\TransportRepository( $mdb ) )->lowestTransportPrice( (int) $product -> wp ) . ' PLN' ) ); - } - } - file_put_contents('../google-feed.xml', $doc -> saveXML()); - } - - static public function count_product_combinations( int $product_id ) - { - global $mdb; - - return $mdb -> count( 'pp_shop_products', [ 'parent_id' => $product_id ] ); - } - - // ajax_products_list - static public function ajax_products_list_archive( $current_page = null, $query = null ) - { - global $mdb; - - $search = ''; - - if ( $query ) - { - $query_array = []; - parse_str( $query, $query_array ); - - foreach ( $query_array as $key => $val ) { - if ( $val !== '' ) - $search .= ' AND ' . $key . ' LIKE \'%' . $val . '%\''; - } - } - - $results = $mdb -> query( 'SELECT ' - . 'DISTINCT( psp.id )' - . 'FROM ' - . 'pp_shop_products AS psp ' - . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id ' - . 'WHERE archive = 1 AND parent_id IS NULL ' . $search . ' ORDER BY id DESC LIMIT ' . ( $current_page - 1 ) * 10 . ', 10' ) -> fetchAll( \PDO::FETCH_ASSOC ); - $results2 = $mdb -> query( 'SELECT ' - . 'COUNT( DISTINCT( psp.id ) ) AS products_count ' - . 'FROM ' - . 'pp_shop_products AS psp ' - . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id ' - . 'WHERE archive = 1 AND parent_id IS NULL ' . $search ) -> fetchAll( \PDO::FETCH_ASSOC ); - - if ( is_array( $results ) ) foreach ( $results as $row ) { - $products[] = \admin\factory\ShopProduct::product_details( $row['id'] ); - } - - return [ 'products' => $products, 'products_count' => $results2[0]['products_count'] ]; - } - - // ajax_products_list - static public function ajax_products_list( $current_page = null, $query = null ) - { - global $mdb; - - if ( $query ) - { - $search = ''; - $query_array = []; - - parse_str( $query, $query_array ); - - foreach ( $query_array as $key => $val ) { - if ( strpos( $key, '|' ) !== false ) - { - $keys_tmp = explode( '|', $key ); - $search .= ' AND ( '; - foreach ( $keys_tmp as $key_tmp ) - { - if ( $key_tmp != reset( $keys_tmp ) ) - $search .= ' OR ' . $key_tmp . ' LIKE \'%' . $val . '%\''; - else - $search .= ' ' . $key_tmp . ' LIKE \'%' . $val . '%\''; - } - $search .= ' )'; - } - else - { - $search .= ' AND ' . $key . ' LIKE \'%' . $val . '%\''; - } - } - - $results = $mdb -> query( 'SELECT ' - . 'DISTINCT( psp.id )' - . 'FROM ' - . 'pp_shop_products AS psp ' - . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id ' - . 'WHERE archive = 0 AND parent_id IS NULL ' . $search . ' ORDER BY id DESC LIMIT ' . ( $current_page - 1 ) * 10 . ', 10' ) -> fetchAll( \PDO::FETCH_ASSOC ); - $results2 = $mdb -> query( 'SELECT ' - . 'COUNT( DISTINCT( psp.id ) ) AS products_count ' - . 'FROM ' - . 'pp_shop_products AS psp ' - . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id ' - . 'WHERE archive = 0 AND parent_id IS NULL ' . $search ) -> fetchAll( \PDO::FETCH_ASSOC ); - } else { - $results = $mdb -> query( 'SELECT id FROM pp_shop_products WHERE parent_id IS NULL ORDER BY id DESC LIMIT ' . ( $current_page - 1 ) * 10 . ', 10' ) -> fetchAll( \PDO::FETCH_ASSOC ); - $results2 = $mdb -> query( 'SELECT COUNT( id ) AS products_count FROM pp_shop_products WHERE parent_id IS NULL' ) -> fetchAll( \PDO::FETCH_ASSOC ); - } - - if ( is_array( $results ) ) foreach ( $results as $row ) { - $products[] = \admin\factory\ShopProduct::product_details( $row['id'] ); - } - - return [ 'products' => $products, 'products_count' => $results2[0]['products_count'] ]; - } - - public static function products_list() - { - global $mdb; - - $results = $mdb -> select( 'pp_shop_products', 'id', [ 'parent_id' => null ] ); - if ( is_array( $results ) ) foreach ( $results as $row ) - { - $products[ $row ] = $mdb -> get ('pp_shop_products_langs', 'name', ['AND' => [ 'product_id' => $row, 'lang_id' => 'pl' ] ] ); - } - - return $products; - } - - public static function images_order_save($product_id, $order) - { - global $mdb; - - $order = explode(';', $order); - if (\is_array($order) && !empty($order)) - { - foreach ($order as $image_id) - { - $mdb -> update('pp_shop_products_images', [ - 'o' => $i++, - ], [ - 'AND' => [ - 'product_id' => $product_id, - 'id' => $image_id, - ], - ]); - } - } - - return true; - } - - public static function image_alt_change($image_id, $image_alt) - { - global $mdb; - $result = $mdb -> update('pp_shop_products_images', [ - 'alt' => $image_alt, - ], [ - 'id' => $image_id, - ]); - \S::delete_cache(); - - return $result; - } - - // product_unarchive - static public function product_unarchive( int $product_id ) - { - global $mdb; - - $mdb -> update( 'pp_shop_products', [ 'status' => 1, 'archive' => 0 ], [ 'id' => $product_id ] ); - $mdb -> update( 'pp_shop_products', [ 'status' => 1, 'archive' => 0 ], [ 'parent_id' => $product_id ] ); - - return true; - } - - static public function product_archive( int $product_id ) - { - global $mdb; - - $mdb -> update( 'pp_shop_products', [ 'status' => 0, 'archive' => 1 ], [ 'id' => $product_id ] ); - $mdb -> update( 'pp_shop_products', [ 'status' => 0, 'archive' => 1 ], [ 'parent_id' => $product_id ] ); - - return true; - } - - public static function product_delete( int $product_id) - { - global $mdb; - - $mdb -> delete( 'pp_shop_products_categories', ['product_id' => $product_id ] ); - $mdb -> delete( 'pp_shop_products_langs', ['product_id' => $product_id ] ); - $mdb -> delete( 'pp_shop_products_images', ['product_id' => $product_id ] ); - $mdb -> delete( 'pp_shop_products_files', ['product_id' => $product_id ] ); - $mdb -> delete( 'pp_shop_products_attributes', ['product_id' => $product_id ] ); - $mdb -> delete( 'pp_shop_products', ['id' => $product_id ] ); - $mdb -> delete( 'pp_shop_product_sets_products', [ 'product_id' => $product_id ] ); - // pp_routes - $mdb -> delete( 'pp_routes', [ 'product_id' => $product_id ] ); - // pp_redirects - $mdb -> delete( 'pp_redirects', [ 'product_id' => $product_id ] ); - - \S::delete_dir( '../upload/product_images/product_' . $product_id . '/' ); - \S::delete_dir( '../upload/product_files/product_' . $product_id . '/' ); - - return true; - } - - public static function product_categories($product_id) - { - global $mdb; - - $results = $mdb -> query('SELECT category_id FROM pp_shop_products_categories WHERE product_id = '.(int) $product_id) -> fetchAll(); - if (\is_array($results) && !empty($results)) - { - foreach ($results as $row) - { - if ('' === $out) - { - $out .= ' - '; - } - - $out .= ( new \Domain\Category\CategoryRepository( $mdb ) )->categoryTitle( (int) $row['category_id'] ); - - if (end($results) !== $row) - { - $out .= ' / '; - } - } - } - - return $out; - } - - public static function max_order() - { - global $mdb; - - return $mdb -> max('pp_shop_products_categories', 'o'); - } - - public static function delete_img($image_id) - { - global $mdb; - $mdb -> update('pp_shop_products_images', ['to_delete' => 1], ['id' => (int) $image_id]); - - return true; - } - - public static function delete_nonassigned_images() - { - global $mdb; - - $results = $mdb -> select('pp_shop_products_images', '*', ['product_id' => null]); - if (\is_array($results)) - { - foreach ($results as $row) - { - if (file_exists('../'.$row['src'])) - { - unlink('../'.$row['src']); - } - } - } - - $mdb -> delete('pp_shop_products_images', ['product_id' => null]); - } - - public static function file_name_change($file_id, $file_name) - { - global $mdb; - $mdb -> update('pp_shop_products_files', ['name' => $file_name], ['id' => (int) $file_id]); - - return true; - } - - public static function delete_file($file_id) - { - global $mdb; - $mdb -> update('pp_shop_products_files', ['to_delete' => 1], ['id' => (int) $file_id]); - - return true; - } - - public static function delete_nonassigned_files() - { - global $mdb; - - $results = $mdb -> select('pp_shop_products_files', '*', ['product_id' => null]); - if (\is_array($results)) - { - foreach ($results as $row) - { - if (file_exists('../'.$row['src'])) - { - unlink('../'.$row['src']); - } - } - } - - $mdb -> delete('pp_shop_products_files', ['product_id' => null]); - } - - public static function save( - $product_id, $name, $short_description, $description, $status, $meta_description, $meta_keywords, $seo_link, $copy_from, $categories, $price_netto, $price_brutto, $vat, $promoted, $warehouse_message_zero, $warehouse_message_nonzero, $tab_name_1, $tab_description_1, $tab_name_2, $tab_description_2, $layout_id, $products_related, int $set_id, $price_netto_promo, $price_brutto_promo, $new_to_date, $stock_0_buy, $wp, $custom_label_0, $custom_label_1, $custom_label_2, $custom_label_3, $custom_label_4, $additional_message, int $quantity, $additional_message_text, int $additional_message_required, $canonical, $meta_title, $producer_id, $sku, $ean, $product_unit, $weight, $xml_name, $custom_field_name, $custom_field_required, $security_information, $custom_field_type - ) - { - global $mdb, $user; - - if ( !$product_id ) - { - $mdb -> insert('pp_shop_products', [ - 'date_add' => date('Y-m-d H:i:s'), - 'date_modify' => date('Y-m-d H:i:s'), - 'modify_by' => $user['id'], - 'status' => 'on' === $status ? 1 : 0, - 'price_netto' => ($price_netto && 0.00 !== $price_netto) ? $price_netto : null, - 'price_brutto' => ($price_brutto && 0.00 !== $price_brutto) ? $price_brutto : null, - 'vat' => $vat, - 'promoted' => 'on' === $promoted ? 1 : 0, - 'layout_id' => $layout_id ? $layout_id : null, - 'price_netto_promo' => ($price_netto_promo && 0.00 !== $price_netto_promo) ? $price_netto_promo : null, - 'price_brutto_promo' => ($price_brutto_promo && 0.00 !== $price_brutto_promo) ? $price_brutto_promo : null, - 'new_to_date' => $new_to_date ? $new_to_date : null, - 'stock_0_buy' => 'on' === $stock_0_buy ? 1 : 0, - 'wp' => $wp ? $wp : null, - 'sku' => $sku ? $sku : null, - 'ean' => $ean ? $ean : null, - 'custom_label_0' => $custom_label_0 ? $custom_label_0 : null, - 'custom_label_1' => $custom_label_1 ? $custom_label_1 : null, - 'custom_label_2' => $custom_label_2 ? $custom_label_2 : null, - 'custom_label_3' => $custom_label_3 ? $custom_label_3 : null, - 'custom_label_4' => $custom_label_4 ? $custom_label_4 : null, - 'additional_message' => $additional_message == 'on' ? 1 : 0, - 'set_id' => $set_id ? $set_id : null, - 'quantity' => $quantity, - 'additional_message_text' => $additional_message_text ? $additional_message_text : null, - 'additional_message_required' => $additional_message_required, - 'producer_id' => !empty( $producer_id ) ? $producer_id : null, - 'product_unit_id' => !empty( $product_unit ) ? $product_unit : null, - 'weight' => !empty( $weight ) ? $weight : null, - ] ); - - $id = $mdb -> id(); - - if ( $id ) - { - $langs = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->languagesList( true ); - foreach ( $langs as $lg ) - { - $mdb -> insert( 'pp_shop_products_langs', [ - 'product_id' => (int) $id, - 'lang_id' => $lg['id'], - 'name' => $name[$lg['id']] ? $name[$lg['id']] : null, - 'short_description' => $short_description[$lg['id']] ? $short_description[$lg['id']] : null, - 'description' => $description[$lg['id']] ? $description[$lg['id']] : null, - 'meta_description' => $meta_description[$lg['id']] ? $meta_description[$lg['id']] : null, - 'meta_keywords' => $meta_keywords[$lg['id']] ? $meta_keywords[$lg['id']] : null, - 'seo_link' => $seo_link[$lg['id']] ? \S::seo($seo_link[$lg['id']]) : null, - 'copy_from' => $copy_from[$lg['id']] ? $copy_from[$lg['id']] : null, - 'warehouse_message_zero' => $warehouse_message_zero[$lg['id']] ? $warehouse_message_zero[$lg['id']] : null, - 'warehouse_message_nonzero' => $warehouse_message_nonzero[$lg['id']] ? $warehouse_message_nonzero[$lg['id']] : null, - 'tab_name_1' => $tab_name_1[$lg['id']] ? $tab_name_1[$lg['id']] : null, - 'tab_description_1' => $tab_description_1[$lg['id']] ? $tab_description_1[$lg['id']] : null, - 'tab_name_2' => $tab_name_2[$lg['id']] ? $tab_name_2[$lg['id']] : null, - 'tab_description_2' => $tab_description_2[$lg['id']] ? $tab_description_2[$lg['id']] : null, - 'canonical' => $canonical[$lg['id']] ? $canonical[$lg['id']] : null, - 'meta_title' => $meta_title[$lg['id']] ? $meta_title[$lg['id']] : null, - 'xml_name' => $xml_name[$lg['id']] ? $xml_name[$lg['id']] : null, - 'security_information' => $security_information[$lg['id']] ? $security_information[$lg['id']] : null, - ] ); - } - - if ( is_array($categories)) - { - foreach ($categories as $category) - { - $order = self::max_order() + 1; - - $mdb -> insert('pp_shop_products_categories', [ - 'product_id' => (int) $id, - 'category_id' => (int) $category, - 'o' => (int) $order, - ]); - } - } - elseif ($categories) - { - $order = self::max_order() + 1; - - $mdb -> insert('pp_shop_products_categories', [ - 'product_id' => (int) $id, - 'category_id' => (int) $categories, - 'o' => (int) $order, - ]); - } - - if (\is_array($products_related)) - { - foreach ($products_related as $product_related ) - { - $mdb -> insert('pp_shop_products_related', [ - 'product_id' => (int) $id, - 'product_related_id' => (int) $product_related, - ]); - } - } - elseif ( $products_related ) - { - $mdb -> insert('pp_shop_products_related', [ - 'product_id' => (int) $id, - 'product_related_id' => (int) $products_related, - ]); - } - - $created = false; - - $results = $mdb -> select('pp_shop_products_files', '*', ['product_id' => null]); - if (\is_array($results)) - { - foreach ($results as $row) - { - $dir = '/upload/product_files/product_'.$id; - - $new_file_name = str_replace('/upload/product_files/tmp', $dir, $row['src']); - - if (file_exists('..'.$row['src'])) - { - if (!is_dir('../'.$dir) && true !== $created) - { - if (mkdir('../'.$dir, 0755, true)) - { - $created = true; - } - } - rename('..'.$row['src'], '..'.$new_file_name); - } - - $mdb -> update('pp_shop_products_files', ['src' => $new_file_name, 'product_id' => $id], ['id' => $row['id']]); - } - } - - $created = false; - - $results = $mdb -> select('pp_shop_products_images', '*', ['product_id' => null]); - if (\is_array($results)) - { - foreach ($results as $row) - { - $dir = '/upload/product_images/product_'.$id; - - $new_file_name = str_replace('/upload/product_images/tmp', $dir, $row['src']); - - if (file_exists('../'.$new_file_name)) - { - $ext = strrpos($new_file_name, '.'); - $fileName_a = substr($new_file_name, 0, $ext); - $fileName_b = substr($new_file_name, $ext); - - $count = 1; - - while (file_exists('../'.$fileName_a.'_'.$count.$fileName_b)) - { - ++$count; - } - - $new_file_name = $fileName_a.'_'.$count.$fileName_b; - } - - if (file_exists('..'.$row['src'])) - { - if (!is_dir('../'.$dir) && true !== $created) - { - if (mkdir('../'.$dir, 0755, true)) - { - $created = true; - } - } - rename('..'.$row['src'], '..'.$new_file_name); - } - - $mdb -> update('pp_shop_products_images', ['src' => $new_file_name, 'product_id' => (int) $id], ['id' => $row['id']]); - } - } - - // dodatkowe pola - for ( $i = 0; $i < count( $custom_field_name ); ++$i ) - { - if ( !empty( $custom_field_name[$i] ) ) - { - $custom_field = $custom_field_name[$i]; - $custom_field_type_data = $custom_field_type[$i]; - $custom_field_required = isset( $custom_field_required[$i] ) ? 1 : 0; - - $mdb -> insert( 'pp_shop_products_custom_fields', [ - 'id_product' => (int) $id, - 'name' => $custom_field, - 'type' => $custom_field_type_data, - 'is_required' => $custom_field_required, - ] ); - } - } - - \S::htacces(); - - \S::delete_dir('../temp/'); - \S::delete_dir('../thumbs/'); - - return $id; - } - } - else - { - $mdb -> update( 'pp_shop_products', [ - 'date_modify' => date('Y-m-d H:i:s'), - 'modify_by' => $user['id'], - 'status' => 'on' === $status ? 1 : 0, - 'price_netto' => ($price_netto && 0.00 !== $price_netto) ? $price_netto : null, - 'price_brutto' => ($price_brutto && 0.00 !== $price_brutto) ? $price_brutto : null, - 'vat' => $vat, - 'promoted' => 'on' === $promoted ? 1 : 0, - 'layout_id' => $layout_id ? $layout_id : null, - 'price_netto_promo' => ($price_netto_promo && 0.00 !== $price_netto_promo) ? $price_netto_promo : null, - 'price_brutto_promo' => ($price_brutto_promo && 0.00 !== $price_brutto_promo) ? $price_brutto_promo : null, - 'new_to_date' => $new_to_date ? $new_to_date : null, - 'stock_0_buy' => 'on' === $stock_0_buy ? 1 : 0, - 'wp' => $wp ? $wp : null, - 'sku' => $sku ? $sku : null, - 'ean' => $ean ? $ean : null, - 'custom_label_0' => $custom_label_0 ? $custom_label_0 : null, - 'custom_label_1' => $custom_label_1 ? $custom_label_1 : null, - 'custom_label_2' => $custom_label_2 ? $custom_label_2 : null, - 'custom_label_3' => $custom_label_3 ? $custom_label_3 : null, - 'custom_label_4' => $custom_label_4 ? $custom_label_4 : null, - 'additional_message' => $additional_message == 'on' ? 1 : 0, - 'set_id' => $set_id ? $set_id : null, - 'quantity' => $quantity, - 'additional_message_text' => $additional_message_text ? $additional_message_text : null, - 'additional_message_required' => $additional_message_required, - 'producer_id' => !empty( $producer_id ) ? $producer_id : null, - 'product_unit_id' => !empty( $product_unit ) ? $product_unit : null, - 'weight' => !empty( $weight ) ? $weight : null, - ], [ - 'id' => (int) $product_id, - ] ); - - $mdb -> update( 'pp_shop_products', [ - 'additional_message' => $additional_message == 'on' ? 1 : 0, - ], [ - 'parent_id' => (int) $product_id, - ] ); - - \admin\factory\ShopProduct::update_product_combinations_prices( $product_id, $price_brutto, $vat, $price_brutto_promo ); - - $langs = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->languagesList( true ); - foreach ( $langs as $lg ) - { - if ( $translation_id = $mdb -> get( 'pp_shop_products_langs', 'id', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lg['id'] ] ] ) ) - { - $current_seo_link = $mdb -> get( 'pp_shop_products_langs', 'seo_link', [ 'id' => $translation_id ] ); - - if ( $seo_link[$lg['id']] ) - $new_seo_link = \S::seo( $seo_link[$lg['id']] ); - else - $new_seo_link = \S::seo( 'p-' . $product_id . '-' . $name[$lg['id']] ); - - if ( $new_seo_link !== $current_seo_link and $current_seo_link != '' ) - { - if ( $mdb -> count( 'pp_redirects', [ 'from' => $new_seo_link, 'to' => $current_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] ) ) - $mdb -> delete( 'pp_redirects', [ 'from' => $new_seo_link, 'to' => $current_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] ); - - $mdb -> delete( 'pp_redirects', [ - 'AND' => [ - 'product_id' => $product_id, - 'lang_id' => $lg['id'], - 'from' => $current_seo_link, - 'to[!]' => $new_seo_link, - ], - ] ); - - if ( !self::seoLinkUsedByOtherProduct( (int) $product_id, (string) $lg['id'], (string) $current_seo_link ) ) - { - self::removeConflictingRedirectSources( (int) $product_id, (string) $lg['id'], (string) $current_seo_link ); - - if ( !$mdb -> count( 'pp_redirects', [ 'from' => $current_seo_link, 'to' => $new_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] ) ) - { - if ( \S::canAddRedirect( $current_seo_link, $new_seo_link, $lg['id'] ) ) - $mdb -> insert( 'pp_redirects', [ 'from' => $current_seo_link, 'to' => $new_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] ); - } - } - else - $mdb -> delete( 'pp_redirects', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lg['id'], 'from' => $current_seo_link ] ] ); - } - - $mdb -> update( 'pp_shop_products_langs', [ - 'name' => $name[$lg['id']] ? $name[$lg['id']] : null, - 'short_description' => $short_description[$lg['id']] ? $short_description[$lg['id']] : null, - 'description' => $description[$lg['id']] ? $description[$lg['id']] : null, - 'meta_description' => $meta_description[$lg['id']] ? $meta_description[$lg['id']] : null, - 'meta_keywords' => $meta_keywords[$lg['id']] ? $meta_keywords[$lg['id']] : null, - 'seo_link' => $seo_link[$lg['id']] ? \S::seo($seo_link[$lg['id']]) : null, - 'copy_from' => $copy_from[$lg['id']] ? $copy_from[$lg['id']] : null, - 'warehouse_message_zero' => $warehouse_message_zero[$lg['id']] ? $warehouse_message_zero[$lg['id']] : null, - 'warehouse_message_nonzero' => $warehouse_message_nonzero[$lg['id']] ? $warehouse_message_nonzero[$lg['id']] : null, - 'tab_name_1' => $tab_name_1[$lg['id']] ? $tab_name_1[$lg['id']] : null, - 'tab_description_1' => $tab_description_1[$lg['id']] ? $tab_description_1[$lg['id']] : null, - 'tab_name_2' => $tab_name_2[$lg['id']] ? $tab_name_2[$lg['id']] : null, - 'tab_description_2' => $tab_description_2[$lg['id']] ? $tab_description_2[$lg['id']] : null, - 'canonical' => $canonical[$lg['id']] ? $canonical[$lg['id']] : null, - 'meta_title' => $meta_title[$lg['id']] ? $meta_title[$lg['id']] : null, - 'xml_name' => $xml_name[$lg['id']] ? $xml_name[$lg['id']] : null, - 'security_information' => $security_information[$lg['id']] ? $security_information[$lg['id']] : null, - ], [ - 'id' => $translation_id - ] ); - } - else - { - $mdb -> insert( 'pp_shop_products_langs', [ - 'product_id' => (int) $product_id, - 'lang_id' => $lg['id'], - 'name' => $name[$lg['id']] ? $name[$lg['id']] : null, - 'short_description' => $short_description[$lg['id']] ? $short_description[$lg['id']] : null, - 'description' => $description[$lg['id']] ? $description[$lg['id']] : null, - 'meta_description' => $meta_description[$lg['id']] ? $meta_description[$lg['id']] : null, - 'meta_keywords' => $meta_keywords[$lg['id']] ? $meta_keywords[$lg['id']] : null, - 'seo_link' => $seo_link[$lg['id']] ? \S::seo($seo_link[$lg['id']]) : null, - 'copy_from' => $copy_from[$lg['id']] ? $copy_from[$lg['id']] : null, - 'warehouse_message_zero' => $warehouse_message_zero[$lg['id']] ? $warehouse_message_zero[$lg['id']] : null, - 'warehouse_message_nonzero' => $warehouse_message_nonzero[$lg['id']] ? $warehouse_message_nonzero[$lg['id']] : null, - 'tab_name_1' => $tab_name_1[$lg['id']] ? $tab_name_1[$lg['id']] : null, - 'tab_description_1' => $tab_description_1[$lg['id']] ? $tab_description_1[$lg['id']] : null, - 'tab_name_2' => $tab_name_2[$lg['id']] ? $tab_name_2[$lg['id']] : null, - 'tab_description_2' => $tab_description_2[$lg['id']] ? $tab_description_2[$lg['id']] : null, - 'canonical' => $canonical[$lg['id']] ? $canonical[$lg['id']] : null, - 'meta_title' => $meta_title[$lg['id']] ? $meta_title[$lg['id']] : null, - 'xml_name' => $xml_name[$lg['id']] ? $xml_name[$lg['id']] : null, - 'security_information' => $security_information[$lg['id']] ? $security_information[$lg['id']] : null, - ] ); - } - } - - $not_in = [0]; - - if (\is_array($categories)) - { - foreach ($categories as $category) - { - $not_in[] = $category; - } - } - elseif ($categories) - { - $not_in[] = $categories; - } - - $mdb -> delete('pp_shop_products_categories', ['AND' => ['product_id' => (int) $product_id, 'category_id[!]' => $not_in]]); - - $categories_tmp = $mdb -> select('pp_shop_products_categories', 'category_id', ['product_id' => (int) $product_id]); - - if (!\is_array($categories)) - { - $categories = [$categories]; - } - - $categories = array_diff($categories, $categories_tmp); - - if (\is_array($categories)) - { - foreach ($categories as $category) - { - $order = self::max_order() + 1; - - if ( $product_id and $category ) - $mdb -> insert( 'pp_shop_products_categories', [ - 'product_id' => (int)$product_id, - 'category_id' => (int)$category, - 'o' => (int) $order, - ] ); - } - } - - // produkty powiązane - $not_in = [0]; - - if (\is_array($products_related)) - { - foreach ($products_related as $product_related) - { - $not_in[] = $product_related; - } - } - elseif ($products_related) - { - $not_in[] = $products_related; - } - - $mdb -> delete('pp_shop_products_related', ['AND' => ['product_id' => (int) $product_id, 'product_related_id[!]' => $not_in]]); - - $products_related_tmp = $mdb -> select('pp_shop_products_related', 'product_related_id', ['product_id' => (int) $product_id]); - - if (!\is_array($products_related)) - { - $products_related = [$products_related]; - } - - $products_related = array_diff($products_related, $products_related_tmp); - - if (\is_array($products_related)) - { - foreach ($products_related as $product_related) - { - if ($product_id && $product_related) - { - $mdb -> insert('pp_shop_products_related', [ - 'product_id' => (int) $product_id, - 'product_related_id' => (int) $product_related, - ]); - } - } - } - - $created = false; - - $results = $mdb -> select('pp_shop_products_files', '*', ['product_id' => null]); - if (\is_array($results)) - { - foreach ($results as $row) - { - $dir = '/upload/product_files/product_'.$product_id; - - $new_file_name = str_replace('/upload/product_files/tmp', $dir, $row['src']); - - if (file_exists('..'.$row['src'])) - { - if (!is_dir('../'.$dir) && true !== $created) - { - if (mkdir('../'.$dir, 0755, true)) - { - $created = true; - } - } - rename('..'.$row['src'], '..'.$new_file_name); - } - - $mdb -> update('pp_shop_products_files', ['src' => $new_file_name, 'product_id' => (int) $product_id], ['id' => $row['id']]); - } - } - - $results = $mdb -> select('pp_shop_products_files', '*', ['AND' => ['product_id' => (int) $product_id, 'to_delete' => 1]]); - if (\is_array($results)) - { - foreach ($results as $row) - { - if (file_exists('../'.$row['src'])) - { - unlink('../'.$row['src']); - } - } - } - - $mdb -> delete('pp_shop_products_files', ['AND' => ['product_id' => (int) $product_id, 'to_delete' => 1]]); - - $created = false; - - // zdjęcia - $results = $mdb -> select('pp_shop_products_images', '*', ['product_id' => null]); - if (\is_array($results)) - { - foreach ($results as $row) - { - $dir = '/upload/product_images/product_'.$product_id; - - $new_file_name = str_replace('/upload/product_images/tmp', $dir, $row['src']); - - if (file_exists('../'.$new_file_name)) - { - $ext = strrpos($new_file_name, '.'); - $fileName_a = substr($new_file_name, 0, $ext); - $fileName_b = substr($new_file_name, $ext); - - $count = 1; - - while (file_exists('../'.$fileName_a.'_'.$count.$fileName_b)) - { - ++$count; - } - - $new_file_name = $fileName_a.'_'.$count.$fileName_b; - } - - if (file_exists('..'.$row['src'])) - { - if (!is_dir('../'.$dir) && true !== $created) - { - if (mkdir('../'.$dir, 0755, true)) - { - $created = true; - } - } - rename('..'.$row['src'], '..'.$new_file_name); - } - - $mdb -> update('pp_shop_products_images', ['src' => $new_file_name, 'product_id' => (int) $product_id], ['id' => $row['id']]); - } - } - - $results = $mdb -> select('pp_shop_products_images', '*', ['AND' => ['product_id' => (int) $product_id, 'to_delete' => 1]]); - if (\is_array($results)) - { - foreach ($results as $row) - { - if (file_exists('../'.$row['src'])) - { - unlink('../'.$row['src']); - } - } - } - - $mdb -> delete('pp_shop_products_images', ['AND' => ['product_id' => (int) $product_id, 'to_delete' => 1]]); - - // dodatkowe pola - // delete only custom fields that are not in the new list - foreach ( $custom_field_name as $custom_field ) - { - if ( !empty( $custom_field ) ) - { - $exits_custom_ids[] = (int)$mdb -> get( 'pp_shop_products_custom_fields', 'id_additional_field', [ 'AND' => [ 'id_product' => $product_id, 'name' => $custom_field ] ] ); - } - } - - $mdb -> delete( 'pp_shop_products_custom_fields', [ 'AND' => [ 'id_product' => $product_id, 'id_additional_field[!]' => $exits_custom_ids ] ] ); - - // $custom_field_name i $custom_field_required - foreach ( $custom_field_name as $i => $custom_field ) - { - if ( !empty( $custom_field ) ) - { - $custom_field_type_data = $custom_field_type[$i]; - $is_required = !empty( $custom_field_required[$i] ) ? 1 : 0; - - if ( !$mdb -> count( 'pp_shop_products_custom_fields', [ 'AND' => [ 'id_product' => $product_id, 'name' => $custom_field ] ] ) ) - { - $mdb -> insert( 'pp_shop_products_custom_fields', [ - 'id_product' => $product_id, - 'name' => $custom_field, - 'type' => $custom_field_type_data, - 'is_required' => $is_required - ]); - } - else - { - $mdb -> update( 'pp_shop_products_custom_fields', - [ - 'type' => $custom_field_type_data, - 'is_required' => $is_required - ], - [ 'AND' => [ 'id_product' => $product_id, 'name' => $custom_field ] ]); - } - } - } - - \S::htacces(); - - \S::delete_dir( '../temp/' ); - \S::delete_dir( '../thumbs/' ); - - $redis = \RedisConnection::getInstance() -> getConnection(); - if ( $redis ) - $redis -> flushAll(); - - return $product_id; - } - } - - // pobierz prostą listę z ilościami produktu - static public function get_product_quantity_list( int $product_id ) - { - global $mdb; - - return $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] ); - } - - // ADMIN - szczególy produktu - static public function product_details( int $product_id ) - { - global $mdb; - - if ( $product = $mdb -> get( 'pp_shop_products', '*', [ 'id' => $product_id ] ) ) - { - $results = $mdb -> select( 'pp_shop_products_langs', '*', [ 'product_id' => $product_id ] ); - if ( is_array( $results ) ) foreach ($results as $row) - $product['languages'][ $row['lang_id'] ] = $row; - - $product['images'] = $mdb -> select( 'pp_shop_products_images', '*', [ 'product_id' => $product_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] ); - $product['files'] = $mdb -> select( 'pp_shop_products_files', '*', [ 'product_id' => $product_id ] ); - $product['categories'] = $mdb -> select( 'pp_shop_products_categories', 'category_id', [ 'product_id' => $product_id ] ); - $product['attributes'] = $mdb -> select( 'pp_shop_products_attributes', [ 'attribute_id', 'value_id' ], [ 'product_id' => $product_id ] ); - $product['products_related'] = $mdb -> select( 'pp_shop_products_related', 'product_related_id', [ 'product_id' => $product_id ] ); - $product['custom_fields'] = $mdb -> select( 'pp_shop_products_custom_fields', '*', [ 'id_product' => $product_id ] ); - } - - return $product; - } - - // duplikowanie produktu w panelu administratora - static public function duplicate_product( int $product_id, int $with_combinations = 0 ) - { - global $mdb; - - $product = $mdb -> get( 'pp_shop_products', '*', [ 'id' => $product_id ] ); - if ( $product ) - { - $mdb -> insert( 'pp_shop_products', [ - 'price_netto' => $product['price_netto'], - 'price_brutto' => $product['price_brutto'], - 'price_netto_promo' => $product['price_netto_promo'], - 'price_brutto_promo' => $product['price_brutto_promo'], - 'vat' => $product['vat'], - 'promoted' => $product['promoted'], - 'layout_id' => $product['layout_id'], - 'new_to_date' => $product['new_to_date'], - 'stock_0_buy' => $product['stock_0_buy'], - 'wp' => $product['wp'], - 'custom_label_0' => $product['custom_label_0'], - 'custom_label_1' => $product['custom_label_1'], - 'custom_label_2' => $product['custom_label_2'], - 'custom_label_3' => $product['custom_label_3'], - 'custom_label_4' => $product['custom_label_4'], - 'additional_message' => $product['additional_message'] - ] ); - - $new_product_id = $mdb -> id(); - if ( $new_product_id ) - { - $attributes = $mdb -> select( 'pp_shop_products_attributes', '*', [ 'product_id' => $product_id ] ); - if ( \S::is_array_fix( $attributes ) ) foreach ( $attributes as $row ) - { - $mdb -> insert( 'pp_shop_products_attributes', [ - 'product_id' => $new_product_id, - 'attribute_id' => $row['attribute_id'], - 'value_id' => $row['value_id'] - ] ); - } - - $categories = $mdb -> select( 'pp_shop_products_categories', '*', [ 'product_id' => $product_id ] ); - if ( \S::is_array_fix( $categories ) ) foreach ( $categories as $row ) - { - $mdb -> insert( 'pp_shop_products_categories', [ - 'product_id' => $new_product_id, - 'category_id' => $row['category_id'], - 'o' => $row['o'] - ] ); - } - - $langs = $mdb -> select( 'pp_shop_products_langs', '*', [ 'product_id' => $product_id ] ); - if ( \S::is_array_fix( $langs ) ) foreach ( $langs as $row ) - { - $mdb -> insert( 'pp_shop_products_langs', [ - 'product_id' => $new_product_id, - 'lang_id' => $row['lang_id'], - 'name' => $row['name'] . ' - kopia', - 'short_description' => $row['short_description'], - 'description' => $row['description'], - 'tab_name_1' => $row['tab_name_1'], - 'tab_description_1' => $row['tab_description_1'], - 'tab_name_2' => $row['tab_name_2'], - 'tab_description_2' => $row['tab_description_2'], - 'meta_description' => $row['meta_description'], - 'meta_keywords' => $row['meta_keywords'], - 'copy_from' => $row['copy_from'], - 'warehouse_message_zero' => $row['warehouse_message_zero'], - 'warehouse_message_nonzero' => $row['warehouse_message_nonzero'] - ] ); - } - - // custom fields - $custom_fields = $mdb -> select( 'pp_shop_products_custom_fields', '*', [ 'id_product' => $product_id ] ); - if ( \S::is_array_fix( $custom_fields ) ) foreach ( $custom_fields as $row ) - { - $mdb -> insert( 'pp_shop_products_custom_fields', [ - 'id_product' => $new_product_id, - 'name' => $row['name'] - ] ); - } - } - - // duplikowanie kombinacji produktu - if ( $with_combinations ) - { - $product_combinations = $mdb -> select( 'pp_shop_products', '*', [ 'parent_id' => $product_id ] ); - foreach ( $product_combinations as $product_combination ) - { - $mdb -> insert( 'pp_shop_products', [ - 'parent_id' => $new_product_id, - 'permutation_hash' => $product_combination['permutation_hash'], - 'price_netto' => $product_combination['price_netto'], - 'price_brutto' => $product_combination['price_brutto'], - 'price_netto_promo' => $product_combination['price_netto_promo'], - 'price_brutto_promo' => $product_combination['price_brutto_promo'], - 'vat' => $product_combination['vat'], - 'stock_0_buy' => $product_combination['stock_0_buy'], - 'quantity' => $product_combination['quantity'], - 'wp' => $product_combination['wp'], - 'additional_message' => $product_combination['additional_message'], - 'additional_message_text' => $product_combination['additional_message_text'], - 'additional_message_required' => $product_combination['additional_message_required'] - ] ); - - $combination_id = $mdb -> id(); - if ( $combination_id ) - { - $pp_shop_products_attributes = $mdb -> select( 'pp_shop_products_attributes', '*', [ 'product_id' => $product_combination['id'] ] ); - foreach ( $pp_shop_products_attributes as $pp_shop_products_attribute ) - { - $mdb -> insert( 'pp_shop_products_attributes', [ - 'product_id' => $combination_id, - 'attribute_id' => $pp_shop_products_attribute['attribute_id'], - 'value_id' => $pp_shop_products_attribute['value_id'] - ] ); - } - } - } - } - - return true; - } - return false; - } - - // - // KOMBINACJE PRODUKTU - // - - static public function product_combination_stock_0_buy_save( int $product_id, $stock_0_buy ) - { - global $mdb; - return $mdb -> update( 'pp_shop_products', [ 'stock_0_buy' => $stock_0_buy == 'true' ? 1 : 0 ], [ 'id' => $product_id ] ); - } - - static public function product_combination_sku_save( int $product_id, $sku ) - { - global $mdb; - return $mdb -> update( 'pp_shop_products', [ 'sku' => $sku ], [ 'id' => $product_id ] ); - } - - static public function product_combination_quantity_save( int $product_id, $quantity ) - { - global $mdb; - return $mdb -> update( 'pp_shop_products', [ 'quantity' => $quantity == '' ? $quantity = null : $quantity = (int) $quantity ], [ 'id' => $product_id ] ); - } - - static public function product_combination_price_save( int $product_id, $price_netto ) - { - global $mdb; - - $vat = $mdb -> get( 'pp_shop_products', 'vat', [ 'id' => $product_id ] ); - - $price_brutto = $price_netto * ( 1 + ( $vat / 100 ) ); - - return $mdb -> update( 'pp_shop_products', [ 'price_netto' => $price_netto == '' ? $price_netto = null : $price_netto = (float) $price_netto, 'price_brutto' => $price_brutto == '' ? $price_brutto = null : $price_brutto = (float) $price_brutto ], [ 'id' => $product_id ] ); - } - - // aktualizacja ceny produktu pod wpływem zmiany ceny wartości atrybutu - static public function update_product_price_by_attribute_value_impact( $value_id, $impact_on_the_price ) - { - global $mdb; - - $products = $mdb -> select( 'pp_shop_products_attributes', [ 'product_id' ], [ 'value_id' => $value_id ] ); - if ( is_array( $products ) ) foreach ( $products as $row ) - { - $parent_id = $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $row['product_id'] ] ); - $product = $mdb -> get( 'pp_shop_products', '*', [ 'id' => $parent_id ] ); - if ( $product ) - { - $price_brutto = $product['price_brutto'] + \S::normalize_decimal( $impact_on_the_price ); - $price_netto = \S::normalize_decimal( $price_brutto / ( 1 + ( $product['vat'] / 100 ) ) ); - - if ( $product['price_netto_promo'] ) - { - $price_brutto_promo = $product['price_brutto_promo'] + \S::normalize_decimal( $impact_on_the_price ); - $price_netto_promo = \S::normalize_decimal( $price_brutto_promo / ( 1 + ( $product['vat'] / 100 ) ) ); - } - else - { - $price_netto_promo = null; - $price_brutto_promo = null; - } - - if ( $impact_on_the_price > 0 ) - { - $mdb -> update( 'pp_shop_products', [ 'price_netto' => $price_netto, 'price_brutto' => $price_brutto, 'price_netto_promo' => $price_netto_promo, 'price_brutto_promo' => $price_brutto_promo, 'date_modify' => date( 'Y-m-d H:i:s' ) ], [ 'id' => $row['product_id'] ] ); - } - else if ( isset( $impact_on_the_price ) && $impact_on_the_price == 0 && $impact_on_the_price != '' ) - { - $mdb -> update( 'pp_shop_products', [ 'price_netto' => null, 'price_brutto' => null, 'price_netto_promo' => null, 'price_brutto_promo' => null, 'quantity' => null, 'stock_0_buy' => null, 'date_modify' => date( 'Y-m-d H:i:s' ) ], [ 'id' => $row['product_id'] ] ); - } - } - } - } -} diff --git a/temp/update_build/tmp_0.275/docs/CHANGELOG.md b/temp/update_build/tmp_0.275/docs/CHANGELOG.md deleted file mode 100644 index 2bc9e81..0000000 --- a/temp/update_build/tmp_0.275/docs/CHANGELOG.md +++ /dev/null @@ -1,458 +0,0 @@ -# Changelog shopPRO - -Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. - ---- - -## ver. 0.275 (2026-02-15) - ShopCategory - -- **ShopCategory** - migracja `/admin/shop_category/*` na Domain + DI + nowe endpointy AJAX - - NOWE: `Domain\Category\CategoryRepository` (`sortTypes`, `subcategories`, `categoryDetails`, `categoryProducts`, `save`, `categoryDelete`, `saveCategoriesOrder`, `saveProductOrder`, `categoryTitle`) - - NOWE: `admin\Controllers\ShopCategoryController` (DI) z akcjami `list/view_list`, `edit/category_edit`, `save`, `delete/category_delete`, `products/category_products`, `category_url_browser`, `save_categories_order`, `save_products_order`, `cookie_categories` - - UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopCategory` - - UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_category/list/` - - UPDATE: widoki `shop-category/*` - wydzielenie skryptow do `*-custom-script.php`, ujednolicone strzalki drzewa (`button + caret + aria-expanded`) - - UPDATE: AJAX drzewek przepiety z `/admin/ajax.php?a=*` na `/admin/shop_category/*` - - UPDATE: zaleznosci `ShopProduct` przepiete z `admin\factory\ShopCategory` na `Domain\Category\CategoryRepository` - - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopCategory.php`, `autoload/admin/factory/class.ShopCategory.php`, `autoload/admin/view/class.ShopCategory.php` - - CLEANUP: usuniety preload `class.ShopCategory.php` z `libraries/grid/config.php` -- TEST: - - NOWE: `tests/Unit/Domain/Category/CategoryRepositoryTest.php` - - NOWE: `tests/Unit/admin/Controllers/ShopCategoryControllerTest.php` - - Testy punktowe: **OK (16 tests, 72 assertions)** - ---- - -## ver. 0.274 (2026-02-15) - ShopProduct mass_edit + UI trees - -- **ShopProduct (mass_edit)** - migracja akcji masowej edycji na Domain + DI - - NOWE: `admin\Controllers\ShopProductController` (DI) z akcjami `mass_edit`, `mass_edit_save`, `get_products_by_category` - - UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopProduct` - - UPDATE: `Domain\Product\ProductRepository` rozszerzone o metody `allProductsForMassEdit`, `getProductsByCategory`, `applyDiscountPercent` (+ aktualizacja cen kombinacji) - - CLEANUP: usuniete legacy akcje `mass_edit`, `mass_edit_save`, `get_products_by_category` z `admin\controls\ShopProduct` -- **ShopProduct mass_edit UI** - przebudowa widoku i skryptu - - UPDATE: `admin/templates/shop-product/mass-edit.php` przepiety na nowy partial JS `mass-edit-custom-script` - - NOWE: `admin/templates/shop-product/mass-edit-custom-script.php` (nestedSortable + iCheck + stabilizacja drzewka) - - UPDATE: `admin/templates/shop-product/subcategories-list.php` ujednolicone strzalki (button + caret) - - FIX: zaznaczenie kategorii w drzewku nie zaznacza automatycznie produktow na liscie -- **Pages / Articles UI** - ujednolicenie drzewek - - UPDATE: `/admin/pages/list/` - nowe strzalki drzewa + `aria-expanded` + odswiezanie stanu branch/leaf - - UPDATE: `/admin/articles/edit/*` (zakladka wyswietlania) - nowe strzalki i checkboxy (iCheck) dla drzewka stron -- **ShopClients** - migracja `/admin/shop_clients` na Domain + DI + nowe widoki - - NOWE: `Domain\Client\ClientRepository` (`listForAdmin`, `ordersForClient`, `totalsForClient`) - - NOWE: `admin\Controllers\ShopClientsController` (DI) z akcjami `list`, `details` + aliasy legacy `view_list`, `clients_details` - - UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopClients` - - UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_clients/list/` - - UPDATE: widoki `shop-clients/view-list` i `shop-clients/clients-details` przepiete na `components/table-list` - - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopClients.php`, `autoload/admin/factory/class.ShopClients.php` -- TEST: - - NOWE: `tests/Unit/admin/Controllers/ShopProductControllerTest.php` - - NOWE: `tests/Unit/Domain/Client/ClientRepositoryTest.php`, `tests/Unit/admin/Controllers/ShopClientsControllerTest.php` - - UPDATE: `tests/Unit/Domain/Product/ProductRepositoryTest.php` (nowe przypadki dla mass_edit) - - UPDATE: `tests/bootstrap.php` (stub `S::normalize_decimal()`) -- Testy: **OK (361 tests, 1125 assertions)** - ---- - -## ver. 0.273 (2026-02-15) - ShopProducer - -- **ShopProducer** - migracja `/admin/shop_producer` na Domain + DI + nowe widoki - - NOWE: `Domain\Producer\ProducerRepository` (`listForAdmin`, `find`, `save`, `delete`, `allProducers`, `findForFrontend`, `producerProducts`, `allActiveIds`) - - NOWE: `admin\Controllers\ShopProducerController` (DI) z akcjami `list`, `edit`, `save`, `delete` - - UPDATE: modul `/admin/shop_producer/*` dziala na `components/table-list` i `components/form-edit` z zakladkami jezykowymi (Opis + SEO) - - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_producer/list/` - - UPDATE: `shop\Producer` przepiety na fasade do `Domain\Producer\ProducerRepository` - - UPDATE: `admin\factory\ShopProduct` - 2 wywolania `admin\factory\ShopTransport` przepiete na `Domain\Transport\TransportRepository` - - UPDATE: `admin\controls\ShopProduct` - usuniety fallback do `admin\factory\Layouts` - - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopProducer.php`, `admin/templates/shop-producer/list.php`, `admin/templates/shop-producer/edit.php` - - CLEANUP: usuniete 6 pustych factory facades: `admin\factory\Languages`, `admin\factory\Newsletter`, `admin\factory\Scontainers`, `admin\factory\ShopProducer`, `admin\factory\ShopTransport`, `admin\factory\Layouts` - - TEST: dodane `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` i `tests/Unit/admin/Controllers/ShopProducerControllerTest.php` -- Testy: **OK (338 tests, 1063 assertions)** - ---- - -## ver. 0.272 (2026-02-15) - ShopProductSets - -- **ShopProductSets** - migracja `/admin/shop_product_sets` na Domain + DI + nowe widoki - - NOWE: `Domain\ProductSet\ProductSetRepository` (`listForAdmin`, `find`, `save`, `delete`, `allSets`, `allProductsMap`) - - NOWE: `admin\Controllers\ShopProductSetsController` (DI) z akcjami `list`, `edit`, `save`, `delete` - - UPDATE: modul `/admin/shop_product_sets/*` dziala na `components/table-list` i `components/form-edit` + Selectize multi-select produktow - - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_product_sets/list/` - - UPDATE: `shop\ProductSet` przepiety na fasade do `Domain\ProductSet\ProductSetRepository` - - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopProductSets.php`, `autoload/admin/factory/class.ShopProductSet.php`, `admin/templates/shop-product-sets/view-list.php`, `admin/templates/shop-product-sets/set-edit.php` - - TEST: dodane `tests/Unit/Domain/ProductSet/ProductSetRepositoryTest.php` i `tests/Unit/admin/Controllers/ShopProductSetsControllerTest.php` -- Testy: **OK (324 tests, 1000 assertions)** - ---- - -## ver. 0.271 (2026-02-14) - ShopAttribute - -- **ShopAttribute** - migracja `/admin/shop_attribute` na Domain + DI + nowe widoki - - NOWE: `Domain\Attribute\AttributeRepository` (`listForAdmin`, `findAttribute`, `saveAttribute`, `deleteAttribute`, `findValues`, `saveValues`, `saveLegacyValues`, `valueDetails`) - - NOWE: `admin\Controllers\ShopAttributeController` (DI) z akcjami `list`, `edit`, `save`, `delete`, `values`, `values_save`, `value_row_tpl` - - UPDATE: modul `/admin/shop_attribute/*` dziala na `components/table-list` i `components/form-edit` - - UPDATE: nowy edytor wartosci cechy (`values-edit`) z walidacja serwerowa i stabilnym `row_key` (bez indeksow do wyboru domyslnej wartosci) - - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_attribute/list/` (bez aliasow legacy) - - UPDATE: przepiecie zaleznosci kombinacji produktu (`admin\controls\ShopProduct`, `admin\factory\ShopProduct`, `admin/templates/shop-product/product-combination.php`) na `Domain\Attribute\AttributeRepository` i `shop\ProductAttribute` - - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopAttribute.php`, `autoload/admin/factory/class.ShopAttribute.php`, `autoload/admin/view/class.ShopAttribute.php`, `admin/templates/shop-attribute/_partials/value.php` - - TEST: dodane `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php` i `tests/Unit/admin/Controllers/ShopAttributeControllerTest.php` -- Testy: **OK (312 tests, 948 assertions)** - ---- - -## ver. 0.270 (2026-02-14) - Apilo payment/status sync hardening - -- **Shop/Order + Apilo** - utwardzenie synchronizacji platnosci i statusow zamowien - - FIX: `shop\Order::set_as_paid()` wysyla do Apilo mapowany typ platnosci (`payment_method_id` -> `apilo_payment_type_id`) zamiast stalego `type = 1` - - NOWE: retry queue dla chwilowej niedostepnosci Apilo (`temp/apilo-sync-queue.json`) dla sync platnosci i statusu - - NOWE: `shop\Order::process_apilo_sync_queue()` przetwarza zalegle syncy - - UPDATE: `cron.php` uruchamia przetwarzanie kolejki sync Apilo przy aktywnej integracji - - UPDATE: rozszerzone logowanie debug (`logs/apilo.txt`) o HTTP code i bledy cURL dla sync platnosci/statusu -- Testy: **OK (300 tests, 895 assertions)** - ---- - -## ver. 0.269 (2026-02-14) - ShopTransport - -- **ShopTransport** - migracja `/admin/shop_transport` na Domain + DI + nowe widoki - - NOWE: `Domain\Transport\TransportRepository` (`listForAdmin`, `find`, `save`, `allActive`, `allForAdmin`, `findActiveById`, `getTransportCost`, `lowestTransportPrice`, `getApiloCarrierAccountId`) - - NOWE: `admin\Controllers\ShopTransportController` (DI) z akcjami `list`, `edit`, `save` - - NOWE: widoki `shop-transport/transports-list.php` i `shop-transport/transport-edit.php` + `transport-edit-custom-script.php` - - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_transport/list/` - - UPDATE: `admin\factory\ShopTransport`, `front\factory\ShopTransport` przepiete na nowe repozytorium - - FIX: `save()` return type `?int` zamiast `int|bool` (spojnosc z PaymentMethod) - - FIX: `toSwitchValue()` helper zamiast `=== 'on'` (obsluga '1', 'on', 'true', 'yes') - - FIX: `\S::delete_dir()` przeniesione z repozytorium do kontrolera (DDD) - - FIX: Medoo `select()` syntax - ORDER w WHERE zamiast 4-arg form - - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopTransport.php`, `autoload/admin/view/class.ShopTransport.php`, `admin/templates/shop-transport/view-list.php` - - FIX: `transports-list.php` - zmienna `'viewModel'` zmieniona na `'list'` (zgodnie z `table-list.php` komponentem) -- Testy: **OK (300 tests, 895 assertions)** - ---- - -## ver. 0.268 (2026-02-14) - ShopPaymentMethod + Apilo token keepalive - -- **ShopPaymentMethod** - migracja `/admin/shop_payment_method` na Domain + DI + nowe widoki - - NOWE: `Domain\PaymentMethod\PaymentMethodRepository` (`listForAdmin`, `find`, `save`, `allActive`, `allForAdmin`, `findActiveById`, `isActive`, `getApiloPaymentTypeId`, `forTransport`) - - NOWE: `admin\Controllers\ShopPaymentMethodController` (DI) z akcjami `list`, `edit`, `save` - - NOWE: widoki `shop-payment-method/payment-methods-list.php` i `shop-payment-method/payment-method-edit.php` - - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_payment_method/list/` - - UPDATE: `admin\controls\ShopTransport`, `front\factory\ShopPaymentMethod`, `shop\PaymentMethod` przepiete na nowe repozytorium - - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopPaymentMethod.php`, `autoload/admin/factory/class.ShopPaymentMethod.php`, `autoload/admin/view/class.ShopPaymentMethod.php`, `admin/templates/shop-payment-method/view-list.php` -- **Integrations/Apilo** - stabilizacja tokenu i lepszy feedback - - NOWE: automatyczne odswiezanie tokenu Apilo przed wygasnieciem (`apiloKeepalive`, refresh lead time) - - UPDATE: cron uruchamia keepalive i odswieza konfiguracje Apilo - - UPDATE: bardziej szczegolowe komunikaty bledow dla przyciskow integracji Apilo (co zrobic dalej) -- Testy: **OK (280 tests, 828 assertions)** - ---- - -## ver. 0.267 (2026-02-14) - ShopStatuses - -- **ShopStatuses** - migracja `/admin/shop_statuses` na Domain + DI + nowe widoki - - NOWE: `Domain\ShopStatus\ShopStatusRepository` (`listForAdmin`, `find`, `save`, `getApiloStatusId`, `getByIntegrationStatusId`, `allStatuses`) - - NOWE: `admin\Controllers\ShopStatusesController` (DI) z akcjami `list`, `edit`, `save` (bez aliasow legacy) - - NOWE: typ pola `FormFieldType::COLOR` + `FormField::color()` + `FormFieldRenderer::renderColor()` (color picker HTML5 zsynchronizowany z polem tekstowym) - - UPDATE: modul `/admin/shop_statuses/*` dziala na `components/table-list` i `components/form-edit` - - UPDATE: `front\factory\ShopStatuses` jako fasada delegujaca do `Domain\ShopStatus\ShopStatusRepository` - - UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_statuses/list/` - - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopStatuses.php`, `autoload/admin/factory/class.ShopStatuses.php` - - UWAGA: statusy maja ID od 0 - kluczowe dla walidacji (find/save uzywaja `$id < 0`) -- Testy: **OK (254 tests, 736 assertions)** - ---- - -## ver. 0.266 (2026-02-13) - ShopCoupon - -- **ShopCoupon** - migracja `/admin/shop_coupon` na Domain + DI + nowe widoki - - NOWE: `Domain\Coupon\CouponRepository` (`listForAdmin`, `find`, `save`, `delete`, `categoriesTree`) - - NOWE: `admin\Controllers\ShopCouponController` (DI) z akcjami `list`, `edit`, `save`, `delete` - - UPDATE: kompatybilnosc aliasow legacy (`view_list`, `coupon_edit`, `coupon_save`, `coupon_delete`) obslugiwana przez nowy kontroler - - UPDATE: modul `/admin/shop_coupon/*` dziala na `components/table-list` i `components/form-edit` - - NOWE: widoki/partiale `shop-coupon/coupons-list`, `shop-coupon/coupon-edit-new`, `shop-coupon/coupon-categories-selector`, `shop-coupon/coupon-categories-tree`, `shop-coupon/coupon-edit-custom-script` - - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopCoupon.php`, `autoload/admin/factory/class.ShopCoupon.php`, `admin/templates/shop-coupon/view-list.php`, `admin/templates/shop-coupon/coupon-edit.php` - - UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_coupon/list/` - - FIX: ujednolicone UI drzewek i checkboxow miedzy kuponami i layoutami -- Testy: **OK (235 tests, 682 assertions)** - ---- - -## ver. 0.265 (2026-02-13) - ShopPromotion poprawki - -- **ShopPromotion** - stabilizacja po migracji - - UPDATE: dodane `date_from` w `Domain\Promotion\PromotionRepository` (save/find/list/sort) - - UPDATE: `admin\Controllers\ShopPromotionController` rozszerzony o pole `Data od` na formularzu i kolumne `Data od` na liscie - - UPDATE: `shop\Promotion::get_active_promotions()` filtruje aktywnosc po `date_from` i `date_to` - - FIX: zapis edycji promocji nie tworzy nowego rekordu (hidden `id` + fallback `id` z URL) - - TEST: rozszerzono `PromotionRepositoryTest` o asercje `date_from` -- Testy: **OK (222 tests, 614 assertions)** - ---- - -## ver. 0.264 (2026-02-13) - ShopPromotion - -- **ShopPromotion** - migracja `/admin/shop_promotion` na Domain + DI + nowe widoki - - NOWE: `Domain\Promotion\PromotionRepository` (`listForAdmin`, `find`, `save`, `delete`, `categoriesTree`, invalidacja cache aktywnych promocji) - - NOWE: `admin\Controllers\ShopPromotionController` (DI) z akcjami `list`, `edit`, `save`, `delete` - - UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopPromotion` - - UPDATE: modul `/admin/shop_promotion/*` dziala na `components/table-list` i `components/form-edit` - - NOWE: widoki/partiale `shop-promotion/promotions-list`, `shop-promotion/promotion-edit`, `shop-promotion/promotion-categories-selector`, `shop-promotion/promotion-categories-tree`, `shop-promotion/promotion-edit-custom-script` - - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopPromotion.php`, `autoload/admin/factory/class.ShopPromotion.php`, `admin/templates/shop-promotion/view-list.php` - - UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_promotion/list/` -- Testy: **OK (222 tests, 609 assertions)** - ---- - -## ver. 0.263 (2026-02-13) - Integrations + cleanup Sellasist/Baselinker - -- NOWE: `Domain\Integrations\IntegrationsRepository` (settings Apilo/ShopPRO, OAuth, product linking, API fetch) -- NOWE: `admin\Controllers\IntegrationsController` (DI) dla akcji Apilo i ShopPRO -- UPDATE: `admin\factory\Integrations` jako fasada delegujaca do repozytorium -- **CLEANUP: usunieto integracje Sellasist i Baselinker z calego projektu:** - - Usuniete klasy: `admin\controls\Integrations`, `admin\controls\Baselinker`, `admin\factory\Baselinker`, `front\factory\Shop`, `shop\ShopStatus` - - Usuniete szablony: `integrations/sellasist-settings.php`, `integrations/baselinker-settings.php`, `admin/templates/baselinker/` - - Wyczyszczone referencje w: `cron.php`, `cron/cron-xml.php`, `shop\Order`, kontrolery/factory/front Shop* -- Testy: **OK (212 tests, 577 assertions)** - ---- - -## ver. 0.262 (2026-02-13) - Pages - -- NOWE: `Domain\Pages\PagesRepository` (CRUD menu/stron, drzewo stron, sortowanie, SEO) -- NOWE: `admin\Controllers\PagesController` (DI) dla akcji menu/page/AJAX -- UPDATE: widoki `admin/templates/pages/*` przepiete na dane z kontrolera/repozytorium -- UPDATE: endpointy AJAX przepiete z `admin/ajax.php?a=*` na `/admin/pages/*` -- CLEANUP: usuniete legacy `controls/Pages`, `view/Pages`, `factory/Pages`, `ajax/pages.php` -- Testy: **OK (186 tests, 478 assertions)** - ---- - -## ver. 0.261 (2026-02-13) - Articles (dalsza refaktoryzacja) - -- UPDATE: `Domain\Article\ArticleRepository` rozszerzone o metody UI/admin i `saveFilesOrder()` -- UPDATE: `admin\Controllers\ArticlesController` obsluguje AJAX: `article_image_alt_change`, `article_file_name_change`, `article_image_delete`, `article_file_delete`, `filesOrderSave` -- UPDATE: lista artykulow nie korzysta juz z `admin\factory\Articles::article_pages()` -- UPDATE: widok edycji przepiety z `/admin/ajax.php` na `/admin/articles/*` -- UPDATE: drag&drop sortowania listy zalacznikow -- CLEANUP: usuniete `autoload/admin/view/class.Articles.php` i `admin/ajax/articles.php` -- Testy: **OK (178 tests, 443 assertions)** - ---- - -## ver. 0.260 (2026-02-12) - ArticlesArchive - -- NOWE: `admin\Controllers\ArticlesArchiveController` (DI) -- UPDATE: `Domain\Article\ArticleRepository` rozszerzone o `listArchivedForAdmin()`, `restore()`, `deletePermanently()` -- UPDATE: `/admin/articles_archive/view_list/` migrowane na `components/table-list` -- CLEANUP: usuniete legacy `controls/ArticlesArchive`, `factory/ArticlesArchive`, `view/ArticlesArchive` -- Testy: **OK (165 tests, 424 assertions)** - ---- - -## ver. 0.259 (2026-02-12) - Scontainers - -- NOWE: `Domain\Scontainers\ScontainersRepository` (listForAdmin, find, save, delete, detailsForLanguage) -- NOWE: `admin\Controllers\ScontainersController` (DI) -- UPDATE: `/admin/scontainers/*` migrowane na `components/table-list` i `components/form-edit` -- UPDATE: `admin\factory\Scontainers` i `front\factory\Scontainers` jako fasady -- CLEANUP: usuniete `controls/Scontainers`, `view/Scontainers` -- Testy: **OK (158 tests, 397 assertions)** - ---- - -## ver. 0.258 (2026-02-12) - Newsletter (stabilizacja) - -- UPDATE: tymczasowo wylaczono flow `prepare/send/preview` (wymaga przebudowy) -- UPDATE: tymczasowo wylaczono modul `Szablony uzytkownika` -- UPDATE: aktywna obsluga tylko szablonow administracyjnych (`is_admin = 1`) -- CLEANUP: usuniete nieuzywane widoki `prepare.php`, `preview.php`, `email-templates-user.php` - ---- - -## ver. 0.257 (2026-02-12) - Newsletter - -- NOWE: `Domain\Newsletter\NewsletterRepository` (subskrybenci, szablony, ustawienia, kolejka wysylki) -- NOWE: `Domain\Newsletter\NewsletterPreviewRenderer` (render podgladu) -- NOWE: `admin\Controllers\NewsletterController` (DI) -- UPDATE: `/admin/newsletter/*` migrowane na `components/table-list` i `components/form-edit` -- UPDATE: `admin\factory\Newsletter` jako fasada; `front\factory\Newsletter` bez `admin\view\Newsletter` -- CLEANUP: usuniete `controls/Newsletter`, `view/Newsletter` -- Testy: **OK (150 tests, 372 assertions)** - ---- - -## ver. 0.256 (2026-02-12) - Layouts - -- NOWE: `Domain\Layouts\LayoutsRepository` (find, save, delete, listForAdmin, menusWithPages, categoriesTree) -- NOWE: `admin\Controllers\LayoutsController` (DI) -- UPDATE: lista `/admin/layouts/view_list/` migrowana na `components/table-list` -- UPDATE: widok `layouts/layout-edit` korzysta z danych z repozytorium -- NOWE: partial `admin/templates/layouts/subcategories-list.php` -- UPDATE: `Domain\Languages\LanguagesRepository::defaultLanguageId()` jako wspolna metoda -- UPDATE: `ArticlesController` korzysta z `LayoutsRepository` (DI) -- CLEANUP: usuniete `controls/Layouts`, `view/Layouts`; `factory/Layouts` jako fasada -- Testy: **OK (141 tests, 336 assertions)** - ---- - -## ver. 0.255 (2026-02-12) - Languages DI cleanup - -- UPDATE: SettingsController, BannerController, DictionariesController, ArticlesController pobieraja liste jezykow przez `Domain/Languages/LanguagesRepository` (DI) -- UPDATE: router DI przekazuje `LanguagesRepository` do kontrolerow -- UPDATE: legacy `admin/controls`, `admin/factory/Shop*` przepiete na `LanguagesRepository` -- FIX: `admin/factory/class.Languages.php` poprawione na ` | Kolumny dynamiczne per jezyk (np. pl, en) | - -**Uzywane w:** `Domain\\Languages\\LanguagesRepository`, `admin\\Controllers\\LanguagesController`, `front\\factory\\Languages` - -**Aktualizacja 2026-02-12:** modul jezykow i tlumaczen (`pp_langs`, `pp_langs_translations`) obslugiwany przez `Domain\\Languages\\LanguagesRepository`. - -## pp_layouts -Szablony layoutow (HTML/CSS/JS + flagi domyslne). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| name | Nazwa szablonu | -| html | Kod HTML | -| css | Kod CSS | -| js | Kod JS | -| m_html | Kod HTML mobilny | -| m_css | Kod CSS mobilny | -| m_js | Kod JS mobilny | -| status | Domyslny layout stron (0/1) | -| categories_default | Domyslny layout kategorii (0/1) | - -**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `admin\\Controllers\\LayoutsController`, `front\\factory\\Layouts` - -## pp_layouts_pages -Przypisanie layoutow do stron CMS. - -| Kolumna | Opis | -|---------|------| -| layout_id | FK do pp_layouts | -| page_id | FK do pp_pages | - -**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `front\\factory\\Layouts` - -## pp_layouts_categories -Przypisanie layoutow do kategorii sklepu. - -| Kolumna | Opis | -|---------|------| -| layout_id | FK do pp_layouts | -| category_id | FK do pp_shop_categories | - -**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `front\\factory\\Layouts` - -**Aktualizacja 2026-02-12 (ver. 0.256):** modul `/admin/layouts` korzysta z `Domain\\Layouts\\LayoutsRepository` (DI kontroler + fasada legacy). - -## pp_newsletter -Adresy e-mail zapisane do newslettera. - -| Kolumna | Opis | -|---------|------| -| id | PK | -| email | Adres e-mail subskrybenta | -| hash | Hash potwierdzenia/wypisu | -| status | 1 = potwierdzony, 0 = oczekujacy | - -**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `front\\factory\\Newsletter` - -## pp_newsletter_send -Kolejka wysylki newslettera. - -| Kolumna | Opis | -|---------|------| -| id | PK | -| email | Adres docelowy | -| dates | Zakres dat artykulow (tekst) | -| id_template | FK do `pp_newsletter_templates` (NULL gdy brak szablonu) | - -**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `front\\factory\\Newsletter::newsletter_send()` - -## pp_newsletter_templates -Szablony tresci e-maili (uzytkownik + administracyjne/systemowe). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| name | Nazwa/klucz szablonu | -| text | Tresc HTML szablonu | -| is_admin | 1 = szablon administracyjny/systemowy, 0 = szablon uzytkownika | - -**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `admin\\Controllers\\NewsletterController`, `front\\factory\\Newsletter` - -**Aktualizacja 2026-02-12 (ver. 0.257):** modul `/admin/newsletter` korzysta z `Domain\\Newsletter\\NewsletterRepository` (DI kontroler + fasada legacy). - -## pp_scontainers -Kontenery statyczne (modul /admin/scontainers). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| status | 1 = aktywny, 0 = nieaktywny | -| show_title | 1 = pokaz tytul, 0 = ukryj tytul | - -**Uzywane w:** `Domain\Scontainers\ScontainersRepository`, `admin\Controllers\ScontainersController`, `front\factory\Scontainers` - -## pp_scontainers_langs -Tlumaczenia kontenerow statycznych (per jezyk). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| container_id | FK do pp_scontainers | -| lang_id | ID jezyka (np. pl, en) | -| title | Tytul kontenera | -| text | Tresc HTML kontenera | - -**Uzywane w:** `Domain\Scontainers\ScontainersRepository`, `front\factory\Scontainers` - -**Aktualizacja 2026-02-12 (ver. 0.259):** modul `/admin/scontainers` korzysta z `Domain\Scontainers\ScontainersRepository` (DI kontroler + fasada legacy). - -**Aktualizacja 2026-02-12 (ver. 0.260):** modul `/admin/articles_archive` korzysta z `Domain\Article\ArticleRepository` (`listArchivedForAdmin`, `restore`, `deletePermanently`) przez `admin\Controllers\ArticlesArchiveController`. - -## pp_shop_attributes -Cechy produktu (modul `/admin/shop_attribute`). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| status | Status: 1 = aktywny, 0 = nieaktywny | -| type | Typ cechy: 0 = tekst, 1 = kolor, 2 = wzor | -| o | Kolejnosc wyswietlania | - -**Uzywane w:** `Domain\Attribute\AttributeRepository`, `admin\Controllers\ShopAttributeController`, `admin\controls\ShopProduct`, `admin\factory\ShopProduct` - -## pp_shop_attributes_langs -Tlumaczenia cech produktu (per jezyk). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| attribute_id | FK do pp_shop_attributes | -| lang_id | ID jezyka (np. pl, en) | -| name | Nazwa cechy | - -**Uzywane w:** `Domain\Attribute\AttributeRepository`, `shop\ProductAttribute` - -## pp_shop_attributes_values -Wartosci cech produktu. - -| Kolumna | Opis | -|---------|------| -| id | PK | -| attribute_id | FK do pp_shop_attributes | -| is_default | Czy wartosc domyslna dla cechy (0/1) | -| impact_on_the_price | Wplyw na cene wariantu (NULL = brak) | - -**Uzywane w:** `Domain\Attribute\AttributeRepository`, `admin\Controllers\ShopAttributeController`, `admin\factory\ShopProduct` - -## pp_shop_attributes_values_langs -Tlumaczenia wartosci cech (per jezyk). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| value_id | FK do pp_shop_attributes_values | -| lang_id | ID jezyka (np. pl, en) | -| name | Nazwa wyswietlana | -| value | Wewnetrzna wartosc techniczna (opcjonalna) | - -**Uzywane w:** `Domain\Attribute\AttributeRepository`, `shop\ProductAttribute` - -## pp_shop_products_attributes -Powiazanie kombinacji produktow z wartosciami cech. - -| Kolumna | Opis | -|---------|------| -| product_id | FK do pp_shop_products (kombinacja) | -| value_id | FK do pp_shop_attributes_values | - -**Uzywane w:** `Domain\Attribute\AttributeRepository::refreshCombinationPricesForValue()`, `admin\controls\ShopProduct`, `admin\factory\ShopProduct` - -**Aktualizacja 2026-02-14 (ver. 0.271):** modul `/admin/shop_attribute` korzysta z `Domain\Attribute\AttributeRepository` przez `admin\Controllers\ShopAttributeController`. Usunieto legacy klasy `admin\controls\ShopAttribute`, `admin\factory\ShopAttribute`, `admin\view\ShopAttribute`. - -## pp_shop_coupon -Kody rabatowe sklepu (modul `/admin/shop_coupon`). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| name | Kod kuponu (UNIQUE) | -| status | Status: 1 = aktywny, 0 = nieaktywny | -| send | Czy kupon zostal wyslany (0/1) | -| used | Czy kupon zostal wykorzystany (0/1) | -| date_used | Data wykorzystania kuponu (NULL gdy brak) | -| used_count | Licznik uzyc kuponu | -| type | Typ kuponu (obecnie: 1 = rabat procentowy na koszyk) | -| amount | Wartosc kuponu (np. procent) | -| one_time | Czy kupon jednorazowy (0/1) | -| include_discounted_product | Czy obejmuje rowniez produkty przecenione (0/1) | -| categories | JSON z ID kategorii objetych kuponem (NULL = bez ograniczenia) | - -**Uzywane w:** `Domain\Coupon\CouponRepository`, `admin\Controllers\ShopCouponController`, `shop\Coupon`, `front\factory\ShopCoupon`, `front\factory\ShopOrder` - -**Aktualizacja 2026-02-13 (ver. 0.266):** modul `/admin/shop_coupon` korzysta z `Domain\Coupon\CouponRepository` przez `admin\Controllers\ShopCouponController`. Usunieto legacy klasy `admin\controls\ShopCoupon` i `admin\factory\ShopCoupon`. - -## pp_shop_promotion -Promocje sklepu (modul `/admin/shop_promotion`). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| name | Nazwa promocji | -| status | Status: 1 = aktywna, 0 = nieaktywna | -| condition_type | Typ warunku promocji (slownik w `shop\Promotion::$condition_type`) | -| discount_type | Typ rabatu (slownik w `shop\Promotion::$discount_type`) | -| amount | Wartosc rabatu (np. procent) | -| date_from | Data startu promocji (NULL = aktywna od razu) | -| date_to | Data konca promocji (NULL = bez daty konca) | -| categories | JSON z ID kategorii grupy I | -| condition_categories | JSON z ID kategorii grupy II | -| include_coupon | Czy laczyc z kuponami rabatowymi (0/1) | -| include_product_promo | Czy uwzgledniac produkty przecenione (0/1) | -| min_product_count | Minimalna liczba produktow (dla wybranych warunkow) | -| price_cheapest_product | Cena najtanszego produktu (dla wybranych warunkow) | - -**Uzywane w:** `Domain\Promotion\PromotionRepository`, `admin\Controllers\ShopPromotionController`, `shop\Promotion`, `front\factory\ShopPromotion` - -**Aktualizacja 2026-02-13:** modul `/admin/shop_promotion` korzysta z `Domain\Promotion\PromotionRepository` przez `admin\Controllers\ShopPromotionController`. Usunieto legacy klasy `admin\controls\ShopPromotion` i `admin\factory\ShopPromotion`. - -**Aktualizacja 2026-02-13 (ver. 0.265):** dodano obsluge `date_from` (repozytorium, formularz admin, lista admin, filtr aktywnych promocji na froncie) oraz poprawke zapisu edycji promocji po `id`. - -## pp_shop_payment_methods -Metody platnosci sklepu (modul `/admin/shop_payment_method`). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| name | Nazwa metody platnosci | -| description | Opis metody platnosci (wyswietlany m.in. w checkout) | -| status | Status: 1 = aktywna, 0 = nieaktywna | -| apilo_payment_type_id | ID typu platnosci Apilo (NULL gdy brak mapowania) | -| sellasist_payment_type_id | DEPRECATED (integracja Sellasist usunieta w ver. 0.263) | - -**Uzywane w:** `Domain\PaymentMethod\PaymentMethodRepository`, `admin\Controllers\ShopPaymentMethodController`, `front\factory\ShopPaymentMethod`, `shop\PaymentMethod`, `admin\controls\ShopTransport`, `cron.php` - -**Aktualizacja 2026-02-14 (ver. 0.268):** modul `/admin/shop_payment_method` korzysta z `Domain\PaymentMethod\PaymentMethodRepository` przez `admin\Controllers\ShopPaymentMethodController`. Usunieto legacy klasy `admin\controls\ShopPaymentMethod`, `admin\factory\ShopPaymentMethod`, `admin\view\ShopPaymentMethod` oraz widok `admin/templates/shop-payment-method/view-list.php`. - -## pp_shop_transports -Rodzaje transportu sklepu (modul `/admin/shop_transport`). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| name | Nazwa (systemowa, readonly) | -| name_visible | Nazwa widoczna dla klienta | -| description | Opis metody transportu | -| status | Status: 1 = aktywny, 0 = nieaktywny | -| cost | Koszt dostawy (PLN) | -| max_wp | Maksymalna waga paczki (NULL = bez limitu) | -| default | Domyslna forma dostawy (0/1) | -| delivery_free | Czy obsluguje darmowa dostawe (0/1) | -| apilo_carrier_account_id | ID konta przewoznika w Apilo (NULL gdy brak mapowania) | -| o | Kolejnosc wyswietlania | - -**Uzywane w:** `Domain\Transport\TransportRepository`, `admin\Controllers\ShopTransportController`, `front\factory\ShopTransport` - -## pp_shop_transport_payment_methods -Powiazanie metod transportu z metodami platnosci (tabela lacznikowa). - -| Kolumna | Opis | -|---------|------| -| id_transport | FK do pp_shop_transports | -| id_payment_method | FK do pp_shop_payment_methods | - -**Uzywane w:** `Domain\Transport\TransportRepository`, `Domain\PaymentMethod\PaymentMethodRepository::forTransport()` - -**Aktualizacja 2026-02-14 (ver. 0.269):** modul `/admin/shop_transport` korzysta z `Domain\Transport\TransportRepository` przez `admin\Controllers\ShopTransportController`. Usunieto legacy klasy `admin\controls\ShopTransport`, `admin\view\ShopTransport` oraz widok `admin/templates/shop-transport/view-list.php`. - -## pp_shop_apilo_settings -Ustawienia integracji Apilo (key-value). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| name | Klucz ustawienia (np. client-id, access-token) | -| value | Wartosc ustawienia | - -**Uzywane w:** `Domain\Integrations\IntegrationsRepository`, `admin\Controllers\IntegrationsController`, `admin\factory\Integrations` - -## pp_shop_shoppro_settings -Ustawienia integracji ShopPRO (key-value). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| name | Klucz ustawienia (np. domain, db_name) | -| value | Wartosc ustawienia | - -**Uzywane w:** `Domain\Integrations\IntegrationsRepository`, `admin\Controllers\IntegrationsController`, `admin\factory\Integrations` - -**Aktualizacja 2026-02-13:** modul `/admin/integrations/` korzysta z `Domain\Integrations\IntegrationsRepository` (DI kontroler + fasada legacy). Usunieto integracje Sellasist i Baselinker. - -## pp_shop_statuses -Statusy zamowien sklepu (modul `/admin/shop_statuses`). Statusy sa predefiniowane - brak dodawania/usuwania, mozliwa edycja koloru i mapowania Apilo. - -| Kolumna | Opis | -|---------|------| -| id | PK (zaczyna sie od 0!) | -| status | Nazwa statusu (read-only) | -| color | Kolor statusu (hex, np. #ff0000) | -| o | Kolejnosc wyswietlania | -| apilo_status_id | ID statusu w Apilo (NULL gdy brak mapowania) | -| baselinker_status_id | DEPRECATED (usuniety w ver. 0.263) | -| sellasist_status_id | DEPRECATED (usuniety w ver. 0.263) | - -**Uzywane w:** `Domain\ShopStatus\ShopStatusRepository`, `admin\Controllers\ShopStatusesController`, `front\factory\ShopStatuses`, `shop\Order`, `cron.php` - -**Aktualizacja 2026-02-14 (ver. 0.267):** modul `/admin/shop_statuses` korzysta z `Domain\ShopStatus\ShopStatusRepository` przez `admin\Controllers\ShopStatusesController`. Usunieto legacy klasy `admin\controls\ShopStatuses` i `admin\factory\ShopStatuses`. `front\factory\ShopStatuses` dziala jako fasada do repozytorium. - -## pp_shop_product_sets -Komplety produktow (modul `/admin/shop_product_sets`). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| name | Nazwa kompletu | -| status | Status: 1 = aktywny, 0 = nieaktywny | - -**Uzywane w:** `Domain\ProductSet\ProductSetRepository`, `admin\Controllers\ShopProductSetsController`, `shop\ProductSet`, `shop\Product` - -## pp_shop_product_sets_products -Powiazanie kompletow z produktami (tabela lacznikowa). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| set_id | FK do pp_shop_product_sets | -| product_id | FK do pp_shop_products | - -**Uzywane w:** `Domain\ProductSet\ProductSetRepository`, `shop\Product`, `front\factory\ShopProduct`, `admin\factory\ShopProduct` - -**Aktualizacja 2026-02-15 (ver. 0.272):** modul `/admin/shop_product_sets` korzysta z `Domain\ProductSet\ProductSetRepository` przez `admin\Controllers\ShopProductSetsController`. Usunieto legacy klasy `admin\controls\ShopProductSets` i `admin\factory\ShopProductSet`. `shop\ProductSet` dziala jako fasada do repozytorium. - -## pp_shop_producer -Producenci produktow (modul `/admin/shop_producer`). - -| Kolumna | Opis | -|---------|------| -| id | PK | -| name | Nazwa producenta | -| status | Status: 1 = aktywny, 0 = nieaktywny | -| img | Sciezka do logo producenta (NULL gdy brak) | - -**Uzywane w:** `Domain\Producer\ProducerRepository`, `admin\Controllers\ShopProducerController`, `shop\Producer`, `shop\Product`, `front\controls\ShopProducer` - -## pp_shop_producer_lang -Tlumaczenia producentow (per jezyk). FK kaskadowe ON DELETE CASCADE. - -| Kolumna | Opis | -|---------|------| -| id | PK | -| producer_id | FK do pp_shop_producer | -| lang_id | ID jezyka (np. pl, en) | -| description | Opis producenta (TEXT) | -| data | Dane producenta (TEXT, HTML) | -| meta_title | Meta title SEO (VARCHAR 255) | - -**Uzywane w:** `Domain\Producer\ProducerRepository`, `shop\Producer`, `shop\Product` - -**Aktualizacja 2026-02-15 (ver. 0.273):** modul `/admin/shop_producer` korzysta z `Domain\Producer\ProducerRepository` przez `admin\Controllers\ShopProducerController`. Usunieto legacy `admin\controls\ShopProducer` i `admin\factory\ShopProducer`. `shop\Producer` dziala jako fasada do repozytorium. diff --git a/temp/update_build/tmp_0.275/docs/PROJECT_STRUCTURE.md b/temp/update_build/tmp_0.275/docs/PROJECT_STRUCTURE.md deleted file mode 100644 index f3de582..0000000 --- a/temp/update_build/tmp_0.275/docs/PROJECT_STRUCTURE.md +++ /dev/null @@ -1,338 +0,0 @@ -# Struktura Projektu shopPRO - -Dokumentacja struktury projektu shopPRO do szybkiego odniesienia. - -## System Cache (Redis) - -### Klasy odpowiedzialne za cache - -#### RedisConnection -- **Plik:** `autoload/class.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` -- **Opis:** Handler do obsługi cache Redis -- **Metody:** - - `get($key)` - pobiera wartość z cache - - `set($key, $value, $ttl = 86400)` - zapisuje wartość do cache - - `exists($key)` - sprawdza czy klucz istnieje - - `delete($key)` - usuwa pojedynczy klucz - - `deletePattern($pattern)` - usuwa klucze według wzorca - -#### Klasa S (pomocnicza) -- **Plik:** `autoload/class.S.php` -- **Metody cache:** - - `clear_redis_cache()` - czyści cały cache Redis (flushAll) - - `clear_product_cache(int $product_id)` - czyści cache konkretnego produktu - -### Wzorce kluczy Redis - -#### Produkty -``` -shop\product:{product_id}:{lang_id}:{permutation_hash} -``` -- Przechowuje zserializowany obiekt produktu -- TTL: 24 godziny (86400 sekund) -- Klasa: `shop\Product::getFromCache()` - `autoload/shop/class.Product.php:121` - -#### Opcje ilościowe produktu -``` -\shop\Product::get_product_permutation_quantity_options:{product_id}:{permutation} -``` -- Przechowuje informacje o ilości i komunikatach magazynowych -- Klasa: `shop\Product::get_product_permutation_quantity_options()` - `autoload/shop/class.Product.php:549` - -#### Zestawy produktów -``` -\shop\Product::product_sets_when_add_to_basket:{product_id} -``` -- Przechowuje produkty często kupowane razem -- Klasa: `shop\Product::product_sets_when_add_to_basket()` - `autoload/shop/class.Product.php:316` - -## Integracje z systemami zewnętrznymi (CRON) - -### Plik: `cron.php` - -#### Apilo -- **Aktualizacja pojedynczego produktu:** synchronizacja cen i stanow - - Czestotliwosc: Co 10 minut -- **Synchronizacja cennika:** masowa aktualizacja cen z Apilo - - Czestotliwosc: Co 1 godzine -- **Synchronizacja zaleglych syncow platnosci/statusow:** kolejka retry dla chwilowej niedostepnosci Apilo (`temp/apilo-sync-queue.json`) - - Przetwarzanie: przy kazdym uruchomieniu `cron.php` (limit wsadowy) - -**Uwaga:** Integracje Sellasist i Baselinker zostaly usuniete w ver. 0.263. - -## Panel Administratora - -### Routing -- Główny katalog: `admin/` -- Template główny: `admin/templates/site/main-layout.php` -- Kontrolery (nowe): `autoload/admin/Controllers/` -- Kontrolery legacy (fallback): `autoload/admin/controls/` - -### Przycisk "Wyczyść cache" -- **Lokalizacja UI:** `admin/templates/site/main-layout.php:172` -- **JavaScript:** `admin/templates/site/main-layout.php:235-274` -- **Endpoint AJAX:** `/admin/settings/clear_cache_ajax/` -- **Kontroler:** `autoload/admin/Controllers/SettingsController.php:43-60` -- **Działanie:** - 1. Pokazuje spinner "Czyszczę cache..." - 2. Czyści katalogi: `temp/`, `thumbs/` - 3. Wykonuje `flushAll()` na Redis - 4. Pokazuje "Cache wyczyszczony!" przez 2 sekundy - 5. Przywraca stan początkowy - -## Struktura katalogów - -``` -shopPRO/ -├── admin/ # Panel administratora -│ ├── templates/ # Szablony widoków -│ └── layout/ # Zasoby CSS/JS/ikony -├── autoload/ # Klasy autoloadowane -│ ├── admin/ # Klasy panelu admin -│ │ ├── Controllers/ # Nowe kontrolery DI -│ │ ├── controls/ # Kontrolery legacy (fallback) -│ │ └── factory/ # Fabryki/helpery -│ ├── Domain/ # Repozytoria/logika domenowa -│ ├── front/ # Klasy frontendu -│ │ └── factory/ # Fabryki/helpery -│ └── shop/ # Klasy sklepu -├── docs/ # Dokumentacja techniczna -├── libraries/ # Biblioteki zewnętrzne -├── temp/ # Cache tymczasowy -├── thumbs/ # Miniatury zdjęć -└── cron.php # Zadania CRON -``` - -## Baza danych - -### Główne tabele produktów -- `pp_shop_products` - produkty główne -- `pp_shop_products_langs` - tłumaczenia produktów -- `pp_shop_products_images` - zdjęcia produktów -- `pp_shop_products_categories` - kategorie produktów -- `pp_shop_products_custom_fields` - pola własne produktów - -### Tabele integracji -- Kolumny w `pp_shop_products`: - - `apilo_product_id`, `apilo_product_name`, `apilo_get_data_date` -- Tabele ustawien: - - `pp_shop_apilo_settings` (key-value) - - `pp_shop_shoppro_settings` (key-value) - -### Tabele checkout -- `pp_shop_payment_methods` - metody platnosci sklepu (mapowanie `apilo_payment_type_id`) -- `pp_shop_transports` - rodzaje transportu sklepu (mapowanie `apilo_carrier_account_id`) -- `pp_shop_transport_payment_methods` - powiazanie metod transportu i platnosci - -Pelna dokumentacja tabel: `DATABASE_STRUCTURE.md` - -## Konfiguracja - -### Redis -- Konfiguracja: `config.php` (zmienna `$config['redis']`) -- Parametry: host, port, password - -### Autoload -- Funkcja: `__autoload_my_classes()` w `cron.php:6` -- Wzorzec: `autoload/{namespace}/class.{ClassName}.php` - -## Klasy pomocnicze - -### \S (autoload/class.S.php) -Główna klasa helper z metodami: -- `seo($val)` - generowanie URL SEO -- `normalize_decimal($val, $precision)` - normalizacja liczb -- `send_email()` - wysyłanie emaili -- `delete_dir($dir)` - usuwanie katalogów -- `htacces()` - generowanie .htaccess i sitemap.xml - -### Medoo -- Plik: `libraries/medoo/medoo.php` -- Zmienna: `$mdb` -- ORM do operacji na bazie danych - -## Najważniejsze wzorce - -### Namespace'y -- `\admin\Controllers\` - nowe kontrolery panelu admin (DI) -- `\admin\controls\` - kontrolery legacy (fallback) -- `\Domain\` - repozytoria/logika domenowa -- `\admin\factory\` - helpery/fabryki admin -- `\front\factory\` - helpery/fabryki frontend -- `\shop\` - klasy sklepu (Product, Order, itp.) - -### Cachowanie produktów -```php -// Pobranie produktu z cache -$product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash); - -// Czyszczenie cache produktu -\S::clear_product_cache($product_id); - -// Czyszczenie całego cache -\S::clear_redis_cache(); -``` - -## Refaktoryzacja do Domain-Driven Architecture - -### Nowa struktura (w trakcie migracji) -``` -autoload/ -├── Domain/ # Nowa warstwa biznesowa (namespace \Domain\) -│ ├── Product/ -│ │ └── ProductRepository.php -│ ├── Banner/ -│ │ └── BannerRepository.php -│ ├── Settings/ -│ │ └── SettingsRepository.php -│ ├── Cache/ -│ │ └── CacheRepository.php -│ ├── Article/ -│ │ └── ArticleRepository.php -│ ├── User/ -│ │ └── UserRepository.php -│ ├── Languages/ -│ │ └── LanguagesRepository.php -│ ├── Layouts/ -│ │ └── LayoutsRepository.php -│ ├── Newsletter/ -│ │ └── NewsletterRepository.php -│ ├── Scontainers/ -│ │ └── ScontainersRepository.php -│ ├── Dictionaries/ -│ │ └── DictionariesRepository.php -│ ├── Pages/ -│ │ └── PagesRepository.php -│ ├── Integrations/ -│ │ └── IntegrationsRepository.php -│ ├── Promotion/ -│ │ └── PromotionRepository.php -│ ├── Coupon/ -│ │ └── CouponRepository.php -│ ├── ShopStatus/ -│ │ └── ShopStatusRepository.php -│ ├── Transport/ -│ │ └── TransportRepository.php -│ ├── ProductSet/ -│ │ └── ProductSetRepository.php -│ ├── Producer/ -│ │ └── ProducerRepository.php -│ └── ... -├── admin/ -│ ├── Controllers/ # Nowe kontrolery (namespace \admin\Controllers\) -│ ├── class.Site.php # Router: nowy kontroler → fallback stary -│ ├── controls/ # Stare kontrolery (niezależny fallback) -│ ├── factory/ # Stare helpery (niezależny fallback) -│ └── view/ # Widoki (statyczne - bez zmian) -├── shop/ # Legacy - fasady do Domain -└── front/factory/ # Legacy - stopniowo migrowane -``` - -**Aktualizacja 2026-02-14 (ver. 0.268):** -- Dodano modul domenowy `Domain/PaymentMethod/PaymentMethodRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopPaymentMethodController.php`. -- Modul `/admin/shop_payment_method/*` dziala na nowych widokach (`payment-methods-list`, `payment-method-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopPaymentMethod.php`, `autoload/admin/factory/class.ShopPaymentMethod.php`, `autoload/admin/view/class.ShopPaymentMethod.php`, `admin/templates/shop-payment-method/view-list.php`. - -**Aktualizacja 2026-02-14 (ver. 0.269):** -- Dodano modul domenowy `Domain/Transport/TransportRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopTransportController.php`. -- Modul `/admin/shop_transport/*` dziala na nowych widokach (`transports-list`, `transport-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopTransport.php`, `autoload/admin/view/class.ShopTransport.php`, `admin/templates/shop-transport/view-list.php`. -- `admin\factory\ShopTransport` i `front\factory\ShopTransport` przepiete na repozytorium. - -**Aktualizacja 2026-02-14 (ver. 0.270):** -- `shop\Order` zapisuje nieudane syncy Apilo (status/platnosc) do kolejki `temp/apilo-sync-queue.json`. -- `cron.php` automatycznie ponawia zalegle syncy (`Order::process_apilo_sync_queue()`). -- `shop\Order::set_as_paid()` wysyla mapowany typ platnosci Apilo (z mapowania metody platnosci), bez stalej wartosci `type`. - -### Routing admin (admin\Site::route()) -1. Sprawdź mapę `$newControllers` → utwórz instancję z DI → wywołaj -2. Jeśli nowy kontroler nie istnieje (`class_exists()` = false) → fallback na `admin\controls\` -3. Stary kontroler jest NIEZALEŻNY od nowych klas (bezpieczny fallback) - -### Dependency Injection -Nowe klasy używają **Dependency Injection** zamiast `global` variables: -```php -// STARE -global $mdb; -$quantity = $mdb->get('pp_shop_products', 'quantity', ['id' => $id]); - -// NOWE -$repository = new \Domain\Product\ProductRepository($mdb); -$quantity = $repository->getQuantity($id); -``` - -## Testowanie (tylko dla deweloperów) - -**UWAGA:** Pliki testów NIE są częścią aktualizacji dla klientów! - -### Narzędzia -- **PHPUnit 9.6.34** - framework testowy -- **test.bat** - uruchamianie testów -- **composer.json** - autoloading PSR-4 - -Pelna dokumentacja testow: `TESTING.md` - -## Dodatkowa aktualizacja 2026-02-14 (ver. 0.271) -- Dodano modul domenowy `Domain/Attribute/AttributeRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopAttributeController.php`. -- Modul `/admin/shop_attribute/*` zostal przepiety na nowe widoki (`attributes-list`, `attribute-edit`, `values-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopAttribute.php`, `autoload/admin/factory/class.ShopAttribute.php`, `autoload/admin/view/class.ShopAttribute.php`, `admin/templates/shop-attribute/_partials/value.php`. -- Przepieto zaleznosci kombinacji produktu na `Domain\Attribute\AttributeRepository` i `shop\ProductAttribute`. -- Dla `ShopAttribute` routing celowo nie wykonuje fallbacku akcji do legacy kontrolera. - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.272) -- Dodano modul domenowy `Domain/ProductSet/ProductSetRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopProductSetsController.php`. -- Modul `/admin/shop_product_sets/*` dziala na nowych widokach (`product-sets-list`, `product-set-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopProductSets.php`, `autoload/admin/factory/class.ShopProductSet.php`, `admin/templates/shop-product-sets/view-list.php`, `admin/templates/shop-product-sets/set-edit.php`. -- `shop\ProductSet` przepiety na fasade do `Domain\ProductSet\ProductSetRepository`. - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.273) -- Dodano modul domenowy `Domain/Producer/ProducerRepository.php`. - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.274) -- Dodano modul domenowy `Domain/Client/ClientRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopClientsController.php`. -- Modul `/admin/shop_clients/*` dziala na nowych widokach opartych o `components/table-list`. -- Usunieto legacy: `autoload/admin/controls/class.ShopClients.php`, `autoload/admin/factory/class.ShopClients.php`. -- Routing i menu admin przepiete na kanoniczny URL `/admin/shop_clients/list/`. -- Dodano kontroler DI `admin/Controllers/ShopProducerController.php`. -- Modul `/admin/shop_producer/*` dziala na nowych widokach (`producers-list`, `producer-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopProducer.php`, `admin/templates/shop-producer/list.php`, `admin/templates/shop-producer/edit.php`. -- `shop\Producer` przepiety na fasade do `Domain\Producer\ProducerRepository`. -- `admin\controls\ShopProduct` uzywa `ProducerRepository::allProducers()`. -- Usunieto 6 pustych factory facades: `admin\factory\Languages`, `admin\factory\Newsletter`, `admin\factory\Scontainers`, `admin\factory\ShopProducer`, `admin\factory\ShopTransport`, `admin\factory\Layouts`. -- Przepieto 2 wywolania `admin\factory\ShopTransport` w `admin\factory\ShopProduct` na `Domain\Transport\TransportRepository`. -- Usuniety fallback do `admin\factory\Layouts` w `admin\controls\ShopProduct`. - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.274) -- Dodano kontroler DI `admin/Controllers/ShopProductController.php` (akcje `mass_edit`, `mass_edit_save`, `get_products_by_category`). -- Routing `admin\Site` rozszerzono o mapowanie `ShopProduct` do nowego kontrolera. -- `Domain/Product/ProductRepository.php` rozszerzono o metody dla mass-edit: `allProductsForMassEdit`, `getProductsByCategory`, `applyDiscountPercent`. -- Usunieto legacy akcje mass-edit z `autoload/admin/controls/class.ShopProduct.php`. -- Widok `/admin/shop_product/mass_edit/` przepiety na nowy partial `admin/templates/shop-product/mass-edit-custom-script.php`. -- Ujednolicono UI drzewek (strzalki/expand) w: - - `admin/templates/pages/pages-list.php` + `admin/templates/pages/subpages-list.php` - - `admin/templates/articles/subpages-list.php` + `admin/templates/articles/article-edit-custom-script.php` - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.275) -- Dodano modul domenowy `Domain/Category/CategoryRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopCategoryController.php`. -- Modul `/admin/shop_category/*` dziala przez DI i kanoniczny URL `/admin/shop_category/list/` (z zachowaniem aliasu `view_list`). -- Widoki `shop-category/*` maja wydzielone skrypty `*-custom-script.php` i ujednolicone strzalki drzewa (`button + caret + aria-expanded`). -- Endpointy AJAX dla drzewka kategorii i kolejnosci produktow przepiete na `/admin/shop_category/save_categories_order/`, `/admin/shop_category/save_products_order/`, `/admin/shop_category/cookie_categories/`. -- Usunieto legacy: `autoload/admin/controls/class.ShopCategory.php`, `autoload/admin/factory/class.ShopCategory.php`, `autoload/admin/view/class.ShopCategory.php`. -- Przepieto zaleznosci `ShopProduct` z `admin\factory\ShopCategory` na `Domain\Category\CategoryRepository`. -- Usunieto preload `autoload/admin/factory/class.ShopCategory.php` z `libraries/grid/config.php`. - ---- -*Dokument aktualizowany: 2026-02-15* diff --git a/temp/update_build/tmp_0.275/docs/REFACTORING_PLAN.md b/temp/update_build/tmp_0.275/docs/REFACTORING_PLAN.md deleted file mode 100644 index 257c0f6..0000000 --- a/temp/update_build/tmp_0.275/docs/REFACTORING_PLAN.md +++ /dev/null @@ -1,297 +0,0 @@ -# Plan Refaktoryzacji shopPRO - Domain-Driven Architecture - -## Cel -Stopniowe przeniesienie logiki biznesowej do architektury warstwowej: -- **Domain/** - logika biznesowa (core) -- **Admin/** - warstwa administratora -- **Frontend/** - warstwa użytkownika -- **Shared/** - współdzielone narzędzia - -## Docelowa struktura - -``` -autoload/ -├── Domain/ # Logika biznesowa (CORE) - namespace \Domain\ -│ ├── Product/ -│ │ ├── ProductRepository.php -│ │ ├── ProductService.php # (przyszłość) -│ │ └── ProductCacheService.php # (przyszłość) -│ ├── Banner/ -│ │ └── BannerRepository.php -│ ├── Settings/ -│ │ └── SettingsRepository.php -│ ├── Cache/ -│ │ └── CacheRepository.php -│ ├── Order/ -│ ├── Category/ -│ └── ... -│ -├── admin/ # Warstwa administratora (istniejący katalog!) -│ ├── Controllers/ # Nowe kontrolery - namespace \admin\Controllers\ -│ ├── controls/ # Stare kontrolery (legacy fallback) -│ ├── factory/ # Stare helpery (legacy) -│ └── view/ # Widoki (statyczne - OK bez zmian) -│ -├── Frontend/ # Warstwa użytkownika (przyszłość) -│ ├── Controllers/ -│ └── Services/ -│ -├── Shared/ # Współdzielone narzędzia -│ ├── Cache/ -│ │ ├── CacheHandler.php -│ │ └── RedisConnection.php -│ └── Helpers/ -│ └── S.php -│ -└── [LEGACY] # Stare klasy (stopniowo deprecated) - ├── shop/ - ├── admin/factory/ - └── front/factory/ -``` - -### WAŻNE: Konwencja namespace → katalog (Linux case-sensitive!) -- `\Domain\` → `autoload/Domain/` (duże D - nowy katalog) -- `\admin\Controllers\` → `autoload/admin/Controllers/` (małe a - istniejący katalog) -- NIE używać `\Admin\` (duże A) bo na serwerze Linux katalog to `admin/` (małe a) - -## Zasady migracji - -### 1. Stopniowość -- Przenosimy **jedną funkcję na raz** -- Zachowujemy kompatybilność wsteczną -- Stare klasy działają jako fasady do nowych - -### 2. Dependency Injection zamiast statycznych metod -```php -// ❌ STARE - statyczne -class Product { - public static function getQuantity($id) { - global $mdb; - return $mdb->get('pp_shop_products', 'quantity', ['id' => $id]); - } -} - -// ✅ NOWE - instancje z DI -class ProductRepository { - private $db; - - public function __construct($db) { - $this->db = $db; - } - - public function getQuantity($id) { - return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]); - } -} -``` - -### 3. Fasady dla kompatybilności -```php -// Stara klasa wywołuje nową -namespace shop; - -class Product { - public static function getQuantity($id) { - global $mdb; - $repo = new \Domain\Product\ProductRepository($mdb); - return $repo->getQuantity($id); - } -} -``` - -## Proces migracji funkcji - -### Krok 1: Wybór funkcji -- Wybierz prostą funkcję statyczną -- Sprawdź jej zależności -- Przeanalizuj gdzie jest używana - -### Krok 2: Stworzenie nowej struktury -- Utwórz folder `Domain/{Module}/` -- Stwórz odpowiednią klasę (Repository/Service/Entity) -- Przenieś logikę - -### Krok 3: Znalezienie użyć -```bash -grep -r "Product::getQuantity" . -``` - -### Krok 4: Aktualizacja wywołań -- Opcja A: Bezpośrednie wywołanie nowej klasy -- Opcja B: Fasada w starej klasie (zalecane na początek) - -### Krok 5: Testy -- Napisz test jednostkowy dla nowej funkcji -- Sprawdź czy stare wywołania działają - -## Status migracji - -### ✅ Zmigrowane moduły -| # | Modul | Wersja | Zakres | -|---|-------|--------|--------| -| 1 | Cache | 0.237 | CacheHandler, RedisConnection, clear_product_cache | -| 2 | Product | 0.238-0.252, 0.274 | getQuantity, getPrice, getName, archive/unarchive, allProductsForMassEdit, getProductsByCategory, applyDiscountPercent | -| 3 | Banner | 0.239 | find, delete, save, kontroler DI | -| 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 | -| 7 | Filemanager | 0.252 | kontroler DI, fix Invalid Key | -| 8 | Users | 0.253 | CRUD, logon, 2FA, kontroler DI | -| 9 | Languages | 0.254 | languages + translations, kontroler DI | -| 10 | Layouts | 0.256 | find, save, delete, menusWithPages, categoriesTree | -| 11 | Newsletter | 0.257-0.258 | subskrybenci, szablony, ustawienia | -| 12 | Scontainers | 0.259 | listForAdmin, find, save, delete | -| 13 | ArticlesArchive | 0.260 | restore, deletePermanently | -| 14 | Articles | 0.261 | pelna migracja (CRUD, AJAX, galeria, pliki) | -| 15 | Pages | 0.262 | menu/page CRUD, drzewo stron, AJAX | -| 16 | Integrations | 0.263 | Apilo/ShopPRO, cleanup Sellasist/Baselinker | -| 17 | ShopPromotion | 0.264-0.265 | listForAdmin, find, save, delete, categoriesTree | -| 18 | ShopCoupon | 0.266 | listForAdmin, find, save, delete, categoriesTree | -| 19 | ShopStatuses | 0.267 | listForAdmin, find, save, color picker | -| 20 | ShopPaymentMethod | 0.268 | listForAdmin, find, save, allActive, mapowanie Apilo, DI kontroler | -| 21 | ShopTransport | 0.269 | listForAdmin, find, save, allActive, allForAdmin, findActiveById, getTransportCost, lowestTransportPrice, getApiloCarrierAccountId, powiazanie z PaymentMethod, DI kontroler | -| 22 | ShopAttribute | 0.271 | list/edit/save/delete/values, nowy edytor wartosci, cleanup legacy, przepiecie zaleznosci kombinacji | -| 23 | ShopProductSets | 0.272 | listForAdmin, find, save, delete, allSets, allProductsMap, multi-select Selectize, DI kontroler | -| 24 | ShopProducer | 0.273 | listForAdmin, find, save, delete, allProducers, producerProducts, fasada shop\Producer, DI kontroler | -| 25 | ShopProduct (mass_edit) | 0.274 | DI kontroler + routing dla `mass_edit`, `mass_edit_save`, `get_products_by_category`, cleanup legacy akcji | -| 26 | ShopClients | 0.274 | DI kontroler + routing dla `list/details`, nowe listy na `components/table-list`, cleanup legacy controls/factory | -| 27 | ShopCategory | 0.275 | CategoryRepository + DI kontroler + routing, endpointy AJAX (`save_categories_order`, `save_products_order`, `cookie_categories`), cleanup legacy controls/factory/view | - -### Product - szczegolowy status -- ✅ getQuantity (ver. 0.238) -- ✅ getPrice (ver. 0.239) -- ✅ getName (ver. 0.239) -- ✅ archive / unarchive (ver. 0.241/0.252) -- ✅ allProductsForMassEdit (ver. 0.274) -- ✅ getProductsByCategory (ver. 0.274) -- ✅ applyDiscountPercent (ver. 0.274) -- [ ] is_product_on_promotion -- [ ] getFromCache -- [ ] getProductImg - -### 📋 Do zrobienia -- Order -- ShopProduct (factory) - -## Kolejność refaktoryzacji (priorytet) - -1-27: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses, ShopPaymentMethod, ShopTransport, ShopAttribute, ShopProductSets, ShopProducer, ShopProduct (mass_edit), ShopClients, ShopCategory - -Nastepne: -28. **Order** - -## Form Edit System - -Nowy uniwersalny system formularzy edycji: -- ✅ Klasy ViewModel: `FormFieldType`, `FormField`, `FormTab`, `FormAction`, `FormEditViewModel` -- ✅ Walidacja: `FormValidator` z obsługą reguł per pole i sekcje językowe -- ✅ Persist: `FormRequestHandler` - zapamiętywanie danych przy błędzie walidacji -- ✅ Renderer: `FormFieldRenderer` - renderowanie wszystkich typów pól -- ✅ Szablon: `admin/templates/components/form-edit.php` - uniwersalny layout -- Wspierane typy pól: text, number, email, password, date, datetime, switch, select, textarea, editor, image, file, hidden, lang_section, color -- Obsługa zakładek (vertical) i sekcji językowych (horizontal) -- **Do zrobienia**: Przerobić pozostałe kontrolery/formularze (Product, Category, Pages, itd.) - -Pelna dokumentacja: `docs/FORM_EDIT_SYSTEM.md` - -## Zasady kodu - -### 1. SOLID Principles -- **S**ingle Responsibility - jedna klasa = jedna odpowiedzialność -- **O**pen/Closed - otwarty na rozszerzenia, zamknięty na modyfikacje -- **L**iskov Substitution - podklasy mogą zastąpić nadklasy -- **I**nterface Segregation - wiele małych interfejsów -- **D**ependency Inversion - zależności od abstrakcji - -### 2. Nazewnictwo -- **Entity** - `Product.php` (reprezentuje obiekt domenowy) -- **Repository** - `ProductRepository.php` (dostęp do danych) -- **Service** - `ProductService.php` (logika biznesowa) -- **Controller** - `ProductController.php` (obsługa requestów) - -### 3. Type Hinting -```php -// ✅ DOBRE -public function getQuantity(int $id): ?int { - return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]); -} - -// ❌ ZŁE -public function getQuantity($id) { - return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]); -} -``` - -## Narzędzia pomocnicze - -### Autoloader (produkcja) -Autoloader w 9 entry pointach obsługuje dwie konwencje: -1. `autoload/{namespace}/class.{ClassName}.php` (legacy) -2. `autoload/{namespace}/{ClassName}.php` (PSR-4, fallback) - -Entry pointy: `index.php`, `ajax.php`, `api.php`, `cron.php`, `cron-turstmate.php`, `download.php`, `admin/index.php`, `admin/ajax.php`, `cron/cron-xml.php` - -### Static Analysis -```bash -composer require --dev phpstan/phpstan -vendor/bin/phpstan analyse autoload/Domain -``` - -## Testowanie - -### Framework: PHPUnit -```bash -composer test -``` - -### Struktura testów -``` -tests/ -├── Unit/ -│ ├── Domain/ -│ │ ├── Article/ArticleRepositoryTest.php -│ │ ├── Banner/BannerRepositoryTest.php -│ │ ├── Cache/CacheRepositoryTest.php -│ │ ├── Coupon/CouponRepositoryTest.php -│ │ ├── Dictionaries/DictionariesRepositoryTest.php -│ │ ├── Integrations/IntegrationsRepositoryTest.php -│ │ ├── PaymentMethod/PaymentMethodRepositoryTest.php -│ │ ├── Producer/ProducerRepositoryTest.php -│ │ ├── Product/ProductRepositoryTest.php -│ │ ├── ProductSet/ProductSetRepositoryTest.php -│ │ ├── Promotion/PromotionRepositoryTest.php -│ │ ├── Settings/SettingsRepositoryTest.php -│ │ ├── ShopStatus/ShopStatusRepositoryTest.php -│ │ └── User/UserRepositoryTest.php -│ └── admin/ -│ └── Controllers/ -│ ├── ArticlesControllerTest.php -│ ├── DictionariesControllerTest.php -│ ├── IntegrationsControllerTest.php -│ ├── ProductArchiveControllerTest.php -│ ├── SettingsControllerTest.php -│ ├── ShopCouponControllerTest.php -│ ├── ShopPaymentMethodControllerTest.php -│ ├── ShopProducerControllerTest.php -│ ├── ShopProductSetsControllerTest.php -│ ├── ShopPromotionControllerTest.php -│ ├── ShopStatusesControllerTest.php -│ └── UsersControllerTest.php -└── Integration/ -``` -**Lacznie: 338 testow, 1063 asercji** - -Aktualizacja 2026-02-15 (ver. 0.273): -- dodano testy `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` -- dodano testy `tests/Unit/admin/Controllers/ShopProducerControllerTest.php` - -Aktualizacja 2026-02-14 (ver. 0.271): -- dodano testy `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php` -- dodano testy `tests/Unit/admin/Controllers/ShopAttributeControllerTest.php` - -Pelna dokumentacja testow: `TESTING.md` - ---- -*Rozpoczęto: 2025-02-05* -*Ostatnia aktualizacja: 2026-02-15* -*Changelog zmian: `docs/CHANGELOG.md`* diff --git a/temp/update_build/tmp_0.275/docs/TESTING.md b/temp/update_build/tmp_0.275/docs/TESTING.md deleted file mode 100644 index bcaefc2..0000000 --- a/temp/update_build/tmp_0.275/docs/TESTING.md +++ /dev/null @@ -1,456 +0,0 @@ -# Testowanie shopPRO - -## Szybki start - -### Pelny zestaw testow -```bash -composer test -``` - -Alternatywnie (Windows): -```bash -./test.ps1 -./test.bat -./test-simple.bat -./test-debug.bat -``` - -Alternatywnie (Git Bash): -```bash -./test.sh -``` - -### Konkretny plik testowy -```bash -./test.ps1 tests/Unit/Domain/Product/ProductRepositoryTest.php -./test.ps1 tests/Unit/admin/Controllers/ArticlesControllerTest.php -``` - -### Konkretny test (`--filter`) -```bash -./test.ps1 --filter testGetQuantityReturnsCorrectValue -``` - -## Aktualny stan suite - -Ostatnio zweryfikowano: 2026-02-15 - -```text -OK (351 tests, 1091 assertions) -``` - -Aktualizacja po migracji ShopClients (2026-02-15, ver. 0.274) - testy punktowe: -```text -OK (10 tests, 34 assertions) -``` - -Aktualizacja po migracji ShopCategory (2026-02-15, ver. 0.275) - testy punktowe: -```text -OK (16 tests, 72 assertions) -``` - -Nowe testy dodane 2026-02-15: -- `tests/Unit/Domain/Client/ClientRepositoryTest.php` -- `tests/Unit/admin/Controllers/ShopClientsControllerTest.php` -- `tests/Unit/Domain/Category/CategoryRepositoryTest.php` -- `tests/Unit/admin/Controllers/ShopCategoryControllerTest.php` - -## Struktura testow - -```text -tests/ -|-- bootstrap.php -|-- Unit/ -| |-- Domain/ -| | |-- Article/ArticleRepositoryTest.php -| | |-- Attribute/AttributeRepositoryTest.php -| | |-- Banner/BannerRepositoryTest.php -| | |-- Cache/CacheRepositoryTest.php -| | |-- Coupon/CouponRepositoryTest.php -| | |-- Category/CategoryRepositoryTest.php -| | |-- Dictionaries/DictionariesRepositoryTest.php -| | |-- Integrations/IntegrationsRepositoryTest.php -| | |-- PaymentMethod/PaymentMethodRepositoryTest.php -| | |-- Producer/ProducerRepositoryTest.php -| | |-- Product/ProductRepositoryTest.php -| | |-- ProductSet/ProductSetRepositoryTest.php -| | |-- Promotion/PromotionRepositoryTest.php -| | |-- Settings/SettingsRepositoryTest.php -| | |-- ShopStatus/ShopStatusRepositoryTest.php -| | |-- Transport/TransportRepositoryTest.php -| | `-- User/UserRepositoryTest.php -| `-- admin/ -| `-- Controllers/ -| |-- ArticlesControllerTest.php -| |-- DictionariesControllerTest.php -| |-- IntegrationsControllerTest.php -| |-- ProductArchiveControllerTest.php -| |-- SettingsControllerTest.php -| |-- ShopAttributeControllerTest.php -| |-- ShopCategoryControllerTest.php -| |-- ShopCouponControllerTest.php -| |-- ShopPaymentMethodControllerTest.php -| |-- ShopProducerControllerTest.php -| |-- ShopProductControllerTest.php -| |-- ShopProductSetsControllerTest.php -| |-- ShopPromotionControllerTest.php -| |-- ShopStatusesControllerTest.php -| |-- ShopTransportControllerTest.php -| `-- UsersControllerTest.php -`-- Integration/ -``` - -## Tryby uruchamiania - -### 1. TestDox (czytelna lista) -```bash -./test.bat -``` -Uruchamia: -```bash -C:\xampp\php\php.exe phpunit.phar --testdox -``` - -### 2. Standard (kropki) -```bash -./test-simple.bat -``` -Uruchamia: -```bash -C:\xampp\php\php.exe phpunit.phar -``` - -### 3. Debug (pelne logowanie) -```bash -./test-debug.bat -``` -Uruchamia: -```bash -C:\xampp\php\php.exe phpunit.phar --debug -``` - -### 4. PowerShell (najbardziej niezawodne) -```bash -./test.ps1 -``` -- najpierw probuje `php` z PATH -- jesli brak, probuje m.in. `C:\xampp\php\php.exe` -- zawsze dodaje `--do-not-cache-result` - -## Interpretacja wynikow - -```text -. = test przeszedl -E = error (blad wykonania) -F = failure (niezgodna asercja) -``` - -Przyklad sukcesu: -```text -................................................................. 65 / 82 ( 79%) -................. 82 / 82 (100%) - -OK (82 tests, 181 assertions) -``` - -## Dodawanie nowych testow - -1. Dodaj plik w odpowiednim module, np. `tests/Unit/Domain//Test.php`. -2. Rozszerz `PHPUnit\Framework\TestCase`. -3. Nazwy metod zaczynaj od `test`. -4. Trzymaj sie wzorca AAA: Arrange, Act, Assert. - -## Mockowanie (przyklad) - -```php -$mockDb = $this->createMock(\medoo::class); -$mockDb->method('get')->willReturn(42); - -$repo = new ProductRepository($mockDb); -$value = $repo->getQuantity(123); - -$this->assertEquals(42, $value); -``` - -## Przydatne informacje - -- Konfiguracja PHPUnit: `phpunit.xml` -- Bootstrap testow: `tests/bootstrap.php` -- Dodatkowy opis: `tests/README.md` - -## Aktualizacja suite - -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (119 tests, 256 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/User/UserRepositoryTest.php` (25 testow: CRUD, logon, 2FA verify/send, checkLogin, updateById) -- `tests/Unit/admin/Controllers/UsersControllerTest.php` (12 testow: kontrakty + normalizeUser) - -Aktualizacja po migracji widokow Users (2026-02-12): -```text -OK (120 tests, 262 assertions) -``` - -## Aktualizacja suite (finalizacja Users) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (120 tests, 262 assertions) -``` - -Aktualizacja po migracji Languages (2026-02-12): -```text -OK (130 tests, 301 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php` -- `tests/Unit/admin/Controllers/LanguagesControllerTest.php` - -## Aktualizacja suite (release 0.254) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (130 tests, 301 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php` -- `tests/Unit/admin/Controllers/LanguagesControllerTest.php` - -## Aktualizacja suite (release 0.255) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (130 tests, 303 assertions) -``` - -## Aktualizacja suite (release 0.256) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (141 tests, 336 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Layouts/LayoutsRepositoryTest.php` -- `tests/Unit/admin/Controllers/LayoutsControllerTest.php` - -Zaktualizowane testy 2026-02-12: -- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php` (defaultLanguageId) -- `tests/Unit/admin/Controllers/ArticlesControllerTest.php` (konstruktor + LayoutsRepository) - -## Aktualizacja suite (release 0.257) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (150 tests, 372 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Newsletter/NewsletterRepositoryTest.php` -- `tests/Unit/admin/Controllers/NewsletterControllerTest.php` - -## Aktualizacja suite (release 0.258) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (150 tests, 372 assertions) -``` - -## Aktualizacja suite (release 0.259) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (158 tests, 397 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Scontainers/ScontainersRepositoryTest.php` -- `tests/Unit/admin/Controllers/ScontainersControllerTest.php` - -## Aktualizacja suite (release 0.260) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (165 tests, 424 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Article/ArticleRepositoryTest.php` (rozszerzenie o testy `restore`, `deletePermanently`, `listArchivedForAdmin`) -- `tests/Unit/admin/Controllers/ArticlesArchiveControllerTest.php` - -## Aktualizacja suite (release 0.261) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (176 tests, 439 assertions) -``` - -Nowe testy/rozszerzenia 2026-02-13: -- `tests/Unit/Domain/Article/ArticleRepositoryTest.php` (nowe przypadki dla `pagesSummaryForArticles`, `updateImageAlt`, `markFileToDelete`) -- `tests/Unit/admin/Controllers/ArticlesControllerTest.php` (nowe kontrakty dla akcji `imageAltChange`, `fileNameChange`, `imageDelete`, `fileDelete`) - -## Aktualizacja suite (release 0.261) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (178 tests, 443 assertions) -``` - -Nowe testy/rozszerzenia 2026-02-13: -- `tests/Unit/Domain/Article/ArticleRepositoryTest.php` (nowe przypadki dla `saveFilesOrder`) - -## Aktualizacja suite (Pages migration) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (186 tests, 478 assertions) -``` - -Nowe testy dodane 2026-02-13: -- `tests/Unit/Domain/Pages/PagesRepositoryTest.php` -- `tests/Unit/admin/Controllers/PagesControllerTest.php` - -Zaktualizowane testy 2026-02-13: -- `tests/Unit/admin/Controllers/ArticlesControllerTest.php` (konstruktor z `Domain\\Pages\\PagesRepository`) - -## Aktualizacja suite (Integrations refactor, ver. 0.263) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (212 tests, 577 assertions) -``` - -Nowe testy dodane 2026-02-13: -- `tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php` (16 testow: getSettings, getSetting, saveSetting, linkProduct, unlinkProduct, getProductSku, apiloGetAccessToken, invalid provider, settings table mapping) -- `tests/Unit/admin/Controllers/IntegrationsControllerTest.php` (10 testow: kontrakty metod, return types, brak metod sellasist/baselinker) - -Zaktualizowane pliki: -- `tests/bootstrap.php` (dodany stub `S::remove_special_chars()`) - -## Aktualizacja suite (ShopPromotion refactor, ver. 0.264) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (222 tests, 609 assertions) -``` - -Nowe testy dodane 2026-02-13: -- `tests/Unit/Domain/Promotion/PromotionRepositoryTest.php` (6 testow: find default, save insert, delete, whitelist sortowania, drzewo kategorii) -- `tests/Unit/admin/Controllers/ShopPromotionControllerTest.php` (4 testy: kontrakty metod i DI konstruktora) - -## Aktualizacja suite (ShopPromotion fix + date_from, ver. 0.265) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (222 tests, 614 assertions) -``` - -Zmiany testowe 2026-02-13: -- rozszerzenie `tests/Unit/Domain/Promotion/PromotionRepositoryTest.php` o asercje `date_from` - -## Aktualizacja suite (ShopCoupon refactor, ver. 0.266) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (235 tests, 682 assertions) -``` - -Nowe testy dodane 2026-02-13: -- `tests/Unit/Domain/Coupon/CouponRepositoryTest.php` (8 testow: find default/normalize, save insert/update, delete, whitelist sortowania, drzewo kategorii) -- `tests/Unit/admin/Controllers/ShopCouponControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, DI konstruktora) - -Ponowna weryfikacja po poprawkach UI (drzewko + checkboxy): 2026-02-13 -- `OK (235 tests, 682 assertions)` - -## Aktualizacja suite (ShopStatuses refactor, ver. 0.267) -Ostatnio zweryfikowano: 2026-02-14 - -```text -OK (254 tests, 736 assertions) -``` - -Nowe testy dodane 2026-02-14: -- `tests/Unit/Domain/ShopStatus/ShopStatusRepositoryTest.php` (9 testow: find z ID=0, find null apilo, save update, save z ID=0, empty apilo sets null, reject negative ID, getApiloStatusId, getByIntegrationStatusId, allStatuses, whitelist sortowania) -- `tests/Unit/admin/Controllers/ShopStatusesControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora) - -## Aktualizacja suite (ShopPaymentMethod refactor, ver. 0.268) -Ostatnio zweryfikowano: 2026-02-14 - -```text -OK (280 tests, 828 assertions) -``` - -Nowe testy dodane 2026-02-14: -- `tests/Unit/Domain/PaymentMethod/PaymentMethodRepositoryTest.php` (14 testow: find invalid/null/normalize, save update/null/non-numeric apilo, listForAdmin whitelist, allActive, allForAdmin, findActiveById, isActive, getApiloPaymentTypeId, forTransport) -- `tests/Unit/admin/Controllers/ShopPaymentMethodControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora) - -## Aktualizacja suite (ShopTransport refactor, ver. 0.269) -Ostatnio zweryfikowano: 2026-02-14 - -```text -OK (300 tests, 895 assertions) -``` - -Nowe testy dodane 2026-02-14: -- `tests/Unit/Domain/Transport/TransportRepositoryTest.php` (14 testow: find invalid/null/normalize/nullables, save insert/update/failure/default reset/switch normalization, listForAdmin whitelist, allActive, getApiloCarrierAccountId, getTransportCost, allForAdmin) -- `tests/Unit/admin/Controllers/ShopTransportControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora z 2 repo) - -## Aktualizacja suite (Apilo sync hardening, ver. 0.270) -Ostatnio zweryfikowano: 2026-02-14 - -```text -OK (300 tests, 895 assertions) -``` - -Zmiany testowe 2026-02-14: -- brak nowych testow; pelna regresja po zmianach sync Apilo (TPAY -> Apilo) przeszla bez bledow - -## Aktualizacja suite (ShopAttribute refactor, ver. 0.271) -Ostatnio zweryfikowano: 2026-02-14 - -```text -OK (312 tests, 948 assertions) -``` - -Nowe testy dodane 2026-02-14: -- `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php` (5 testow: domyslne dane cechy, whitelist sortowania/paginacji, zapis wartosci i domyslnej, usuwanie pustych tlumaczen, jezyk domyslny) -- `tests/Unit/admin/Controllers/ShopAttributeControllerTest.php` (7 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora, walidacja `validateValuesRows`) - -## Aktualizacja suite (ShopProductSets refactor, ver. 0.272) -Ostatnio zweryfikowano: 2026-02-15 - -```text -OK (324 tests, 1000 assertions) -``` - -Nowe testy dodane 2026-02-15: -- `tests/Unit/Domain/ProductSet/ProductSetRepositoryTest.php` (7 testow: find default/normalize, save insert/update, delete invalid, whitelist sortowania/paginacji, allSets) -- `tests/Unit/admin/Controllers/ShopProductSetsControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, return types, DI konstruktora) - -## Aktualizacja suite (ShopProducer refactor, ver. 0.273) -Ostatnio zweryfikowano: 2026-02-15 - -```text -OK (338 tests, 1063 assertions) -``` - -Nowe testy dodane 2026-02-15: -- `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` (9 testow: find default/normalize, save insert/update, delete invalid/success, whitelist sortowania/paginacji, allProducers, producerProducts) -- `tests/Unit/admin/Controllers/ShopProducerControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, return types, DI konstruktora) - -## Aktualizacja suite (ShopProduct mass_edit, ver. 0.274) -Ostatnio zweryfikowano: 2026-02-15 - -```text -OK (351 tests, 1091 assertions) -``` - -Nowe testy dodane 2026-02-15: -- `tests/Unit/Domain/Product/ProductRepositoryTest.php` (rozszerzenie: `allProductsForMassEdit`, `getProductsByCategory`, `applyDiscountPercent`) -- `tests/Unit/admin/Controllers/ShopProductControllerTest.php` (7 testow: kontrakty metod, return types, DI konstruktora) diff --git a/temp/update_build/tmp_0.275/libraries/grid/config.php b/temp/update_build/tmp_0.275/libraries/grid/config.php deleted file mode 100644 index 238b9c9..0000000 --- a/temp/update_build/tmp_0.275/libraries/grid/config.php +++ /dev/null @@ -1,48 +0,0 @@ - 'mysql', - 'database_name' => $database['name'], - 'server' => $database['host'], - 'username' => $database['user'], - 'password' => $database['password'], - 'charset' => 'utf8' - ); - -$mdb = new medoo( [ - 'database_type' => 'mysql', - 'database_name' => $database['name'], - 'server' => $database['host'], - 'username' => $database['user'], - 'password' => $database['password'], - 'charset' => 'utf8' - ] ); diff --git a/temp/update_build/tmp_0.275/tests/Unit/admin/Controllers/ShopCategoryControllerTest.php b/temp/update_build/tmp_0.275/tests/Unit/admin/Controllers/ShopCategoryControllerTest.php deleted file mode 100644 index 679731c..0000000 --- a/temp/update_build/tmp_0.275/tests/Unit/admin/Controllers/ShopCategoryControllerTest.php +++ /dev/null @@ -1,80 +0,0 @@ -repository = $this->createMock(CategoryRepository::class); - $this->languagesRepository = $this->createMock(LanguagesRepository::class); - $this->controller = new ShopCategoryController($this->repository, $this->languagesRepository); - } - - public function testConstructorAcceptsDependencies(): void - { - $controller = new ShopCategoryController($this->repository, $this->languagesRepository); - $this->assertInstanceOf(ShopCategoryController::class, $controller); - } - - public function testHasExpectedActionMethods(): void - { - $this->assertTrue(method_exists($this->controller, 'view_list')); - $this->assertTrue(method_exists($this->controller, 'list')); - $this->assertTrue(method_exists($this->controller, 'category_edit')); - $this->assertTrue(method_exists($this->controller, 'edit')); - $this->assertTrue(method_exists($this->controller, 'save')); - $this->assertTrue(method_exists($this->controller, 'category_delete')); - $this->assertTrue(method_exists($this->controller, 'delete')); - $this->assertTrue(method_exists($this->controller, 'category_products')); - $this->assertTrue(method_exists($this->controller, 'products')); - $this->assertTrue(method_exists($this->controller, 'category_url_browser')); - $this->assertTrue(method_exists($this->controller, 'save_categories_order')); - $this->assertTrue(method_exists($this->controller, 'save_products_order')); - $this->assertTrue(method_exists($this->controller, 'cookie_categories')); - } - - public function testViewActionsReturnString(): void - { - $reflection = new \ReflectionClass($this->controller); - - $this->assertEquals('string', (string)$reflection->getMethod('view_list')->getReturnType()); - $this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType()); - $this->assertEquals('string', (string)$reflection->getMethod('category_edit')->getReturnType()); - $this->assertEquals('string', (string)$reflection->getMethod('edit')->getReturnType()); - $this->assertEquals('string', (string)$reflection->getMethod('category_products')->getReturnType()); - $this->assertEquals('string', (string)$reflection->getMethod('products')->getReturnType()); - } - - public function testMutationActionsReturnVoid(): void - { - $reflection = new \ReflectionClass($this->controller); - - $this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType()); - $this->assertEquals('void', (string)$reflection->getMethod('category_delete')->getReturnType()); - $this->assertEquals('void', (string)$reflection->getMethod('delete')->getReturnType()); - $this->assertEquals('void', (string)$reflection->getMethod('category_url_browser')->getReturnType()); - $this->assertEquals('void', (string)$reflection->getMethod('save_categories_order')->getReturnType()); - $this->assertEquals('void', (string)$reflection->getMethod('save_products_order')->getReturnType()); - $this->assertEquals('void', (string)$reflection->getMethod('cookie_categories')->getReturnType()); - } - - public function testConstructorRequiresCategoryAndLanguagesRepositories(): void - { - $reflection = new \ReflectionClass(ShopCategoryController::class); - $constructor = $reflection->getConstructor(); - $params = $constructor->getParameters(); - - $this->assertCount(2, $params); - $this->assertEquals('Domain\\Category\\CategoryRepository', $params[0]->getType()->getName()); - $this->assertEquals('Domain\\Languages\\LanguagesRepository', $params[1]->getType()->getName()); - } -} diff --git a/temp/update_build/update_0.273.zip b/temp/update_build/update_0.273.zip deleted file mode 100644 index b277f34..0000000 Binary files a/temp/update_build/update_0.273.zip and /dev/null differ diff --git a/temp/update_build/update_0.274.zip b/temp/update_build/update_0.274.zip deleted file mode 100644 index 1ecb78b..0000000 Binary files a/temp/update_build/update_0.274.zip and /dev/null differ diff --git a/temp/update_build/update_0.275.zip b/temp/update_build/update_0.275.zip deleted file mode 100644 index ab9d403..0000000 Binary files a/temp/update_build/update_0.275.zip and /dev/null differ diff --git a/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list-custom-script.php b/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list-custom-script.php deleted file mode 100644 index 56ab843..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list-custom-script.php +++ /dev/null @@ -1,100 +0,0 @@ - - - diff --git a/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list.php b/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list.php deleted file mode 100644 index 3e70c9a..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list.php +++ /dev/null @@ -1,5 +0,0 @@ - $this->viewModel]); ?> - -viewModel->customScriptView)): ?> - viewModel->customScriptView, ['list' => $this->viewModel]); ?> - diff --git a/temp/update_build/ver_0.200_20260211_000158/admin/templates/filemanager/filemanager.php b/temp/update_build/ver_0.200_20260211_000158/admin/templates/filemanager/filemanager.php deleted file mode 100644 index dc2d008..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/admin/templates/filemanager/filemanager.php +++ /dev/null @@ -1,4 +0,0 @@ -filemanager_url ?? '/libraries/filemanager-9.14.2/dialog.php')); -?> - diff --git a/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list-custom-script.php b/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list-custom-script.php deleted file mode 100644 index aacad98..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list-custom-script.php +++ /dev/null @@ -1,100 +0,0 @@ - - - diff --git a/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list.php b/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list.php deleted file mode 100644 index 3e70c9a..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list.php +++ /dev/null @@ -1,5 +0,0 @@ - $this->viewModel]); ?> - -viewModel->customScriptView)): ?> - viewModel->customScriptView, ['list' => $this->viewModel]); ?> - diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/Domain/Product/ProductRepository.php b/temp/update_build/ver_0.200_20260211_000158/autoload/Domain/Product/ProductRepository.php deleted file mode 100644 index e397697..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/autoload/Domain/Product/ProductRepository.php +++ /dev/null @@ -1,247 +0,0 @@ -db = $db; - } - - /** - * Pobiera stan magazynowy produktu - * - * @param int $productId ID produktu - * @return int|null Ilość produktu lub null jeśli nie znaleziono - */ - public function getQuantity(int $productId): ?int - { - $quantity = $this->db->get('pp_shop_products', 'quantity', ['id' => $productId]); - - // Medoo zwraca false jeśli nie znaleziono - return $quantity !== false ? (int)$quantity : null; - } - - /** - * Pobiera produkt po ID - * - * @param int $productId ID produktu - * @return array|null Dane produktu lub null - */ - public function find(int $productId): ?array - { - $product = $this->db->get('pp_shop_products', '*', ['id' => $productId]); - return $product ?: null; - } - - /** - * Zwraca liste produktow z archiwum do panelu admin. - * - * @return array{items: array>, total: int} - */ - public function listArchivedForAdmin( - array $filters, - string $sortColumn = 'id', - string $sortDir = 'DESC', - int $page = 1, - int $perPage = 10 - ): array { - $allowedSortColumns = [ - 'id' => 'psp.id', - 'name' => 'name', - 'price_brutto' => 'psp.price_brutto', - 'price_brutto_promo' => 'psp.price_brutto_promo', - 'quantity' => 'psp.quantity', - 'combinations' => 'combinations', - ]; - - $sortSql = $allowedSortColumns[$sortColumn] ?? 'psp.id'; - $sortDir = strtoupper(trim($sortDir)) === 'ASC' ? 'ASC' : 'DESC'; - $page = max(1, $page); - $perPage = min(self::MAX_PER_PAGE, max(1, $perPage)); - $offset = ($page - 1) * $perPage; - - $where = ['psp.archive = 1', 'psp.parent_id IS NULL']; - $params = []; - - $phrase = trim((string)($filters['phrase'] ?? '')); - if (strlen($phrase) > 255) { - $phrase = substr($phrase, 0, 255); - } - - if ($phrase !== '') { - $where[] = '( - psp.ean LIKE :phrase - OR psp.sku LIKE :phrase - OR EXISTS ( - SELECT 1 - FROM pp_shop_products_langs AS pspl2 - WHERE pspl2.product_id = psp.id - AND pspl2.name LIKE :phrase - ) - )'; - $params[':phrase'] = '%' . $phrase . '%'; - } - - $whereSql = implode(' AND ', $where); - - $sqlCount = " - SELECT COUNT(0) - FROM pp_shop_products AS psp - WHERE {$whereSql} - "; - - $stmtCount = $this->db->query($sqlCount, $params); - $countRows = $stmtCount ? $stmtCount->fetchAll() : []; - $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; - - $sql = " - SELECT - psp.id, - psp.price_brutto, - psp.price_brutto_promo, - psp.quantity, - psp.sku, - psp.ean, - ( - SELECT pspl.name - FROM pp_shop_products_langs AS pspl - INNER JOIN pp_langs AS pl ON pl.id = pspl.lang_id - WHERE pspl.product_id = psp.id - AND pspl.name <> '' - ORDER BY pl.o ASC - LIMIT 1 - ) AS name, - ( - SELECT pspi.src - FROM pp_shop_products_images AS pspi - WHERE pspi.product_id = psp.id - ORDER BY pspi.o ASC, pspi.id ASC - LIMIT 1 - ) AS image_src, - ( - SELECT pspi.alt - FROM pp_shop_products_images AS pspi - WHERE pspi.product_id = psp.id - ORDER BY pspi.o ASC, pspi.id ASC - LIMIT 1 - ) AS image_alt, - ( - SELECT COUNT(0) - FROM pp_shop_products AS pspc - WHERE pspc.parent_id = psp.id - ) AS combinations - FROM pp_shop_products AS psp - WHERE {$whereSql} - ORDER BY {$sortSql} {$sortDir}, psp.id {$sortDir} - LIMIT {$perPage} OFFSET {$offset} - "; - - $stmt = $this->db->query($sql, $params); - $items = $stmt ? $stmt->fetchAll() : []; - - return [ - 'items' => is_array($items) ? $items : [], - 'total' => $total, - ]; - } - - /** - * Pobiera cenę produktu (promocyjną jeśli jest niższa, w przeciwnym razie regularną) - * - * @param int $productId ID produktu - * @return float|null Cena brutto lub null jeśli nie znaleziono - */ - public function getPrice(int $productId): ?float - { - $prices = $this->db->get('pp_shop_products', ['price_brutto', 'price_brutto_promo'], ['id' => $productId]); - - if (!$prices) { - return null; - } - - if ($prices['price_brutto_promo'] != '' && $prices['price_brutto_promo'] < $prices['price_brutto']) { - return (float)$prices['price_brutto_promo']; - } - - return (float)$prices['price_brutto']; - } - - /** - * Pobiera nazwę produktu w danym języku - * - * @param int $productId ID produktu - * @param string $langId ID języka - * @return string|null Nazwa produktu lub null jeśli nie znaleziono - */ - public function getName(int $productId, string $langId): ?string - { - $name = $this->db->get('pp_shop_products_langs', 'name', ['AND' => ['product_id' => $productId, 'lang_id' => $langId]]); - - return $name ?: null; - } - - /** - * Aktualizuje ilość produktu - * - * @param int $productId ID produktu - * @param int $quantity Nowa ilość - * @return bool Czy aktualizacja się powiodła - */ - public function updateQuantity(int $productId, int $quantity): bool - { - $result = $this->db->update( - 'pp_shop_products', - ['quantity' => $quantity], - ['id' => $productId] - ); - - return $result !== false; - } - - /** - * Przywraca produkt z archiwum (wraz z kombinacjami) - * - * @param int $productId ID produktu - * @return bool Czy operacja się powiodła - */ - public function unarchive(int $productId): bool - { - $this->db->update( 'pp_shop_products', [ 'status' => 1, 'archive' => 0 ], [ 'id' => $productId ] ); - $this->db->update( 'pp_shop_products', [ 'status' => 1, 'archive' => 0 ], [ 'parent_id' => $productId ] ); - - return true; - } - - /** - * Przenosi produkt do archiwum (wraz z kombinacjami) - * - * @param int $productId ID produktu - * @return bool Czy operacja się powiodła - */ - public function archive(int $productId): bool - { - $this->db->update( 'pp_shop_products', [ 'status' => 0, 'archive' => 1 ], [ 'id' => $productId ] ); - $this->db->update( 'pp_shop_products', [ 'status' => 0, 'archive' => 1 ], [ 'parent_id' => $productId ] ); - - return true; - } -} diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/BannerController.php b/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/BannerController.php deleted file mode 100644 index 666a64c..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/BannerController.php +++ /dev/null @@ -1,337 +0,0 @@ -repository = $repository; - $this->formHandler = new FormRequestHandler(); - } - - /** - * Lista banerow - */ - public function list(): string - { - $sortableColumns = ['name', 'status', 'home_page', 'date_start', 'date_end']; - - $filterDefinitions = [ - [ - 'key' => 'name', - 'label' => 'Nazwa', - 'type' => 'text', - ], - [ - 'key' => 'status', - 'label' => 'Aktywny', - 'type' => 'select', - 'options' => [ - '' => '- aktywny -', - '1' => 'tak', - '0' => 'nie', - ], - ], - ]; - - $listRequest = \admin\Support\TableListRequestFactory::fromRequest( - $filterDefinitions, - $sortableColumns, - 'name' - ); - - // Historycznie lista banerow domyslnie byla sortowana rosnaco po nazwie. - $sortDir = $listRequest['sortDir']; - if (trim((string)\S::get('sort')) === '') { - $sortDir = 'ASC'; - } - - $result = $this->repository->listForAdmin( - $listRequest['filters'], - $listRequest['sortColumn'], - $sortDir, - $listRequest['page'], - $listRequest['perPage'] - ); - - $rows = []; - $lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1; - foreach ($result['items'] as $item) { - $id = (int)$item['id']; - $name = (string)($item['name'] ?? ''); - $homePage = (int)($item['home_page'] ?? 0); - $isActive = (int)($item['status'] ?? 0) === 1; - $thumbnailSrc = trim((string)($item['thumbnail_src'] ?? '')); - if ($thumbnailSrc !== '' && !preg_match('#^(https?:)?//#i', $thumbnailSrc) && strpos($thumbnailSrc, '/') !== 0) { - $thumbnailSrc = '/' . ltrim($thumbnailSrc, '/'); - } - - $thumbnail = '-'; - if ($thumbnailSrc !== '') { - $thumbnail = ''; - } - - $rows[] = [ - 'lp' => $lp++ . '.', - 'thumbnail' => $thumbnail, - 'name' => '' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '', - 'status' => $isActive ? 'tak' : 'nie', - 'home_page' => $homePage === 1 ? 'tak' : 'nie', - 'slider' => $homePage === 1 ? 'nie' : 'tak', - 'date_start' => !empty($item['date_start']) ? date('Y-m-d', strtotime((string)$item['date_start'])) : '-', - 'date_end' => !empty($item['date_end']) ? date('Y-m-d', strtotime((string)$item['date_end'])) : '-', - '_actions' => [ - [ - 'label' => 'Edytuj', - 'url' => '/admin/banners/banner_edit/id=' . $id, - 'class' => 'btn btn-xs btn-primary', - ], - [ - 'label' => 'Usun', - 'url' => '/admin/banners/banner_delete/id=' . $id, - 'class' => 'btn btn-xs btn-danger', - 'confirm' => 'Na pewno chcesz usunac wybrany element?', - ], - ], - ]; - } - - $total = (int)$result['total']; - $totalPages = max(1, (int)ceil($total / $listRequest['perPage'])); - - $viewModel = new \admin\ViewModels\Common\PaginatedTableViewModel( - [ - ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false], - ['key' => 'thumbnail', 'label' => 'Miniatura', 'class' => 'text-center', 'sortable' => false, 'raw' => true], - ['key' => 'name', 'sort_key' => 'name', 'label' => 'Nazwa', 'sortable' => true, 'raw' => true], - ['key' => 'status', 'sort_key' => 'status', 'label' => 'Aktywny', 'class' => 'text-center', 'sortable' => true, 'raw' => true], - ['key' => 'home_page', 'sort_key' => 'home_page', 'label' => 'Strona glowna', 'class' => 'text-center', 'sortable' => true, 'raw' => true], - ['key' => 'slider', 'label' => 'Slajder', 'class' => 'text-center', 'sortable' => false, 'raw' => true], - ['key' => 'date_start', 'sort_key' => 'date_start', 'label' => 'Data rozpoczecia', 'class' => 'text-center', 'sortable' => true], - ['key' => 'date_end', 'sort_key' => 'date_end', 'label' => 'Data zakonczenia', 'class' => 'text-center', 'sortable' => true], - ], - $rows, - $listRequest['viewFilters'], - [ - 'column' => $listRequest['sortColumn'], - 'dir' => $sortDir, - ], - [ - 'page' => $listRequest['page'], - 'per_page' => $listRequest['perPage'], - 'total' => $total, - 'total_pages' => $totalPages, - ], - array_merge($listRequest['queryFilters'], [ - 'sort' => $listRequest['sortColumn'], - 'dir' => $sortDir, - 'per_page' => $listRequest['perPage'], - ]), - $listRequest['perPageOptions'], - $sortableColumns, - '/admin/banners/view_list/', - 'Brak danych w tabeli.', - '/admin/banners/banner_edit/', - 'Dodaj baner', - 'banners/banners-list-custom-script' - ); - - return \Tpl::view('banners/banners-list', [ - 'viewModel' => $viewModel, - ]); - } - - /** - * Edycja banera - */ - public function edit(): string - { - $bannerId = (int)\S::get('id'); - $banner = $this->repository->find($bannerId); - $languages = \admin\factory\Languages::languages_list(); - - // Sprawdź czy są błędy walidacji z poprzedniego requestu - $validationErrors = $_SESSION['form_errors'][$this->getFormId()] ?? null; - if ($validationErrors) { - unset($_SESSION['form_errors'][$this->getFormId()]); - } - - $viewModel = $this->buildFormViewModel($banner, $languages, $validationErrors); - - return \Tpl::view('components/form-edit', ['form' => $viewModel]); - } - - /** - * Zapisanie banera (AJAX) - */ - public function save(): void - { - $response = ['success' => false, 'errors' => []]; - - $bannerId = (int)\S::get('id'); - $banner = $this->repository->find($bannerId); - $languages = \admin\factory\Languages::languages_list(); - - $viewModel = $this->buildFormViewModel($banner, $languages); - - // Przetwórz dane z POST - $result = $this->formHandler->handleSubmit($viewModel, $_POST); - - if (!$result['success']) { - // Zapisz błędy w sesji i zwróć jako JSON - $_SESSION['form_errors'][$this->getFormId()] = $result['errors']; - $response['errors'] = $result['errors']; - echo json_encode($response); - exit; - } - - // Zapisz dane - $data = $result['data']; - $data['id'] = $bannerId ?: null; - - $savedId = $this->repository->save($data); - - if ($savedId) { - \S::delete_dir('../temp/'); - $response = [ - 'success' => true, - 'id' => $savedId, - 'message' => 'Baner został zapisany.' - ]; - } else { - $response['errors'] = ['general' => 'Błąd podczas zapisywania do bazy.']; - } - - echo json_encode($response); - exit; - } - - /** - * Usuniecie banera - */ - public function delete(): void - { - $bannerId = (int)\S::get('id'); - if ($this->repository->delete($bannerId)) { - \S::delete_dir('../temp/'); - \S::alert('Baner zostal usuniety.'); - } - - header('Location: /admin/banners/view_list/'); - exit; - } - - /** - * Buduje model widoku formularza - */ - private function buildFormViewModel(array $banner, array $languages, ?array $errors = null): FormEditViewModel - { - $bannerId = $banner['id'] ?? 0; - $isNew = empty($bannerId); - - // Domyślne wartości dla nowego banera - if ($isNew) { - $banner['status'] = 1; - $banner['home_page'] = 0; - } - - $tabs = [ - new FormTab('settings', 'Ustawienia', 'fa-wrench'), - new FormTab('content', 'Zawartość', 'fa-file'), - ]; - - $fields = [ - // Zakładka Ustawienia - FormField::text('name', [ - 'label' => 'Nazwa', - 'tab' => 'settings', - 'required' => true, - ]), - FormField::switch('status', [ - 'label' => 'Aktywny', - 'tab' => 'settings', - 'value' => ($banner['status'] ?? 1) == 1, - ]), - FormField::date('date_start', [ - 'label' => 'Data rozpoczęcia', - 'tab' => 'settings', - ]), - FormField::date('date_end', [ - 'label' => 'Data zakończenia', - 'tab' => 'settings', - ]), - FormField::switch('home_page', [ - 'label' => 'Slajder / Strona główna', - 'tab' => 'settings', - 'value' => ($banner['home_page'] ?? 0) == 1, - ]), - - // Sekcja językowa w zakładce Zawartość - FormField::langSection('translations', 'content', [ - FormField::image('src', [ - 'label' => 'Obraz', - 'filemanager' => true, - ]), - FormField::text('url', [ - 'label' => 'Url', - ]), - FormField::textarea('html', [ - 'label' => 'Kod HTML', - 'rows' => 6, - ]), - FormField::editor('text', [ - 'label' => 'Treść', - 'toolbar' => 'MyTool', - 'height' => 300, - ]), - ]), - ]; - - $actions = [ - FormAction::save( - '/admin/banners/banner_save/' . ($isNew ? '' : 'id=' . $bannerId), - '/admin/banners/view_list/' - ), - FormAction::cancel('/admin/banners/view_list/'), - ]; - - return new FormEditViewModel( - $this->getFormId(), - $isNew ? 'Nowy baner' : 'Edycja banera', - $banner, - $fields, - $tabs, - $actions, - 'POST', - '/admin/banners/banner_save/' . ($isNew ? '' : 'id=' . $bannerId), - '/admin/banners/view_list/', - true, - ['id' => $bannerId], - $languages, - $errors - ); - } - - /** - * Zwraca identyfikator formularza - */ - private function getFormId(): string - { - return 'banner-edit'; - } -} diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/FilemanagerController.php b/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/FilemanagerController.php deleted file mode 100644 index ba2535e..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/FilemanagerController.php +++ /dev/null @@ -1,46 +0,0 @@ -ensureFilemanagerAccessKey(); - $filemanagerUrl = $this->buildFilemanagerUrl($akey); - - return \Tpl::view('filemanager/filemanager', [ - 'filemanager_url' => $filemanagerUrl, - ]); - } - - private function ensureFilemanagerAccessKey(): string - { - $expiresAt = (int)($_SESSION['rfm_akey_expires'] ?? 0); - $existingKey = trim((string)($_SESSION['rfm_akey'] ?? '')); - - if ($existingKey !== '' && $expiresAt >= time()) { - $_SESSION['rfm_akey_expires'] = time() + self::RFM_KEY_TTL; - return $existingKey; - } - - try { - $newKey = bin2hex(random_bytes(16)); - } catch (\Throwable $e) { - $newKey = sha1(uniqid('rfm', true)); - } - - $_SESSION['rfm_akey'] = $newKey; - $_SESSION['rfm_akey_expires'] = time() + self::RFM_KEY_TTL; - - return $newKey; - } - - private function buildFilemanagerUrl(string $akey): string - { - return self::FILEMANAGER_DIALOG_PATH . '?akey=' . rawurlencode($akey); - } -} - diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/ProductArchiveController.php b/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/ProductArchiveController.php deleted file mode 100644 index 5883b59..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/ProductArchiveController.php +++ /dev/null @@ -1,165 +0,0 @@ -productRepository = $productRepository; - } - - public function list(): string - { - $sortableColumns = ['id', 'name', 'price_brutto', 'price_brutto_promo', 'quantity']; - - $filterDefinitions = [ - [ - 'key' => 'phrase', - 'label' => 'Nazwa / EAN / SKU', - 'type' => 'text', - ], - ]; - - $listRequest = \admin\Support\TableListRequestFactory::fromRequest( - $filterDefinitions, - $sortableColumns, - 'id', - [10, 15, 25, 50, 100], - 10 - ); - - $result = $this->productRepository->listArchivedForAdmin( - $listRequest['filters'], - $listRequest['sortColumn'], - $listRequest['sortDir'], - $listRequest['page'], - $listRequest['perPage'] - ); - - $rows = []; - $lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1; - foreach ($result['items'] as $item) { - $id = (int)($item['id'] ?? 0); - $name = trim((string)($item['name'] ?? '')); - $sku = trim((string)($item['sku'] ?? '')); - $ean = trim((string)($item['ean'] ?? '')); - $imageSrc = trim((string)($item['image_src'] ?? '')); - $imageAlt = trim((string)($item['image_alt'] ?? '')); - $priceBrutto = (string)($item['price_brutto'] ?? ''); - $priceBruttoPromo = (string)($item['price_brutto_promo'] ?? ''); - $quantity = (int)($item['quantity'] ?? 0); - $combinations = (int)($item['combinations'] ?? 0); - - if ($imageSrc === '') { - $imageSrc = '/admin/layout/images/no-image.png'; - } elseif (!preg_match('#^(https?:)?//#i', $imageSrc) && strpos($imageSrc, '/') !== 0) { - $imageSrc = '/' . ltrim($imageSrc, '/'); - } - - $categories = trim((string)\admin\factory\ShopProduct::product_categories($id)); - $categoriesHtml = ''; - if ($categories !== '') { - $categoriesHtml = '' - . htmlspecialchars($categories, ENT_QUOTES, 'UTF-8') - . ''; - } - - $skuEanParts = []; - if ($sku !== '') { - $skuEanParts[] = 'SKU: ' . htmlspecialchars($sku, ENT_QUOTES, 'UTF-8'); - } - if ($ean !== '') { - $skuEanParts[] = 'EAN: ' . htmlspecialchars($ean, ENT_QUOTES, 'UTF-8'); - } - $skuEanHtml = ''; - if (!empty($skuEanParts)) { - $skuEanHtml = '' . implode(', ', $skuEanParts) . ''; - } - - $productCell = '
' - . '' . htmlspecialchars($imageAlt, ENT_QUOTES, 'UTF-8') . '' - . '
' - . '' - . $categoriesHtml - . $skuEanHtml; - - $rows[] = [ - 'lp' => $lp++ . '.', - 'product' => $productCell, - 'price_brutto' => $priceBrutto !== '' ? $priceBrutto : '-', - 'price_brutto_promo' => $priceBruttoPromo !== '' ? $priceBruttoPromo : '-', - 'quantity' => (string)$quantity, - '_actions' => [ - [ - 'label' => 'Przywroc', - 'url' => '/admin/product_archive/unarchive/product_id=' . $id, - 'class' => 'btn btn-xs btn-success', - 'confirm' => 'Na pewno chcesz przywrocic wybrany produkt z archiwum?', - 'confirm_ok' => 'Przywroc', - 'confirm_cancel' => 'Anuluj', - ], - ], - ]; - } - - $total = (int)$result['total']; - $totalPages = max(1, (int)ceil($total / $listRequest['perPage'])); - - $viewModel = new \admin\ViewModels\Common\PaginatedTableViewModel( - [ - ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false], - ['key' => 'product', 'sort_key' => 'name', 'label' => 'Nazwa', 'sortable' => true, 'raw' => true], - ['key' => 'price_brutto', 'sort_key' => 'price_brutto', 'label' => 'Cena', 'class' => 'text-center', 'sortable' => true], - ['key' => 'price_brutto_promo', 'sort_key' => 'price_brutto_promo', 'label' => 'Cena promocyjna', 'class' => 'text-center', 'sortable' => true], - ['key' => 'quantity', 'sort_key' => 'quantity', 'label' => 'Stan MG', 'class' => 'text-center', 'sortable' => true] - ], - $rows, - $listRequest['viewFilters'], - [ - 'column' => $listRequest['sortColumn'], - 'dir' => $listRequest['sortDir'], - ], - [ - 'page' => $listRequest['page'], - 'per_page' => $listRequest['perPage'], - 'total' => $total, - 'total_pages' => $totalPages, - ], - array_merge($listRequest['queryFilters'], [ - 'sort' => $listRequest['sortColumn'], - 'dir' => $listRequest['sortDir'], - 'per_page' => $listRequest['perPage'], - ]), - $listRequest['perPageOptions'], - $sortableColumns, - '/admin/product_archive/products_list/', - 'Brak danych w tabeli.', - null, - null, - 'product-archive/products-list-custom-script' - ); - - return \Tpl::view('product-archive/products-list', [ - 'viewModel' => $viewModel, - ]); - } - - public function unarchive(): void - { - if ( $this->productRepository->unarchive( (int) \S::get( 'product_id' ) ) ) - \S::alert( 'Produkt został przywrócony z archiwum.' ); - else - \S::alert( 'Podczas przywracania produktu z archiwum wystąpił błąd. Proszę spróbować ponownie' ); - - header( 'Location: /admin/product_archive/products_list/' ); - exit; - } -} diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/class.Site.php b/temp/update_build/ver_0.200_20260211_000158/autoload/admin/class.Site.php deleted file mode 100644 index 7d196bd..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/class.Site.php +++ /dev/null @@ -1,356 +0,0 @@ - $user['login'], - 'ts' => time() - ]; - - $json = json_encode($payloadArr, JSON_UNESCAPED_SLASHES); - $sig = hash_hmac('sha256', $json, self::APP_SECRET_KEY); - $payload = base64_encode($json . '.' . $sig); - - setcookie( $cookie_name, $payload, [ - 'expires' => time() + (86400 * 14), - 'path' => '/', - 'domain' => $domain, - 'secure' => true, - 'httponly' => true, - 'samesite' => 'Lax', - ]); - } - } - - public static function special_actions() - { - $sa = \S::get('s-action'); - $domain = preg_replace('/^www\./', '', $_SERVER['SERVER_NAME']); - $cookie_name = 'admin_remember_' . str_replace( '.', '-', $domain ); - - switch ($sa) - { - case 'user-logon': - { - $login = \S::get('login'); - $pass = \S::get('password'); - - $result = \admin\factory\Users::logon($login, $pass); - - if ( $result == 1 ) - { - $user = \admin\factory\Users::details($login); - - if ( $user['twofa_enabled'] == 1 ) - { - \S::set_session( 'twofa_pending', [ - 'uid' => (int)$user['id'], - 'login' => $login, - 'remember' => (bool)\S::get('remember'), - 'started' => time(), - ] ); - - if ( !\admin\factory\Users::send_twofa_code( (int)$user['id'] ) ) - { - \S::alert('Nie udało się wysłać kodu 2FA. Spróbuj ponownie.'); - \S::delete_session('twofa_pending'); - header('Location: /admin/'); - exit; - } - - header('Location: /admin/user/twofa/'); - exit; - } - else - { - $user = \admin\factory\Users::details($login); - - self::finalize_admin_login( - $user, - $domain, - $cookie_name, - (bool)\S::get('remember') - ); - - header('Location: /admin/articles/view_list/'); - exit; - } - } - else - { - if ($result == -1) - { - \S::alert('Z powodu 5 nieudanych prób Twoje konto zostało zablokowane.'); - } - else - { - \S::alert('Podane hasło jest nieprawidłowe lub użytkownik nie istnieje.'); - } - header('Location: /admin/'); - exit; - } - } - break; - - case 'user-2fa-verify': - { - $pending = \S::get_session('twofa_pending'); - if ( !$pending || empty( $pending['uid'] ) ) { - \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.'); - header('Location: /admin/'); - exit; - } - - $code = trim((string)\S::get('twofa')); - if (!preg_match('/^\d{6}$/', $code)) - { - \S::alert('Nieprawidłowy format kodu.'); - header('Location: /admin/user/twofa/'); - exit; - } - - $ok = \admin\factory\Users::verify_twofa_code((int)$pending['uid'], $code); - if (!$ok) - { - \S::alert('Błędny lub wygasły kod.'); - header('Location: /admin/user/twofa/'); - exit; - } - - // 2FA OK — finalna sesja - $user = \admin\factory\Users::details($pending['login']); - - self::finalize_admin_login( - $user, - $domain, - $cookie_name, - $pending['remember'] ? true : false - ); - - header('Location: /admin/articles/view_list/'); - exit; - } - break; - - case 'user-2fa-resend': - { - $pending = \S::get_session('twofa_pending'); - if (!$pending || empty($pending['uid'])) - { - \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.'); - header('Location: /admin/'); - exit; - } - - if (!\admin\factory\Users::send_twofa_code((int)$pending['uid'], true)) - { - \S::alert('Kod można wysłać ponownie po krótkiej przerwie.'); - } - else - { - \S::alert('Nowy kod został wysłany.'); - } - header('Location: /admin/user/twofa/'); - exit; - } - break; - - case 'user-logout': - { - setcookie($cookie_name, "", [ - 'expires' => time() - 86400, - 'path' => '/', - 'domain' => $domain, - 'secure' => true, - 'httponly' => true, - 'samesite' => 'Lax', - ]); - \S::delete_session('twofa_pending'); - session_destroy(); - header('Location: /admin/'); - exit; - } - break; - } - } - - /** - * Mapa nowych kontrolerów: module => fabryka kontrolera (DI) - * Przy migracji kolejnego kontrolera - dodaj wpis tutaj - */ - private static $newControllers = []; - - /** - * Zwraca mapę fabryk kontrolerów (inicjalizacja runtime) - */ - private static function getControllerFactories(): array - { - if ( !empty( self::$newControllers ) ) - return self::$newControllers; - - self::$newControllers = [ - 'Articles' => function() { - global $mdb; - - return new \admin\Controllers\ArticlesController( - new \Domain\Article\ArticleRepository( $mdb ) - ); - }, - 'Banners' => function() { - global $mdb; - - return new \admin\Controllers\BannerController( - new \Domain\Banner\BannerRepository( $mdb ) - ); - }, - 'Settings' => function() { - global $mdb; - - return new \admin\Controllers\SettingsController( - new \Domain\Settings\SettingsRepository( $mdb ) - ); - }, - 'ProductArchive' => function() { - global $mdb; - - return new \admin\Controllers\ProductArchiveController( - new \Domain\Product\ProductRepository( $mdb ) - ); - }, - // Alias dla starego modułu /admin/archive/products_list/ - 'Archive' => function() { - global $mdb; - - return new \admin\Controllers\ProductArchiveController( - new \Domain\Product\ProductRepository( $mdb ) - ); - }, - 'Dictionaries' => function() { - global $mdb; - - return new \admin\Controllers\DictionariesController( - new \Domain\Dictionaries\DictionariesRepository( $mdb ) - ); - }, - 'Filemanager' => function() { - return new \admin\Controllers\FilemanagerController(); - }, - ]; - - return self::$newControllers; - } - - /** - * Tworzy instancję nowego kontrolera z Dependency Injection - */ - private static function createController( string $moduleName ) - { - global $mdb; - - $factories = self::getControllerFactories(); - if ( !isset( $factories[$moduleName] ) ) - return null; - - $factory = $factories[$moduleName]; - if ( !is_callable( $factory ) ) - return null; - - return $factory(); - } - - /** - * Mapowanie nazw akcji: stara_nazwa => nowa_nazwa - * Potrzebne gdy stary routing używa innej konwencji nazw - */ - private static $actionMap = [ - 'gallery_order_save' => 'galleryOrderSave', - 'view_list' => 'list', - 'article_edit' => 'edit', - 'article_save' => 'save', - 'article_delete' => 'delete', - 'banner_edit' => 'edit', - 'banner_save' => 'save', - 'banner_delete' => 'delete', - 'clear_cache' => 'clearCache', - 'clear_cache_ajax' => 'clearCacheAjax', - 'settings_save' => 'save', - 'products_list' => 'list', - 'unit_edit' => 'edit', - 'unit_save' => 'save', - 'unit_delete' => 'delete', - ]; - - public static function route() - { - $_SESSION['admin'] = true; - - if ( \S::get( 'p' ) ) - \S::set_session( 'p' , \S::get( 'p' ) ); - - $page = \S::get_session( 'p' ); - - // Budowanie nazwy modułu - $moduleName = ''; - $results = explode( '_', \S::get( 'module' ) ); - if ( is_array( $results ) ) foreach ( $results as $row ) - $moduleName .= ucfirst( $row ); - - $action = \S::get( 'action' ); - - // 1. Sprawdź czy istnieje nowy kontroler - $factories = self::getControllerFactories(); - if ( isset( $factories[$moduleName] ) ) - { - $controller = self::createController( $moduleName ); - if ( $controller ) - { - // Mapuj nazwę akcji (stara → nowa) lub użyj oryginalnej - $newAction = self::$actionMap[$action] ?? $action; - - if ( method_exists( $controller, $newAction ) ) - { - return $controller->$newAction(); - } - } - - } - - // 2. Fallback na stary kontroler - $class = '\admin\controls\\' . $moduleName; - - if ( class_exists( $class ) and method_exists( new $class, $action ) ) - return call_user_func_array( array( $class, $action ), array() ); - else - { - \S::alert( 'Nieprawidłowy adres url.' ); - return false; - } - } - - static public function update() - { - global $mdb; - - if ( $results = $mdb -> select( 'pp_updates', [ 'name' ], [ 'done' => 0 ] ) ) - { - foreach ( $results as $row ) - { - $class = '\admin\factory\Update'; - $method = $row['name']; - - if ( class_exists( $class ) and method_exists( new $class, $method ) ) - call_user_func_array( array( $class, $method ), array() ); - } - } - } -} diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/controls/class.ShopProduct.php b/temp/update_build/ver_0.200_20260211_000158/autoload/admin/controls/class.ShopProduct.php deleted file mode 100644 index e076736..0000000 --- a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/controls/class.ShopProduct.php +++ /dev/null @@ -1,414 +0,0 @@ - update( 'pp_shop_products', [ 'price_brutto_promo' => $price_brutto_promo, 'price_netto_promo' => $price_netto_promo ], [ 'id' => \S::get( 'products' )[0] ] ); - - \admin\factory\ShopProduct::update_product_combinations_prices( \S::get( 'products' )[0], $price_netto, $vat, $price_netto_promo ); - - echo json_encode( [ 'status' => 'ok', 'price_brutto_promo' => $price_brutto_promo, 'price_brutto' => $price_brutto ] ); - exit; - } - echo json_encode( [ 'status' => 'error' ] ); - exit; - } - - // get_products_by_category - static public function get_products_by_category() { - global $mdb; - - $products = $mdb -> select( 'pp_shop_products_categories', 'product_id', [ 'category_id' => \S::get( 'category_id' ) ] ); - - echo json_encode( [ 'status' => 'ok', 'products' => $products ] ); - exit; - } - - static public function mass_edit() - { - return \Tpl::view( 'shop-product/mass-edit', [ - 'products' => \admin\factory\ShopProduct::products_list(), - 'categories' => \admin\factory\ShopCategory::subcategories( null ), - 'dlang' => \front\factory\Languages::default_language() - ] ); - } - - static public function generate_combination() - { - foreach ( $_POST as $key => $val ) - { - if ( strpos( $key, 'attribute_' ) !== false ) - { - $attribute = explode( 'attribute_', $key ); - $attributes[ $attribute[1] ] = $val; - } - } - - if ( \admin\factory\ShopProduct::generate_permutation( (int) \S::get( 'product_id' ), $attributes ) ) - \S::alert( 'Kombinacje produktu zostały wygenerowane.' ); - - header( 'Location: /admin/shop_product/product_combination/product_id=' . (int) \S::get( 'product_id' ) ); - exit; - } - - //usunięcie kombinacji produktu - static public function delete_combination() - { - if ( \admin\factory\ShopProduct::delete_combination( (int)\S::get( 'combination_id' ) ) ) - \S::alert( 'Kombinacja produktu została usunięta' ); - else - \S::alert( 'Podczas usuwania kombinacji produktu wystąpił błąd. Proszę spróbować ponownie' ); - - header( 'Location: /admin/shop_product/product_combination/product_id=' . \S::get( 'product_id' ) ); - exit; - } - - static public function duplicate_product() - { - if ( \admin\factory\ShopProduct::duplicate_product( (int)\S::get( 'product-id' ), (int)\S::get( 'combination' ) ) ) - \S::set_message( 'Produkt został zduplikowany.' ); - else - \S::alert( 'Podczas duplikowania produktu wystąpił błąd. Proszę spróbować ponownie' ); - - header( 'Location: /admin/shop_product/view_list/' ); - exit; - } - - public static function image_delete() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas usuwania zdjecia wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::delete_img( \S::get( 'image_id' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - public static function images_order_save() - { - if ( \admin\factory\ShopProduct::images_order_save( \S::get( 'product_id' ), \S::get( 'order' ) ) ) - echo json_encode( [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.' ] ); - - exit; - } - - public static function image_alt_change() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany atrybutu alt zdjęcia wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::image_alt_change( \S::get( 'image_id' ), \S::get( 'image_alt' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - // szybka zmiana statusu produktu - static public function change_product_status() { - - if ( \admin\factory\ShopProduct::change_product_status( (int)\S::get( 'product-id' ) ) ) - \S::set_message( 'Status produktu został zmieniony' ); - - header( 'Location: ' . $_SERVER['HTTP_REFERER'] ); - exit; - } - - // szybka zmiana google xml label - static public function product_change_custom_label() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany google xml label wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::product_change_custom_label( (int) \S::get( 'product_id' ), \S::get( 'custom_label' ), \S::get( 'value' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - // szybka zmiana ceny promocyjnej - static public function product_change_price_brutto_promo() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::product_change_price_brutto_promo( (int) \S::get( 'product_id' ), \S::get( 'price' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - // szybka zmiana ceny - static public function product_change_price_brutto() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \admin\factory\ShopProduct::product_change_price_brutto( (int) \S::get( 'product_id' ), \S::get( 'price' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - // pobierz bezpośredni url produktu - static public function ajax_product_url() - { - echo json_encode( [ 'url' => \shop\Product::getProductUrl( \S::get( 'product_id' ) ) ] ); - exit; - } - - // zapisanie produktu - public static function save() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania produktu wystąpił błąd. Proszę spróbować ponownie.' ]; - $values = json_decode( \S::get( 'values' ), true ); - - if ( $id = \admin\factory\ShopProduct::save( - $values['id'], $values['name'], $values['short_description'], $values['description'], $values['status'], $values['meta_description'], $values['meta_keywords'], $values['seo_link'], - $values['copy_from'], $values['categories'], $values['price_netto'], $values['price_brutto'], $values['vat'], $values['promoted'], $values['warehouse_message_zero'], $values['warehouse_message_nonzero'], $values['tab_name_1'], - $values['tab_description_1'], $values['tab_name_2'], $values['tab_description_2'], $values['layout_id'], $values['products_related'], (int) $values['set'], $values['price_netto_promo'], $values['price_brutto_promo'], - $values['new_to_date'], $values['stock_0_buy'], $values['wp'], $values['custom_label_0'], $values['custom_label_1'], $values['custom_label_2'], $values['custom_label_3'], $values['custom_label_4'], $values['additional_message'], (int)$values['quantity'], $values['additional_message_text'], $values['additional_message_required'] == 'on' ? 1 : 0, $values['canonical'], $values['meta_title'], $values['producer_id'], $values['sku'], $values['ean'], $values['product_unit'], $values['weight'], $values['xml_name'], $values['custom_field_name'], $values['custom_field_required'], $values['security_information'], $values['custom_field_type'] - ) ) { - $response = [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.', 'id' => $id ]; - } - - echo json_encode( $response ); - exit; - } - - // product_unarchive - static public function product_unarchive() - { - if ( \admin\factory\ShopProduct::product_unarchive( (int) \S::get( 'product_id' ) ) ) - \S::alert( 'Produkt został przywrócony z archiwum.' ); - else - \S::alert( 'Podczas przywracania produktu z archiwum wystąpił błąd. Proszę spróbować ponownie' ); - - header( 'Location: /admin/product_archive/products_list/' ); - exit; - } - - static public function product_archive() - { - if ( \admin\factory\ShopProduct::product_archive( (int) \S::get( 'product_id' ) ) ) - \S::alert( 'Produkt został przeniesiony do archiwum.' ); - else - \S::alert( 'Podczas przenoszenia produktu do archiwum wystąpił błąd. Proszę spróbować ponownie' ); - - header( 'Location: /admin/shop_product/view_list/' ); - exit; - } - - public static function product_delete() - { - if ( \admin\factory\ShopProduct::product_delete( (int) \S::get( 'id' ) ) ) - \S::set_message( 'Produkt został usunięty.' ); - else - \S::alert( 'Podczas usuwania produktu wystąpił błąd. Proszę spróbować ponownie' ); - header( 'Location: /admin/shop_product/view_list/' ); - exit; - } - - // edycja produktu - public static function product_edit() { - global $user, $mdb; - - if ( !$user ) { - header( 'Location: /admin/' ); - exit; - } - - \admin\factory\ShopProduct::delete_nonassigned_images(); - \admin\factory\ShopProduct::delete_nonassigned_files(); - - return \Tpl::view( 'shop-product/product-edit', [ - 'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'id' ) ), - 'languages' => \admin\factory\Languages::languages_list(), - 'categories' => \admin\factory\ShopCategory::subcategories( null ), - 'layouts' => \admin\factory\Layouts::layouts_list(), - 'products' => \admin\factory\ShopProduct::products_list(), - 'dlang' => \front\factory\Languages::default_language(), - 'sets' => \shop\ProductSet::sets_list(), - 'producers' => \admin\factory\ShopProducer::all(), - 'units' => ( new \Domain\Dictionaries\DictionariesRepository( $mdb ) ) -> allUnits(), - 'user' => $user - ] ); - } - - // ajax_load_products ARCHIVE - static public function ajax_load_products_archive() - { - echo json_encode( [ - 'status' => 'deprecated', - 'msg' => 'Endpoint nie jest juz wspierany. Uzyj /admin/product_archive/products_list/.', - 'redirect_url' => '/admin/product_archive/products_list/' - ] ); - exit; - } - - // ajax_load_products - static public function ajax_load_products() { - - $response = [ 'status' => 'error', 'msg' => 'Podczas ładowania produktów wystąpił błąd. Proszę spróbować ponownie.' ]; - - \S::set_session( 'products_list_current_page', \S::get( 'current_page' ) ); - \S::set_session( 'products_list_query', \S::get( 'query' ) ); - - if ( $products = \admin\factory\ShopProduct::ajax_products_list( \S::get_session( 'products_list_current_page' ), \S::get_session( 'products_list_query' ) ) ) { - $response = [ - 'status' => 'ok', - 'pagination_max' => ceil( $products['products_count'] / 10 ), - 'html' => \Tpl::view( 'shop-product/products-list-table', [ - 'products' => $products['products'], - 'current_page' => \S::get( 'current_page' ), - 'baselinker_enabled' => \admin\factory\Integrations::baselinker_settings( 'enabled' ), - 'apilo_enabled' => \admin\factory\Integrations::apilo_settings( 'enabled' ), - 'sellasist_enabled' => \admin\factory\Integrations::sellasist_settings( 'enabled' ), - 'show_xml_data' => \S::get_session( 'show_xml_data' ) - ] ) - ]; - } - - echo json_encode( $response ); - exit; - } - - static public function view_list() - { - $current_page = \S::get_session( 'products_list_current_page' ); - - if ( !$current_page ) { - $current_page = 1; - \S::set_session( 'products_list_current_page', $current_page ); - } - - $query = \S::get_session( 'products_list_query' ); - if ( $query ) { - $query_array = []; - parse_str( $query, $query_array ); - } - - if ( \S::get( 'show_xml_data' ) === 'true' ) { - \S::set_session( 'show_xml_data', true ); - } else if ( \S::get( 'show_xml_data' ) === 'false' ) { - \S::set_session( 'show_xml_data', false ); - } - - return \Tpl::view( 'shop-product/products-list', [ - 'current_page' => $current_page, - 'query_array' => $query_array, - 'pagination_max' => ceil( \admin\factory\ShopProduct::count_product() / 10 ), - 'baselinker_enabled' => \admin\factory\Integrations::baselinker_settings( 'enabled' ), - 'apilo_enabled' => \admin\factory\Integrations::apilo_settings( 'enabled' ), - 'sellasist_enabled' => \admin\factory\Integrations::sellasist_settings( 'enabled' ), - 'show_xml_data' => \S::get_session( 'show_xml_data' ), - 'shoppro_enabled' => \admin\factory\Integrations::shoppro_settings( 'enabled' ) - ] ); - } - - // - // KOMBINACJE PRODUKTU - // - - // zapisanie możliwości zakupu przy stanie 0 w kombinacji produktu - static public function product_combination_stock_0_buy_save() - { - \admin\factory\ShopProduct::product_combination_stock_0_buy_save( (int)\S::get( 'product_id' ), \S::get( 'stock_0_buy' ) ); - exit; - } - - // zapisanie sku w kombinacji produktu - static public function product_combination_sku_save() - { - \admin\factory\ShopProduct::product_combination_sku_save( (int)\S::get( 'product_id' ), \S::get( 'sku' ) ); - exit; - } - - // zapisanie ilości w kombinacji produktu - static public function product_combination_quantity_save() - { - \admin\factory\ShopProduct::product_combination_quantity_save( (int)\S::get( 'product_id' ), \S::get( 'quantity' ) ); - exit; - } - - // zapisanie ceny w kombinacji produktu - static public function product_combination_price_save() - { - \admin\factory\ShopProduct::product_combination_price_save( (int)\S::get( 'product_id' ), \S::get( 'price' ) ); - exit; - } - - //wyświetlenie kombinacji produktu - static public function product_combination() - { - return \Tpl::view( 'shop-product/product-combination', [ - 'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'product_id' ) ), - 'attributes' => \admin\factory\ShopAttribute::get_attributes_list(), - 'default_language' => \front\factory\Languages::default_language(), - 'product_permutations' => \admin\factory\ShopProduct::get_product_permutations( (int) \S::get( 'product_id' ) ) - ] ); - } - - // generate_sku_code - static public function generate_sku_code() { - $response = [ 'status' => 'error', 'msg' => 'Podczas generowania kodu sku wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( $sku = \shop\Product::generate_sku_code( \S::get( 'product_id' ) ) ) - $response = [ 'status' => 'ok', 'sku' => $sku ]; - - echo json_encode( $response ); - exit; - } - - // product_xml_name_save - static public function product_xml_name_save() { - $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania nazwy produktu wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \shop\Product::product_xml_name_save( \S::get( 'product_id' ), \S::get( 'product_xml_name' ), \S::get( 'lang_id' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } - - // product_custom_label_suggestions - static public function product_custom_label_suggestions() { - $response = [ 'status' => 'error', 'msg' => 'Podczas pobierania sugestii dla custom label wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( $suggestions = \shop\Product::product_custom_label_suggestions( \S::get( 'custom_label' ), \S::get( 'label_type' ) ) ) - $response = [ 'status' => 'ok', 'suggestions' => $suggestions ]; - - echo json_encode( $response ); - exit; - } - - // product_custom_label_save - static public function product_custom_label_save() { - $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania custom label wystąpił błąd. Proszę spróbować ponownie.' ]; - - if ( \shop\Product::product_custom_label_save( \S::get( 'product_id' ), \S::get( 'custom_label' ), \S::get( 'label_type' ) ) ) - $response = [ 'status' => 'ok' ]; - - echo json_encode( $response ); - exit; - } -} diff --git a/temp/update_build/ver_0.290.zip b/temp/update_build/ver_0.290.zip deleted file mode 100644 index 3577bf8..0000000 Binary files a/temp/update_build/ver_0.290.zip and /dev/null differ diff --git a/temp/update_build/ver_0.290_files.txt b/temp/update_build/ver_0.290_files.txt deleted file mode 100644 index 5c689ac..0000000 --- a/temp/update_build/ver_0.290_files.txt +++ /dev/null @@ -1,5 +0,0 @@ -F: ../autoload/front/controls/class.ShopCoupon.php -F: ../autoload/front/factory/class.ShopCoupon.php -F: ../autoload/front/controls/class.ShopOrder.php -F: ../autoload/front/factory/class.ShopOrder.php -F: ../autoload/front/view/class.ShopOrder.php diff --git a/temp/update_build/ver_0.291.zip b/temp/update_build/ver_0.291.zip deleted file mode 100644 index ca67eaa..0000000 Binary files a/temp/update_build/ver_0.291.zip and /dev/null differ diff --git a/temp/update_build/ver_0.291_files.txt b/temp/update_build/ver_0.291_files.txt deleted file mode 100644 index 715550b..0000000 --- a/temp/update_build/ver_0.291_files.txt +++ /dev/null @@ -1,2 +0,0 @@ -F: ../autoload/front/controls/class.ShopProducer.php -F: ../autoload/shop/class.Producer.php diff --git a/temp/update_build/ver_0.292.zip b/temp/update_build/ver_0.292.zip deleted file mode 100644 index e992110..0000000 Binary files a/temp/update_build/ver_0.292.zip and /dev/null differ diff --git a/temp/update_build/ver_0.292_files.txt b/temp/update_build/ver_0.292_files.txt deleted file mode 100644 index 0993463..0000000 --- a/temp/update_build/ver_0.292_files.txt +++ /dev/null @@ -1,9 +0,0 @@ -F: ../autoload/front/controls/class.ShopProduct.php -F: ../autoload/front/factory/class.ShopPaymentMethod.php -F: ../autoload/front/factory/class.ShopProduct.php -F: ../autoload/front/factory/class.ShopPromotion.php -F: ../autoload/front/factory/class.ShopStatuses.php -F: ../autoload/front/factory/class.ShopTransport.php -F: ../autoload/front/view/class.ShopPaymentMethod.php -F: ../autoload/front/view/class.ShopTransport.php -F: ../autoload/shop/class.PaymentMethod.php diff --git a/temp/update_build/ver_0.293.zip b/temp/update_build/ver_0.293.zip deleted file mode 100644 index e832633..0000000 Binary files a/temp/update_build/ver_0.293.zip and /dev/null differ diff --git a/temp/update_build/ver_0.293_files.txt b/temp/update_build/ver_0.293_files.txt deleted file mode 100644 index 0a506e8..0000000 --- a/temp/update_build/ver_0.293_files.txt +++ /dev/null @@ -1,2 +0,0 @@ -F: ../autoload/front/controls/class.Site.php -F: ../autoload/front/view/class.Site.php diff --git a/temp/ver_0.294.zip b/temp/ver_0.294.zip deleted file mode 100644 index 80fefac..0000000 Binary files a/temp/ver_0.294.zip and /dev/null differ diff --git a/temp/ver_0.294_files.txt b/temp/ver_0.294_files.txt deleted file mode 100644 index 5b811be..0000000 --- a/temp/ver_0.294_files.txt +++ /dev/null @@ -1,12 +0,0 @@ -F: ../autoload/shop/class.Basket.php -F: ../autoload/shop/class.Category.php -F: ../autoload/shop/class.Coupon.php -F: ../autoload/shop/class.Order.php -F: ../autoload/shop/class.Product.php -F: ../autoload/shop/class.ProductAttribute.php -F: ../autoload/shop/class.ProductCustomField.php -F: ../autoload/shop/class.ProductSet.php -F: ../autoload/shop/class.Promotion.php -F: ../autoload/shop/class.Search.php -F: ../autoload/shop/class.Shop.php -F: ../autoload/shop/class.Transport.php diff --git a/tests/Unit/Domain/Order/OrderAdminServiceTest.php b/tests/Unit/Domain/Order/OrderAdminServiceTest.php new file mode 100644 index 0000000..569a0da --- /dev/null +++ b/tests/Unit/Domain/Order/OrderAdminServiceTest.php @@ -0,0 +1,230 @@ +createMock(OrderRepository::class); + } + + return new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo); + } + + public function testConstructorAcceptsOnlyOrderRepository(): void + { + $orderRepo = $this->createMock(OrderRepository::class); + $service = new OrderAdminService($orderRepo); + $this->assertInstanceOf(OrderAdminService::class, $service); + } + + public function testConstructorAcceptsAllDependencies(): void + { + $orderRepo = $this->createMock(OrderRepository::class); + $productRepo = $this->createMock(ProductRepository::class); + $settingsRepo = $this->createMock(SettingsRepository::class); + $transportRepo = $this->createMock(TransportRepository::class); + + $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo); + $this->assertInstanceOf(OrderAdminService::class, $service); + } + + public function testSearchProductsReturnsEmptyForEmptyQuery(): void + { + $productRepo = $this->createMock(ProductRepository::class); + $productRepo->expects($this->never())->method('searchProductByNameAjax'); + + $service = $this->createService(null, $productRepo); + $result = $service->searchProducts('', 'pl'); + + $this->assertSame([], $result); + } + + public function testSearchProductsReturnsEmptyWithoutProductRepo(): void + { + $service = $this->createService(); + $result = $service->searchProducts('test', 'pl'); + + $this->assertSame([], $result); + } + + public function testSearchProductsReturnsFormattedResults(): void + { + $productRepo = $this->createMock(ProductRepository::class); + $productRepo->method('searchProductByNameAjax') + ->with('koszulka', 'pl') + ->willReturn([ + ['product_id' => 10], + ['product_id' => 20], + ]); + + $productRepo->method('findCached') + ->willReturnCallback(function ($id) { + if ($id === 10) { + return [ + 'language' => ['name' => 'Koszulka biała'], + 'sku' => 'KB-001', + 'ean' => '', + 'price_brutto' => 49.99, + 'price_brutto_promo' => 39.99, + 'vat' => 23, + 'quantity' => 15, + 'parent_id' => 0, + ]; + } + return null; // product 20 not found + }); + + $productRepo->method('getProductImg') + ->willReturn('/images/products/test.jpg'); + + $service = $this->createService(null, $productRepo); + $results = $service->searchProducts('koszulka', 'pl'); + + $this->assertCount(1, $results); + $this->assertSame(10, $results[0]['product_id']); + $this->assertSame('Koszulka biała', $results[0]['name']); + $this->assertSame('KB-001', $results[0]['sku']); + $this->assertSame(49.99, $results[0]['price_brutto']); + $this->assertSame(39.99, $results[0]['price_brutto_promo']); + $this->assertSame(15, $results[0]['quantity']); + } + + public function testSaveOrderProductsReturnsFalseForInvalidOrderId(): void + { + $service = $this->createService(); + $this->assertFalse($service->saveOrderProducts(0, [])); + } + + public function testSaveOrderProductsDeletesRemovedProducts(): void + { + $orderRepo = $this->createMock(OrderRepository::class); + $productRepo = $this->createMock(ProductRepository::class); + $settingsRepo = $this->createMock(SettingsRepository::class); + $transportRepo = $this->createMock(TransportRepository::class); + + // Existing products + $orderRepo->method('orderProducts') + ->with(1) + ->willReturn([ + ['id' => 100, 'product_id' => 5, 'quantity' => 2, 'price_brutto' => 10, 'price_brutto_promo' => 0], + ['id' => 101, 'product_id' => 6, 'quantity' => 1, 'price_brutto' => 20, 'price_brutto_promo' => 0], + ]); + + // Product 100 is submitted with delete flag + // Product 101 is not submitted at all (also deleted) + $orderRepo->expects($this->exactly(2))->method('deleteOrderProduct'); + $orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]); + $transportRepo->method('findActiveById')->willReturn(null); + + // Stock should be returned for both deleted products + $productRepo->method('getQuantity')->willReturn(10); + $productRepo->expects($this->exactly(2))->method('updateQuantity'); + + $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo); + $result = $service->saveOrderProducts(1, [ + ['order_product_id' => 100, 'delete' => '1', 'quantity' => 2], + ]); + + $this->assertTrue($result); + } + + public function testSaveOrderProductsUpdatesQuantityAndAdjustsStock(): void + { + $orderRepo = $this->createMock(OrderRepository::class); + $productRepo = $this->createMock(ProductRepository::class); + $settingsRepo = $this->createMock(SettingsRepository::class); + $transportRepo = $this->createMock(TransportRepository::class); + + // Existing: qty=3 + $orderRepo->method('orderProducts') + ->willReturn([ + ['id' => 100, 'product_id' => 5, 'quantity' => 3, 'price_brutto' => 10, 'price_brutto_promo' => 0], + ]); + + // Submit: qty=5 (increased by 2 → stock decreases by 2) + $orderRepo->expects($this->once())->method('updateOrderProduct') + ->with(100, $this->callback(function ($data) { + return $data['quantity'] === 5; + })); + + $orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]); + $transportRepo->method('findActiveById')->willReturn(null); + + $productRepo->method('getQuantity')->with(5)->willReturn(20); + $productRepo->expects($this->once())->method('updateQuantity') + ->with(5, 18); // 20 + (3 - 5) = 18 + + $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo); + $service->saveOrderProducts(1, [ + ['order_product_id' => 100, 'product_id' => 5, 'quantity' => 5, 'price_brutto' => 10, 'price_brutto_promo' => 0], + ]); + } + + public function testSaveOrderProductsAddsNewProductAndDecreasesStock(): void + { + $orderRepo = $this->createMock(OrderRepository::class); + $productRepo = $this->createMock(ProductRepository::class); + $settingsRepo = $this->createMock(SettingsRepository::class); + $transportRepo = $this->createMock(TransportRepository::class); + + $orderRepo->method('orderProducts')->willReturn([]); + + $orderRepo->expects($this->once())->method('addOrderProduct') + ->with(1, $this->callback(function ($data) { + return $data['product_id'] === 10 + && $data['name'] === 'New Product' + && $data['quantity'] === 2; + })); + + $orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]); + $transportRepo->method('findActiveById')->willReturn(null); + + $productRepo->method('getQuantity')->with(10)->willReturn(15); + $productRepo->expects($this->once())->method('updateQuantity') + ->with(10, 13); // 15 - 2 + + $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo); + $service->saveOrderProducts(1, [ + [ + 'order_product_id' => 0, + 'product_id' => 10, + 'parent_product_id' => 10, + 'name' => 'New Product', + 'vat' => 23, + 'price_brutto' => 50, + 'price_brutto_promo' => 0, + 'quantity' => 2, + ], + ]); + } + + public function testGetFreeDeliveryThresholdReturnsZeroWithoutSettingsRepo(): void + { + $service = $this->createService(); + $this->assertSame(0.0, $service->getFreeDeliveryThreshold()); + } + + public function testGetFreeDeliveryThresholdReturnsValue(): void + { + $settingsRepo = $this->createMock(SettingsRepository::class); + $settingsRepo->method('getSingleValue') + ->with('free_delivery') + ->willReturn('150.00'); + + $service = $this->createService(null, null, $settingsRepo); + $this->assertSame(150.0, $service->getFreeDeliveryThreshold()); + } +} diff --git a/tests/Unit/Domain/Order/OrderRepositoryTest.php b/tests/Unit/Domain/Order/OrderRepositoryTest.php index 98cac9b..bc93a8b 100644 --- a/tests/Unit/Domain/Order/OrderRepositoryTest.php +++ b/tests/Unit/Domain/Order/OrderRepositoryTest.php @@ -209,6 +209,138 @@ class OrderRepositoryTest extends TestCase $this->assertSame($expectedPrefix . '006', $number); } + // --- Order product CRUD tests --- + + public function testGetOrderProductReturnsNullForInvalidId(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->never())->method('get'); + + $repository = new OrderRepository($mockDb); + $this->assertNull($repository->getOrderProduct(0)); + $this->assertNull($repository->getOrderProduct(-1)); + } + + public function testGetOrderProductReturnsArray(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->method('get') + ->with('pp_shop_order_products', '*', ['id' => 5]) + ->willReturn(['id' => 5, 'order_id' => 1, 'name' => 'Test']); + + $repository = new OrderRepository($mockDb); + $result = $repository->getOrderProduct(5); + + $this->assertIsArray($result); + $this->assertSame(5, $result['id']); + } + + public function testAddOrderProductReturnsNullForInvalidOrderId(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->never())->method('insert'); + + $repository = new OrderRepository($mockDb); + $this->assertNull($repository->addOrderProduct(0, ['name' => 'Test'])); + } + + public function testAddOrderProductInsertsAndReturnsId(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once())->method('insert') + ->with('pp_shop_order_products', $this->callback(function ($data) { + return $data['order_id'] === 10 + && $data['product_id'] === 5 + && $data['name'] === 'Test Product' + && $data['quantity'] === 2; + })); + $mockDb->method('id')->willReturn('99'); + + $repository = new OrderRepository($mockDb); + $result = $repository->addOrderProduct(10, [ + 'product_id' => 5, + 'name' => 'Test Product', + 'quantity' => 2, + 'price_brutto' => 19.99, + ]); + + $this->assertSame(99, $result); + } + + public function testUpdateOrderProductReturnsFalseForInvalidId(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->never())->method('update'); + + $repository = new OrderRepository($mockDb); + $this->assertFalse($repository->updateOrderProduct(0, ['quantity' => 3])); + } + + public function testUpdateOrderProductUpdatesFields(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once())->method('update') + ->with('pp_shop_order_products', $this->callback(function ($data) { + return $data['quantity'] === 3 + && $data['price_brutto'] === 25.50; + }), ['id' => 7]); + + $repository = new OrderRepository($mockDb); + $result = $repository->updateOrderProduct(7, [ + 'quantity' => 3, + 'price_brutto' => 25.50, + ]); + + $this->assertTrue($result); + } + + public function testUpdateOrderProductReturnsFalseForEmptyData(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->never())->method('update'); + + $repository = new OrderRepository($mockDb); + $this->assertFalse($repository->updateOrderProduct(7, [])); + } + + public function testDeleteOrderProductReturnsFalseForInvalidId(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->never())->method('delete'); + + $repository = new OrderRepository($mockDb); + $this->assertFalse($repository->deleteOrderProduct(0)); + } + + public function testDeleteOrderProductCallsDelete(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once())->method('delete') + ->with('pp_shop_order_products', ['id' => 12]); + + $repository = new OrderRepository($mockDb); + $this->assertTrue($repository->deleteOrderProduct(12)); + } + + public function testUpdateTransportCostDoesNothingForInvalidId(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->never())->method('update'); + + $repository = new OrderRepository($mockDb); + $repository->updateTransportCost(0, 15.0); + } + + public function testUpdateTransportCostUpdatesOrder(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once())->method('update') + ->with('pp_shop_orders', ['transport_cost' => 12.50], ['id' => 5]); + + $repository = new OrderRepository($mockDb); + $repository->updateTransportCost(5, 12.50); + } + public function testGenerateOrderNumberStartsAt001(): void { $mockDb = $this->createMock(\medoo::class); diff --git a/tests/Unit/admin/Controllers/ShopOrderControllerTest.php b/tests/Unit/admin/Controllers/ShopOrderControllerTest.php index f055c68..2b72a32 100644 --- a/tests/Unit/admin/Controllers/ShopOrderControllerTest.php +++ b/tests/Unit/admin/Controllers/ShopOrderControllerTest.php @@ -41,6 +41,7 @@ class ShopOrderControllerTest extends TestCase $this->assertTrue(method_exists($this->controller, 'toggle_trustmate_send')); $this->assertTrue(method_exists($this->controller, 'delete')); $this->assertTrue(method_exists($this->controller, 'order_delete')); + $this->assertTrue(method_exists($this->controller, 'search_products_ajax')); } public function testViewActionsReturnString(): void @@ -70,6 +71,7 @@ class ShopOrderControllerTest extends TestCase $this->assertEquals('void', (string)$reflection->getMethod('toggle_trustmate_send')->getReturnType()); $this->assertEquals('void', (string)$reflection->getMethod('delete')->getReturnType()); $this->assertEquals('void', (string)$reflection->getMethod('order_delete')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('search_products_ajax')->getReturnType()); } public function testConstructorRequiresOrderAdminService(): void @@ -78,7 +80,9 @@ class ShopOrderControllerTest extends TestCase $constructor = $reflection->getConstructor(); $params = $constructor->getParameters(); - $this->assertCount(1, $params); + $this->assertCount(2, $params); $this->assertEquals('Domain\\Order\\OrderAdminService', $params[0]->getType()->getName()); + $this->assertEquals('Domain\\Product\\ProductRepository', $params[1]->getType()->getName()); + $this->assertTrue($params[1]->isOptional()); } } diff --git a/updates/0.20/ver_0.295.zip b/updates/0.20/ver_0.295.zip new file mode 100644 index 0000000..6aa0d93 Binary files /dev/null and b/updates/0.20/ver_0.295.zip differ diff --git a/updates/changelog.php b/updates/changelog.php index a34c25c..6bc7f2b 100644 --- a/updates/changelog.php +++ b/updates/changelog.php @@ -1,3 +1,9 @@ +ver. 0.295 - 19.02.2026
+- NEW - Edycja produktów w zamówieniu z panelu admina (dodawanie, usuwanie, zmiana ilości/cen) +- NEW - Wyszukiwarka produktów AJAX w formularzu edycji zamówienia +- NEW - Automatyczna korekta stanów magazynowych i przeliczanie kosztu dostawy +- FIX - Cena promo w zamówieniu = 0 gdy identyczna z ceną bazową +
ver. 0.294 - 19.02.2026
- FIX - Code review zakończony (96/96 klas, ~1144 metod): 27 fixów across all layers - FIX - Domain: null guard na query()->fetchAll() w 8 repozytoriach, redundancja DI w PromotionRepository diff --git a/updates/versions.php b/updates/versions.php index 6de390c..2b5c3dd 100644 --- a/updates/versions.php +++ b/updates/versions.php @@ -1,5 +1,5 @@