From 7574785d684f9da73cd0577dfeb6b1e018e96a0a Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Sat, 14 Feb 2026 00:56:09 +0100 Subject: [PATCH] release 0.267: front layout/basket fixes and product redirect hardening --- .phpunit.result.cache | 2 +- admin/templates/html/input-switch.php | 4 +- apilo-bck | 339 ------------------- autoload/admin/factory/class.ShopProduct.php | 58 +++- autoload/class.S.php | 55 +-- autoload/front/factory/class.Layouts.php | 95 +++++- autoload/front/view/class.Site.php | 3 + autoload/shop/class.Basket.php | 22 +- autoload/shop/class.Product.php | 17 +- geocode-cache.php | 211 ------------ templates/shop-basket/basket-details.php | 20 +- templates/shop-product/product.php | 6 +- updates/0.20/ver_0.267.zip | Bin 0 -> 40517 bytes updates/0.20/ver_0.267_files.txt | 2 + updates/0.20/ver_0.267_sql.txt | 11 + updates/changelog.php | 11 +- updates/versions.php | 2 +- 17 files changed, 251 insertions(+), 607 deletions(-) delete mode 100644 apilo-bck delete mode 100644 geocode-cache.php create mode 100644 updates/0.20/ver_0.267.zip create mode 100644 updates/0.20/ver_0.267_files.txt create mode 100644 updates/0.20/ver_0.267_sql.txt diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 228c629..df04dc3 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":1,"defects":{"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.001,"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.002,"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.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedFilesDeletesDbRows":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedImagesDeletesDbRows":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveReturnsZeroWhenInsertFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveSetsStatusToMinusOne":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveReturnsFalseWhenUpdateFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderUpdatesImageOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderSkipsEmptyValues":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasBrowseListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasGalleryOrderSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testBrowseListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testGalleryOrderSaveMethodReturnType":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminUsesBoundParamsForTitleFilter":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveWithLegacyFormat":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveUpdatesExistingTranslationsByBannerAndLang":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testListForAdminIncludesThumbnailSrc":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testRestoreSetsStatusToZero":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeletePermanentlyRemovesArticleAndRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListArchivedForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsUnitWithTranslations":0.001,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsNullWhenUnitNotFound":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testSaveInsertsNewUnitAndTranslationsForStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testDeleteRemovesUnitAndTranslations":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdReturnsTextFromDatabase":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdSupportsStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testAllUnitsReturnsArrayIndexedById":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsArrayOrNull":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveLanguageRejectsInvalidLanguageId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveTranslationInsertsNewTranslationAndReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDeleteTranslationReturnsBoolean":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdReturnsLanguageWithStartFlag":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdFallsBackToFirstLanguageOrPl":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsLayoutWithRelations":0.001,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testDeleteReturnsFalseWhenOnlyOneLayoutExists":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsDefaultLayoutWhenRecordDoesNotExist":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testSaveInsertsNewLayoutAndReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testListAllReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsNullForInvalidId":0.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.08,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.081,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.155,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseWhen2FADisabled":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseForInvalidEmail":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testUpdateByIdCallsDbUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testListForAdminReturnsItemsAndTotal":0.001,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorAcceptsRepository":0.002,"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.001,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingUpdatesExistingValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingInsertsNewValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testInvalidProviderThrowsException":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testLinkProductUpdatesDatabase":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testUnlinkProductClearsFields":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloGetAccessTokenReturnsNullWithoutSettings":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListThrowsForInvalidType":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testAllPublicMethodsExist":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSettingsTableMapping":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShopproProviderWorks":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenusListReturnsArray":0.001,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenuDeleteReturnsFalseWhenMenuHasPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testGenerateSeoLinkAddsSuffixWhenBaseSlugExists":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testPageUrlPreviewBuildsLanguagePrefixedUrlForNonDefaultLanguage":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testFindReturnsDefaultPromotionForInvalidId":0.001,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testSaveInsertsPromotionAndReturnsId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageAltChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileNameChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageAltChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileNameChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloSettingsMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloDataFetchMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloProductMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllShopproMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testApiloSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testShopproSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testVoidReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveSellasistMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveBaselinkerMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorRequiresPagesLanguagesAndLayoutsRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorRequiresPromotionRepository":0}} \ No newline at end of file +{"version":1,"defects":{"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.001,"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.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.006,"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.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveReturnsZeroWhenInsertFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveSetsStatusToMinusOne":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveReturnsFalseWhenUpdateFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderUpdatesImageOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderSkipsEmptyValues":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasBrowseListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasGalleryOrderSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testBrowseListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testGalleryOrderSaveMethodReturnType":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminUsesBoundParamsForTitleFilter":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveWithLegacyFormat":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveUpdatesExistingTranslationsByBannerAndLang":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testListForAdminIncludesThumbnailSrc":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testRestoreSetsStatusToZero":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeletePermanentlyRemovesArticleAndRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListArchivedForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsUnitWithTranslations":0.001,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsNullWhenUnitNotFound":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testSaveInsertsNewUnitAndTranslationsForStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testDeleteRemovesUnitAndTranslations":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdReturnsTextFromDatabase":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdSupportsStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testAllUnitsReturnsArrayIndexedById":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsArrayOrNull":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveLanguageRejectsInvalidLanguageId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveTranslationInsertsNewTranslationAndReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDeleteTranslationReturnsBoolean":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdReturnsLanguageWithStartFlag":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdFallsBackToFirstLanguageOrPl":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsLayoutWithRelations":0.001,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testDeleteReturnsFalseWhenOnlyOneLayoutExists":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsDefaultLayoutWhenRecordDoesNotExist":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testSaveInsertsNewLayoutAndReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testListAllReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsNullForInvalidId":0.002,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSaveSettingsUpdatesHeaderAndFooter":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testDeleteTemplateReturnsFalseForAdminTemplate":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateByNameReturnsText":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsDefaultContainerForInvalidId":0.001,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsContainerWithTranslations":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDetailsForLanguageReturnsNullForInvalidData":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserWhenExists":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsErrorWhenLoginIsTaken":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsOkWhenAvailable":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveCreatesUserWithNormalizedSwitches":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithoutPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrue":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsFalseOnFailure":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsUserByLogin":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsSuccessForValidCredentials":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsNegativeOneForBlockedUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseAfterMaxAttempts":0.077,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.076,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.153,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseWhen2FADisabled":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseForInvalidEmail":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testUpdateByIdCallsDbUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorAcceptsRepository":0.002,"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.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.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.001,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingUpdatesExistingValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingInsertsNewValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testInvalidProviderThrowsException":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testLinkProductUpdatesDatabase":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testUnlinkProductClearsFields":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloGetAccessTokenReturnsNullWithoutSettings":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListThrowsForInvalidType":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testAllPublicMethodsExist":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSettingsTableMapping":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShopproProviderWorks":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenusListReturnsArray":0.001,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenuDeleteReturnsFalseWhenMenuHasPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testGenerateSeoLinkAddsSuffixWhenBaseSlugExists":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testPageUrlPreviewBuildsLanguagePrefixedUrlForNonDefaultLanguage":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testFindReturnsDefaultPromotionForInvalidId":0.001,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testSaveInsertsPromotionAndReturnsId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageAltChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileNameChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageAltChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileNameChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorAcceptsDependencies":0.001,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloSettingsMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloDataFetchMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloProductMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllShopproMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testApiloSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testShopproSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testVoidReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveSellasistMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveBaselinkerMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorRequiresPagesLanguagesAndLayoutsRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorRequiresPromotionRepository":0}} \ No newline at end of file diff --git a/admin/templates/html/input-switch.php b/admin/templates/html/input-switch.php index 0d9aca4..cb3afc7 100644 --- a/admin/templates/html/input-switch.php +++ b/admin/templates/html/input-switch.php @@ -15,7 +15,7 @@ $out .= 'id="' . $this -> params['id'] . '" '; else $out .= 'id="' . $this -> params['name'] . '" '; - $out .= 'name="' . $this -> params['name'] . '" type="checkbox" value="1"'; + $out .= 'name="' . $this -> params['name'] . '" type="checkbox" value="on"'; if ( $this -> params['checked'] ) $out .= 'checked="checked" '; @@ -24,4 +24,4 @@ $out .= ''; $out .= ''; -echo $out; \ No newline at end of file +echo $out; diff --git a/apilo-bck b/apilo-bck deleted file mode 100644 index d31c03f..0000000 --- a/apilo-bck +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - - - - - - - - - -const currentUrl = window.location.href; -console.log(currentUrl); -const patterns = [ - /^https:\/\/projectpro\.apilo\.com\/order\/order\/news\/?$/, - /^https:\/\/projectpro\.apilo\.com\/order\/order\/in-progress\/?$/, - /^https:\/\/projectpro\.apilo\.com\/order\/order\/to-send\/?$/, - /^https:\/\/projectpro\.apilo\.com\/order\/order\/completed\/?$/, - /^https:\/\/projectpro\.apilo\.com\/order\/order\/all\/?$/ -]; -if (patterns.some(pattern => pattern.test(currentUrl))) { - waitForTableAndSetImage(); - attachTableReloadListener(); -} - -function waitForTableAndSetImage() { - const intervalId = setInterval(() => { - let dataTables_scrollBody = document.getElementsByClassName('dataTables_scrollBody'); - if (dataTables_scrollBody.length > 0) { - let dataTables_tbody = dataTables_scrollBody[0].getElementsByTagName('tbody')[0]; - let rows = dataTables_tbody.getElementsByTagName('tr'); - - if (rows.length > 0) { - clearInterval(intervalId); - setImageToProduct(); - } - } - }, 100); -} - -function attachTableReloadListener() { - const table = document.querySelector('.dataTables_scrollBody table'); - if (table) { - const observer = new MutationObserver((mutationsList, observer) => { - for (const mutation of mutationsList) { - if (mutation.type === 'childList') { - waitForTableAndSetImage(); - break; - } - } - }); - - observer.observe(table.querySelector('tbody'), { childList: true }); - } -} - -function setImageToProduct(img = '') { - let dataTables_scrollBody = document.getElementsByClassName('dataTables_scrollBody'); - if (dataTables_scrollBody.length > 0) { - let dataTables_tbody = dataTables_scrollBody[0].getElementsByTagName('tbody')[0]; - let rows = dataTables_tbody.getElementsByTagName('tr'); - - for (let i = 0; i < rows.length; i++) { - let cells = rows[i].getElementsByTagName('td'); - - if (cells.length > 1) { - let secondCellText = cells[1].textContent.trim(); - let domain; - - if (secondCellText.includes('marianek.pl')) { - domain = 'marianek.pl'; - } else if (secondCellText.includes('pomysloweprezenty.pl')) { - domain = 'pomysloweprezenty.pl'; - } else { - continue; - } - - if (cells.length >= 5) { - let fifthCell = cells[4]; - let divsInFifthCell = fifthCell.children; - - for (let i = 0; i < divsInFifthCell.length; i++) { - let currentDiv = divsInFifthCell[i]; - let imgDiv = currentDiv.getElementsByTagName('div')[0]; - let dataDiv = currentDiv.getElementsByTagName('div')[1]; - - if (dataDiv) { - let skuText = dataDiv.innerHTML.match(/SKU:\s*([A-Za-z0-9-]+)/); - - if (skuText && skuText[1]) { - getProductData(skuText[1], domain).then(data => { - if (!data) { - console.log('Product not found:', skuText[1]); - return; - } - - console.log('Product found:', skuText[1]); - imgDiv.innerHTML = ''; - const imgElement = makeImg(data); - imgDiv.appendChild(imgElement); - - imgElement.addEventListener('mouseover', function() { - showLargeImage(data, imgElement); - }); - - imgElement.addEventListener('mouseout', function() { - hideLargeImage(); - }); - }); - } - } - } - } - } - } - } -} - -function makeImg(src = 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png') { - const img = document.createElement('img'); - img.src = src; - img.alt = 'image'; - img.className = 'img-fluid center-block'; - img.style.maxWidth = '48px'; - img.style.maxHeight = '48px'; - - return img; -} - -function showLargeImage(src, imgElement) { - const largeImg = document.createElement('img'); - largeImg.src = src; - largeImg.style.position = 'absolute'; - largeImg.style.maxWidth = '400px'; - largeImg.style.maxHeight = '400px'; - largeImg.style.border = '1px solid #ccc'; - largeImg.style.background = '#fff'; - largeImg.style.zIndex = '1000'; - largeImg.style.top = imgElement.getBoundingClientRect().top + window.scrollY + 'px'; - largeImg.style.left = imgElement.getBoundingClientRect().left + imgElement.offsetWidth + 10 + 'px'; - largeImg.id = 'largeImagePreview'; - - document.body.appendChild(largeImg); -} - -function hideLargeImage() { - const largeImg = document.getElementById('largeImagePreview'); - if (largeImg) { - largeImg.remove(); - } -} - -async function getProductData(sku, domain) { - let url; - if (domain === 'marianek.pl') { - url = `https://marianek.pl/api/v1/product.php?sku=${sku}`; - } else if (domain === 'pomysloweprezenty.pl') { - url = `https://pomysloweprezenty.pl/api/v1/product.php?sku=${sku}`; - } else { - console.error('Unsupported domain:', domain); - return null; - } - - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); console.log(data); - return data.img; - } catch (error) { - console.error('Error fetching product data: ' + url, error); - return null; - } -} - -const currentUrl2 = window.location.href; -const pattern = /^https:\/\/projectpro\.apilo\.com\/warehouse\/shipment\/new-for-order\/.+/; - -if (pattern.test(currentUrl2)) { - const portletBody = document.querySelector('.kt-portlet__body'); - if (portletBody) { - const buttonContainer = document.createElement('div'); - buttonContainer.className = 'custom-button-container'; - buttonContainer.style.display = 'flex'; - buttonContainer.style.gap = '10px'; - buttonContainer.style.marginBottom = '15px'; - portletBody.parentNode.insertBefore(buttonContainer, portletBody); - - const buttonP2D = createButton('Inpost P2D', '#007bff', 'P2D.inpost', 'RZE14N||RZE14N'); - const buttonD2D = createButton('Inpost D2D', '#ff7800', 'D2D.inpostkurier'); - const buttonD2P = createButton('Inpost D2P', '#28a745', 'D2P.inpost'); - const buttonP2P = createButton('Inpost P2P', '#ffc107', 'P2P.inpost', 'RZE14N||RZE14N'); - - buttonContainer.appendChild(buttonP2D); - buttonContainer.appendChild(buttonD2D); - buttonContainer.appendChild(buttonD2P); - buttonContainer.appendChild(buttonP2P); - } -} - -function createButton(text, backgroundColor, method, dropoffPoint = null) { - const button = document.createElement('button'); - button.textContent = text; - button.style.background = backgroundColor; - button.style.display = 'inline-flex'; - button.style.width = '200px'; - button.style.height = '40px'; - button.style.alignItems = 'center'; - button.style.justifyContent = 'center'; - button.style.color = '#FFF'; - button.style.border = 'none'; - button.style.cursor = 'pointer'; - - button.addEventListener('click', () => handleShipment(method, dropoffPoint)); - - return button; -} - -async function handleShipment(method, dropoffPoint = null) { - try { - await selectPackageType(); - await setShipmentMethod(method); - if (dropoffPoint) { - await setDropoffPoint(dropoffPoint); - } - await setParcelWeight('1'); - await submitShipment(); - } catch (error) { - console.error('Wystąpił błąd: ', error); - } -} - -function retryUntilSuccess(fn, interval = 500, retries = 10) { - return new Promise((resolve, reject) => { - const attempt = async () => { - try { - const result = await fn(); - resolve(result); - } catch (err) { - if (retries === 0) { - reject(err); - } else { - setTimeout(() => { - retries--; - attempt(); - }, interval); - } - } - }; - attempt(); - }); -} - -function selectPackageType() { - return retryUntilSuccess(() => { - return new Promise((resolve, reject) => { - const selectElement = document.getElementById('warehousebundle_shipment_packageType'); - if (selectElement) { - selectElement.value = 'package'; - const event = document.createEvent('HTMLEvents'); - event.initEvent('change', true, false); - selectElement.dispatchEvent(event); - resolve(); - } else { - reject('Nie znaleziono elementu packageType'); - } - }); - }); -} - -function setShipmentMethod(method) { - return retryUntilSuccess(() => { - return new Promise((resolve, reject) => { - const methodElement = document.getElementById('warehousebundle_shipment_method'); - if (methodElement) { - methodElement.value = method; - const methodEvent = document.createEvent('HTMLEvents'); - methodEvent.initEvent('change', true, false); - methodElement.dispatchEvent(methodEvent); - resolve(); - } else { - reject('Nie znaleziono elementu shipment_method'); - } - }); - }); -} - -function setDropoffPoint(dropoffPoint) { - return retryUntilSuccess(() => { - return new Promise((resolve, reject) => { - const dropoffPointElement = document.getElementById('warehousebundle_shipment_preferences_dropoffPoint'); - if (dropoffPointElement) { - dropoffPointElement.value = dropoffPoint; - const dropoffPointEvent = document.createEvent('HTMLEvents'); - dropoffPointEvent.initEvent('change', true, false); - dropoffPointElement.dispatchEvent(dropoffPointEvent); - resolve(); - } else { - reject('Nie znaleziono elementu dropoffPoint'); - } - }); - }); -} - -function setParcelWeight(weight) { - return retryUntilSuccess(() => { - return new Promise((resolve, reject) => { - const weightElement = document.getElementById('warehousebundle_shipment_shipmentParcels_0_weight'); - if (weightElement) { - weightElement.value = weight; - const weightEvent = document.createEvent('HTMLEvents'); - weightEvent.initEvent('change', true, false); - weightElement.dispatchEvent(weightEvent); - resolve(); - } else { - reject('Nie znaleziono elementu shipmentParcels_0_weight'); - } - }); - }); -} - -function submitShipment() { - return retryUntilSuccess(() => { - return new Promise((resolve, reject) => { - const submitButton = document.getElementById('warehousebundle_shipment_buttons_submit'); - if (submitButton) { - submitButton.click(); - resolve(); - } else { - reject('Nie znaleziono przycisku submit'); - } - }); - }); -} \ No newline at end of file diff --git a/autoload/admin/factory/class.ShopProduct.php b/autoload/admin/factory/class.ShopProduct.php index d07164f..e8c2522 100644 --- a/autoload/admin/factory/class.ShopProduct.php +++ b/autoload/admin/factory/class.ShopProduct.php @@ -3,6 +3,38 @@ namespace admin\factory; use shop\Product; class ShopProduct { + private static function seoLinkUsedByOtherProduct( int $product_id, string $lang_id, string $seo_link ): bool + { + global $mdb; + + if ( !$seo_link ) + return false; + + return (bool) $mdb -> 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 ) { @@ -1010,18 +1042,30 @@ class ShopProduct if ( $new_seo_link !== $current_seo_link and $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 ( $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 ) ) { - 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 ] ); - else + 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 ) ) + 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', [ 'product_id' => $product_id, 'lang_id' => $lg['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', [ diff --git a/autoload/class.S.php b/autoload/class.S.php index c6aac6f..67e0aa3 100644 --- a/autoload/class.S.php +++ b/autoload/class.S.php @@ -49,46 +49,58 @@ class S return $parts; } - static function canAddRedirect( $from, $to ) + static function canAddRedirect( $from, $to, $lang_id = null ) { global $mdb; - $redirects = $mdb -> select( 'pp_redirects', '*' ); + if ( !$from or !$to or $from === $to ) + return false; + + $where = []; + if ( null !== $lang_id ) + $where['lang_id'] = $lang_id; + + $redirects = $mdb -> select( 'pp_redirects', [ 'from', 'to' ], $where ); $redirectMap = []; foreach ( $redirects as $redirect ) { - $redirectMap[$redirect['from']] = $redirect['to']; + if ( !isset( $redirectMap[$redirect['from']] ) ) + $redirectMap[$redirect['from']] = []; + + if ( !in_array( $redirect['to'], $redirectMap[$redirect['from']], true ) ) + $redirectMap[$redirect['from']][] = $redirect['to']; } - // Dodaj nowe przekierowanie do mapy tymczasowo - $redirectMap[$from] = $to; + if ( !isset( $redirectMap[$from] ) ) + $redirectMap[$from] = []; + + if ( !in_array( $to, $redirectMap[$from], true ) ) + $redirectMap[$from][] = $to; - // Funkcja do sprawdzania cyklu za pomocą DFS $visited = []; - $stack = []; - - function hasCycle($current, $target, &$redirectMap, &$visited) + $stack = [ $to ]; + while ( !empty( $stack ) ) { - if ($current === $target) { - return true; - } + $current = array_pop( $stack ); - if (isset($visited[$current])) { - return false; - } + if ( $current === $from ) + return false; + + if ( isset( $visited[$current] ) ) + continue; $visited[$current] = true; - if (isset($redirectMap[$current])) { - return hasCycle($redirectMap[$current], $target, $redirectMap, $visited); + if ( isset( $redirectMap[$current] ) ) + { + foreach ( $redirectMap[$current] as $next ) + if ( !isset( $visited[$next] ) ) + $stack[] = $next; } - - return false; } - // Sprawdź, czy istnieje ścieżka z $newTo do $newFrom - return !hasCycle($to, $from, $redirectMap, $visited); + return true; } static public function clear_redis_cache() @@ -1113,3 +1125,4 @@ class S return false; } } + diff --git a/autoload/front/factory/class.Layouts.php b/autoload/front/factory/class.Layouts.php index c23d930..5454e2f 100644 --- a/autoload/front/factory/class.Layouts.php +++ b/autoload/front/factory/class.Layouts.php @@ -8,6 +8,29 @@ class Layouts return $mdb -> get( 'pp_layouts', 'id', [ 'categories_default' => 1 ] ); } + static public function default_layout() + { + global $mdb; + + $cacheHandler = new \CacheHandler(); + $cacheKey = "\front\factory\Layouts::default_layout"; + + $objectData = $cacheHandler -> get( $cacheKey ); + if ( $objectData ) + { + $cachedLayout = @unserialize( $objectData ); + if ( is_array( $cachedLayout ) and !empty( $cachedLayout ) ) + return $cachedLayout; + + $cacheHandler -> delete( $cacheKey ); + } + + $layout = $mdb -> get( 'pp_layouts', '*', [ 'status' => 1 ] ); + $cacheHandler -> set( $cacheKey, $layout ); + + return $layout; + } + static public function product_layout( $product_id ) { global $mdb; @@ -16,18 +39,47 @@ class Layouts $cacheKey = "\front\factory\Layouts::product_layout:$product_id"; $objectData = $cacheHandler -> get( $cacheKey ); - - if ( !$objectData ) + if ( $objectData ) { - $layout = $mdb -> get( 'pp_layouts', [ '[><]pp_shop_products' => [ 'id' => 'layout_id' ] ], '*', [ 'pp_shop_products.id' => (int)$product_id ] ); + $cachedLayout = @unserialize( $objectData ); + if ( is_array( $cachedLayout ) and !empty( $cachedLayout ) ) + return $cachedLayout; - $cacheHandler -> set( $cacheKey, $layout ); + $cacheHandler -> delete( $cacheKey ); } + + $layoutRows = $mdb -> query( + "SELECT pp_layouts.* + FROM pp_layouts + JOIN pp_shop_products ON pp_layouts.id = pp_shop_products.layout_id + WHERE pp_shop_products.id = " . (int)$product_id . " + ORDER BY pp_layouts.id DESC" + ) -> fetchAll( \PDO::FETCH_ASSOC ); + + if ( is_array( $layoutRows ) and isset( $layoutRows[0] ) ) + $layout = $layoutRows[0]; else { - return unserialize( $objectData ); + $layoutRows = $mdb -> query( + "SELECT pp_layouts.* + FROM pp_layouts + JOIN pp_layouts_categories ON pp_layouts.id = pp_layouts_categories.layout_id + JOIN pp_shop_products_categories ON pp_shop_products_categories.category_id = pp_layouts_categories.category_id + WHERE pp_shop_products_categories.product_id = " . (int)$product_id . " + ORDER BY pp_shop_products_categories.o ASC, pp_layouts.id DESC" + ) -> fetchAll( \PDO::FETCH_ASSOC ); + + if ( is_array( $layoutRows ) and isset( $layoutRows[0] ) ) + $layout = $layoutRows[0]; + else + $layout = $mdb -> get( 'pp_layouts', '*', [ 'categories_default' => 1 ] ); } + if ( !$layout ) + $layout = $mdb -> get( 'pp_layouts', '*', [ 'status' => 1 ] ); + + $cacheHandler -> set( $cacheKey, $layout ); + return $layout; } @@ -62,21 +114,34 @@ class Layouts $cacheKey = "\front\factory\Layouts::category_layout:$category_id"; $objectData = $cacheHandler -> get( $cacheKey ); - - if ( !$objectData ) + if ( $objectData ) { - $layout = $mdb -> query( "SELECT pp_layouts.* FROM pp_layouts JOIN pp_layouts_categories ON pp_layouts.id = pp_layouts_categories.layout_id WHERE pp_layouts_categories.category_id = " . (int)$category_id ) -> fetchAll( \PDO::FETCH_ASSOC ); - if ( !$layout ) - $layout = $mdb -> get( 'pp_layouts', '*', [ 'categories_default' => 1 ] ); + $cachedLayout = @unserialize( $objectData ); + if ( is_array( $cachedLayout ) and !empty( $cachedLayout ) ) + return $cachedLayout; - $cacheHandler -> set( $cacheKey, $layout[0] ); + $cacheHandler -> delete( $cacheKey ); } + + $layoutRows = $mdb -> query( + "SELECT pp_layouts.* + FROM pp_layouts + JOIN pp_layouts_categories ON pp_layouts.id = pp_layouts_categories.layout_id + WHERE pp_layouts_categories.category_id = " . (int)$category_id . " + ORDER BY pp_layouts.id DESC" + ) -> fetchAll( \PDO::FETCH_ASSOC ); + + if ( is_array( $layoutRows ) and isset( $layoutRows[0] ) ) + $layout = $layoutRows[0]; else - { - return unserialize( $objectData ); - } + $layout = $mdb -> get( 'pp_layouts', '*', [ 'categories_default' => 1 ] ); - return $layout[0]; + if ( !$layout ) + $layout = $mdb -> get( 'pp_layouts', '*', [ 'status' => 1 ] ); + + $cacheHandler -> set( $cacheKey, $layout ); + + return $layout; } static public function active_layout( $page_id ) diff --git a/autoload/front/view/class.Site.php b/autoload/front/view/class.Site.php index d48388a..7d9d677 100644 --- a/autoload/front/view/class.Site.php +++ b/autoload/front/view/class.Site.php @@ -34,6 +34,9 @@ class Site if ( \S::get( 'category' ) ) $layout = \front\factory\Layouts::category_layout( \S::get( 'category' ) ); + if ( !$layout and \S::get( 'module' ) ) + $layout = \front\factory\Layouts::default_layout(); + if ( !$layout ) $layout = \front\factory\Layouts::active_layout( $page['id'] ); diff --git a/autoload/shop/class.Basket.php b/autoload/shop/class.Basket.php index 58de358..8f1a675 100644 --- a/autoload/shop/class.Basket.php +++ b/autoload/shop/class.Basket.php @@ -23,7 +23,27 @@ class Basket implements \ArrayAccess foreach ( $basket as $key => $val ) { - $quantity_options = \shop\Product::get_product_permutation_quantity_options( $val['parent_id'] ? $val['parent_id'] : $val['product-id'], $val['attributes'][0] ); + $permutation = null; + + if ( isset( $val['parent_id'] ) and (int)$val['parent_id'] and isset( $val['product-id'] ) ) + $permutation = \shop\Product::get_product_permutation_hash( (int)$val['product-id'] ); + + if ( !$permutation and isset( $val['attributes'] ) and is_array( $val['attributes'] ) and count( $val['attributes'] ) ) + $permutation = implode( '|', $val['attributes'] ); + + $quantity_options = \shop\Product::get_product_permutation_quantity_options( + $val['parent_id'] ? $val['parent_id'] : $val['product-id'], + $permutation + ); + + if ( + (int)$basket[ $key ][ 'quantity' ] < 1 + and ( (int)$quantity_options['quantity'] > 0 or (int)$quantity_options['stock_0_buy'] === 1 ) + ) + { + $basket[ $key ][ 'quantity' ] = 1; + $result = true; + } if ( ( $val[ 'quantity' ] > $quantity_options['quantity'] ) and !$quantity_options['stock_0_buy'] ) { diff --git a/autoload/shop/class.Product.php b/autoload/shop/class.Product.php index 0e46e6d..770e55e 100644 --- a/autoload/shop/class.Product.php +++ b/autoload/shop/class.Product.php @@ -537,7 +537,7 @@ class Product implements \ArrayAccess global $mdb, $settings; $cacheHandler = new \CacheHandler(); - $cacheKey = "\shop\Product::get_product_permutation_quantity_options:$product_id:$permutation"; + $cacheKey = "\shop\Product::get_product_permutation_quantity_options:v2:$product_id:$permutation"; $objectData = $cacheHandler -> get( $cacheKey ); @@ -546,10 +546,15 @@ class Product implements \ArrayAccess if ( $mdb -> count( 'pp_shop_products', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] ) ) { $result['quantity'] = $mdb -> get( 'pp_shop_products', 'quantity', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] ); + $result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] ); + if ( $result['quantity'] == null ) { $result['quantity'] = $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] ); - $result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $product_id] ); + + if ( $result['stock_0_buy'] == null ) + $result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $product_id] ); + $result['messages'] = $mdb -> get( 'pp_shop_products_langs', [ 'warehouse_message_zero', 'warehouse_message_nonzero' ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => 'pl' ] ] ); if ( !$result['messages']['warehouse_message_zero'] ) @@ -560,7 +565,6 @@ class Product implements \ArrayAccess } else { - $result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] ); $result['messages'] = $mdb -> get( 'pp_shop_products_langs', [ 'warehouse_message_zero', 'warehouse_message_nonzero' ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => 'pl' ] ] ); if ( !$result['messages']['warehouse_message_zero'] ) @@ -758,6 +762,13 @@ class Product implements \ArrayAccess return $mdb -> get( 'pp_shop_products', 'id', [ 'AND' => [ 'parent_id' => $parent_id, 'permutation_hash' => implode( '|', $attributes ) ] ] ); } + // pobranie permutation_hash dla kombinacji produktu + static public function get_product_permutation_hash( int $product_id ) + { + global $mdb; + return $mdb -> get( 'pp_shop_products', 'permutation_hash', [ 'id' => $product_id ] ); + } + // pobranie listy atrybutów z wybranymi wartościami static public function get_product_attributes( $products ) { diff --git a/geocode-cache.php b/geocode-cache.php deleted file mode 100644 index 0ebb528..0000000 --- a/geocode-cache.php +++ /dev/null @@ -1,211 +0,0 @@ - $raw, - 'norm' => $norm, - 'hash' => hash('sha256', $norm), - 'mode' => $mode, - ]; -} - -// DB -try { - $pdo = new PDO(DB_DSN, DB_USER, DB_PASS, [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ]); -} catch (Throwable $e) { - http_response_code(500); - echo json_encode(['error' => 'DB connection failed']); - exit; -} - -// Wejście -$q = isset($_GET['q']) ? trim((string)$_GET['q']) : ''; -$lat = isset($_GET['lat']) ? (float)$_GET['lat'] : null; -$lng = isset($_GET['lng']) ? (float)$_GET['lng'] : null; - -if ($q === '' && ($lat === null || $lng === null)) { - http_response_code(400); - echo json_encode(['error' => 'Provide q=address or lat & lng']); - exit; -} - -$key = build_cache_key($q, $lat, $lng); -$now = now_mysql(); - -// ===== 1) CACHE LOOKUP (forward i reverse) ===== -$stmt = $pdo->prepare("SELECT * FROM geocode_cache WHERE query_hash = :h AND (expires_at IS NULL OR expires_at > NOW()) LIMIT 1"); -$stmt->execute([':h' => $key['hash']]); -if ($row = $stmt->fetch()) { - $pdo->prepare("UPDATE geocode_cache SET hits = hits + 1, updated_at = NOW() WHERE id = :id")->execute([':id' => $row['id']]); - echo json_encode([ - 'from_cache' => true, - 'source' => 'cache', - 'stale' => false, - 'lat' => (float)$row['lat'], - 'lng' => (float)$row['lng'], - 'formatted_address' => $row['formatted_address'], - 'place_id' => $row['place_id'], - 'provider' => $row['provider'], - ], JSON_UNESCAPED_UNICODE); - exit; -} - -// ===== 2) PYTANIE DO GOOGLE ===== -function google_request(array $params): array { - $base = 'https://maps.googleapis.com/maps/api/geocode/json'; - $params['key'] = GOOGLE_GEOCODE_KEY; - $params += ['language' => GOOGLE_LANGUAGE, 'region' => GOOGLE_REGION]; - $url = $base . '?' . http_build_query($params); - - $ch = curl_init($url); - curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_CONNECTTIMEOUT => 3]); - $res = curl_exec($ch); $err = curl_error($ch); curl_close($ch); - if ($res === false) throw new RuntimeException('cURL error: ' . $err); - $data = json_decode($res, true); - if (!is_array($data)) throw new RuntimeException('Invalid JSON from Google'); - if (($data['status'] ?? '') !== 'OK' || empty($data['results'][0])) { - $status = $data['status'] ?? 'UNKNOWN'; - throw new RuntimeException('Geocoding failed: ' . $status); - } - return $data; -} - -try { - $data = ($key['mode'] === 'forward') - ? google_request(['address' => $q]) - : google_request(['latlng' => $lat . ',' . $lng]); - - $top = $data['results'][0]; - $latV = (float)$top['geometry']['location']['lat']; - $lngV = (float)$top['geometry']['location']['lng']; - $fmt = (string)($top['formatted_address'] ?? ''); - $pid = (string)($top['place_id'] ?? ''); - - // ===== 3) ZAPIS DO CACHE (forward i reverse) ===== - $ttlDays = ($key['mode'] === 'forward') ? CACHE_TTL_DAYS_FORWARD : CACHE_TTL_DAYS_REVERSE; - $expires = add_days($now, $ttlDays); - - $stmt = $pdo->prepare("INSERT INTO geocode_cache - (query_hash, query_raw, query_norm, lat, lng, formatted_address, place_id, provider, hits, created_at, updated_at, expires_at, raw_json) - VALUES (:h, :raw, :norm, :lat, :lng, :fmt, :pid, 'google', 1, :now, :now, :exp, :json) - ON DUPLICATE KEY UPDATE - lat = VALUES(lat), - lng = VALUES(lng), - formatted_address = VALUES(formatted_address), - place_id = VALUES(place_id), - updated_at = VALUES(updated_at), - expires_at = VALUES(expires_at), - raw_json = VALUES(raw_json), - hits = hits + 1"); - - $stmt->execute([ - ':h' => $key['hash'], - ':raw' => $key['raw'], - ':norm'=> $key['norm'], - ':lat' => $latV, - ':lng' => $lngV, - ':fmt' => $fmt, - ':pid' => $pid, - ':now' => $now, - ':exp' => $expires, - ':json'=> json_encode($data, JSON_UNESCAPED_UNICODE), - ]); - - echo json_encode([ - 'from_cache' => false, - 'source' => 'google', - 'stale' => false, - 'lat' => $latV, - 'lng' => $lngV, - 'formatted_address' => $fmt, - 'place_id' => $pid, - 'provider' => 'google', - ], JSON_UNESCAPED_UNICODE); - exit; - -} catch (Throwable $e) { - // ===== 4) Fallback: zwróć najświeższe co mamy (też dla reverse) ===== - try { - $stmt = $pdo->prepare("SELECT * FROM geocode_cache WHERE query_hash = :h LIMIT 1"); - $stmt->execute([':h' => $key['hash']]); - if ($row = $stmt->fetch()) { - http_response_code(200); - echo json_encode([ - 'from_cache' => true, - 'source' => 'cache', - 'stale' => true, - 'lat' => (float)$row['lat'], - 'lng' => (float)$row['lng'], - 'formatted_address' => $row['formatted_address'], - 'place_id' => $row['place_id'], - 'provider' => $row['provider'], - ], JSON_UNESCAPED_UNICODE); - exit; - } - } catch (Throwable $ignored) {} - - http_response_code(502); - echo json_encode(['error' => $e->getMessage()]); - exit; -} \ No newline at end of file diff --git a/templates/shop-basket/basket-details.php b/templates/shop-basket/basket-details.php index cc98a12..4e2649c 100644 --- a/templates/shop-basket/basket-details.php +++ b/templates/shop-basket/basket-details.php @@ -7,6 +7,22 @@ unset( $price ); unset( $price_new ); $product = \shop\Product::getFromCache( (int)$position['product-id'], $this -> lang_id ); + + $permutation = null; + if ( isset( $position['parent_id'] ) and (int)$position['parent_id'] and isset( $position['product-id'] ) ) + $permutation = \shop\Product::get_product_permutation_hash( (int)$position['product-id'] ); + + if ( !$permutation and isset( $position['attributes'] ) and is_array( $position['attributes'] ) and count( $position['attributes'] ) ) + $permutation = implode( '|', $position['attributes'] ); + + $quantity_options = \shop\Product::get_product_permutation_quantity_options( + (int)( $position['parent_id'] ? $position['parent_id'] : $position['product-id'] ), + $permutation + ); + + $max_quantity = (int)$quantity_options['quantity']; + if ( !$max_quantity and (int)$quantity_options['stock_0_buy'] ) + $max_quantity = 999; ?>
@@ -72,7 +88,7 @@ - + @@ -111,4 +127,4 @@
Brak produktów w koszyku
-
\ No newline at end of file + diff --git a/templates/shop-product/product.php b/templates/shop-product/product.php index 8077c7d..35cc314 100644 --- a/templates/shop-product/product.php +++ b/templates/shop-product/product.php @@ -321,17 +321,17 @@ }); }; - if ( $( '#product #tab-0' ).visible() ) + if ( $( '#product #tab-0' ).is( ':visible' ) ) $( '#product #tabs-menu #tab-link-0' ).addClass( 'current' ); else $( '#product #tabs-menu #tab-link-0' ).removeClass( 'current' ); - if ( $( '#product #tab-1' ).visible() ) + if ( $( '#product #tab-1' ).is( ':visible' ) ) $( '#product #tabs-menu #tab-link-1' ).addClass( 'current' ); else $( '#product #tabs-menu #tab-link-1' ).removeClass( 'current' ); - if ( $( '#product #tab-2' ).visible() ) + if ( $( '#product #tab-2' ).is( ':visible' ) ) $( '#product #tabs-menu #tab-link-2' ).addClass( 'current' ); else $( '#product #tabs-menu #tab-link-2' ).removeClass( 'current' ); diff --git a/updates/0.20/ver_0.267.zip b/updates/0.20/ver_0.267.zip new file mode 100644 index 0000000000000000000000000000000000000000..c90bfc87626a4437a62cd5cc87819fc8ff7c71e6 GIT binary patch literal 40517 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 PJX|La`&>Bn&)a_hIgcng literal 0 HcmV?d00001 diff --git a/updates/0.20/ver_0.267_files.txt b/updates/0.20/ver_0.267_files.txt new file mode 100644 index 0000000..6f2e124 --- /dev/null +++ b/updates/0.20/ver_0.267_files.txt @@ -0,0 +1,2 @@ +F: ../apilo-bck +F: ../geocode-cache.php diff --git a/updates/0.20/ver_0.267_sql.txt b/updates/0.20/ver_0.267_sql.txt new file mode 100644 index 0000000..0dcc48c --- /dev/null +++ b/updates/0.20/ver_0.267_sql.txt @@ -0,0 +1,11 @@ +DELETE r1 FROM pp_redirects r1 +INNER JOIN pp_redirects r2 + ON r1.`from` = r2.`from` + AND (r1.lang_id <=> r2.lang_id) + AND ( + IFNULL(r1.date_add, '1000-01-01 00:00:00') < IFNULL(r2.date_add, '1000-01-01 00:00:00') + OR ( + IFNULL(r1.date_add, '1000-01-01 00:00:00') = IFNULL(r2.date_add, '1000-01-01 00:00:00') + AND r1.id < r2.id + ) + ); diff --git a/updates/changelog.php b/updates/changelog.php index e2098d7..ba595e9 100644 --- a/updates/changelog.php +++ b/updates/changelog.php @@ -1,3 +1,13 @@ +ver. 0.267 - 13.02.2026
+- FIX - front: poprawione dobieranie layoutu dla kategorii/produktu/koszyka i innych stron modułowych (fallback do layoutu domyślnego) +- FIX - produkt/koszyk: poprawiona obsługa ilości dla kombinacji (stan 0 po dodaniu do koszyka, limit max, odczyt `stock_0_buy`) +- FIX - produkt: usunięty błąd JS `TypeError: $(...).visible is not a function` (zamiana na `:visible`) +- FIX - SEO redirecty produktów: blokada konfliktów po kopiowaniu URL oraz utwardzone wykrywanie pętli redirectów (`lang_id` + graf przejść) +- UPDATE - admin: `input-switch` zapisuje wartość `on` (spójnie z obsługą pól checkbox w formularzach) +- CLEANUP - usunięte pliki: `apilo-bck`, `geocode-cache.php` +- UPDATE - testy: `OK (235 tests, 682 assertions)` +- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.267.zip`, `ver_0.267_files.txt`, `ver_0.267_sql.txt` +
ver. 0.266 - 13.02.2026
- NEW - migracja modulu `ShopCoupon` do architektury Domain + DI (`Domain\Coupon\CouponRepository`, `admin\Controllers\ShopCouponController`) - UPDATE - modul `/admin/shop_coupon/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit` @@ -462,4 +472,3 @@ - FIX - poprawa adresu strony głównej - diff --git a/updates/versions.php b/updates/versions.php index 3221827..4a343b3 100644 --- a/updates/versions.php +++ b/updates/versions.php @@ -1,5 +1,5 @@