From 42e4396064bb46ec99b2c65bee40efb0c3409785 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Thu, 12 Feb 2026 23:54:56 +0100 Subject: [PATCH] Refactor Scontainers management - Removed legacy Scontainers controller and view files, transitioning to a new controller structure. - Introduced ScontainersController to handle CRUD operations with improved dependency injection. - Created ScontainersRepository for database interactions, encapsulating logic for container management. - Updated container edit and list views to utilize new templating system. - Added unit tests for ScontainersRepository and ScontainersController to ensure functionality. - Enhanced form handling for container editing, including validation and error management. --- .phpunit.result.cache | 2 +- AGENTS.md | 5 + .../templates/scontainers/container-edit.php | 124 +------ .../templates/scontainers/containers-list.php | 80 +---- .../Scontainers/ScontainersRepository.php | 311 ++++++++++++++++++ .../Controllers/ScontainersController.php | 297 +++++++++++++++++ autoload/admin/controls/class.Pages.php | 19 +- autoload/admin/controls/class.Scontainers.php | 40 --- autoload/admin/controls/class.ShopProduct.php | 19 +- autoload/admin/factory/class.Scontainers.php | 92 +----- autoload/admin/view/class.Scontainers.php | 20 -- autoload/front/factory/class.Scontainers.php | 36 +- .../Scontainers/ScontainersRepositoryTest.php | 63 ++++ .../Controllers/ScontainersControllerTest.php | 59 ++++ updates/0.20/ver_0.259_files.txt | 2 + 15 files changed, 816 insertions(+), 353 deletions(-) create mode 100644 autoload/Domain/Scontainers/ScontainersRepository.php create mode 100644 autoload/admin/Controllers/ScontainersController.php delete mode 100644 autoload/admin/controls/class.Scontainers.php delete mode 100644 autoload/admin/view/class.Scontainers.php create mode 100644 tests/Unit/Domain/Scontainers/ScontainersRepositoryTest.php create mode 100644 tests/Unit/admin/Controllers/ScontainersControllerTest.php create mode 100644 updates/0.20/ver_0.259_files.txt diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 80a3900..1d17c8a 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.002,"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.004,"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.002,"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.001,"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.002,"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.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveReturnsZeroWhenInsertFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":0.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.001}} \ 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.002,"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.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveReturnsZeroWhenInsertFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveSetsStatusToMinusOne":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveReturnsFalseWhenUpdateFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderUpdatesImageOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderSkipsEmptyValues":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasBrowseListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasGalleryOrderSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testBrowseListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testGalleryOrderSaveMethodReturnType":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminUsesBoundParamsForTitleFilter":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveWithLegacyFormat":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveUpdatesExistingTranslationsByBannerAndLang":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testListForAdminIncludesThumbnailSrc":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testRestoreSetsStatusToZero":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeletePermanentlyRemovesArticleAndRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListArchivedForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsUnitWithTranslations":0.001,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsNullWhenUnitNotFound":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testSaveInsertsNewUnitAndTranslationsForStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testDeleteRemovesUnitAndTranslations":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdReturnsTextFromDatabase":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdSupportsStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testAllUnitsReturnsArrayIndexedById":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsArrayOrNull":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveLanguageRejectsInvalidLanguageId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveTranslationInsertsNewTranslationAndReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDeleteTranslationReturnsBoolean":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdReturnsLanguageWithStartFlag":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdFallsBackToFirstLanguageOrPl":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsLayoutWithRelations":0.001,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testDeleteReturnsFalseWhenOnlyOneLayoutExists":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsDefaultLayoutWhenRecordDoesNotExist":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testSaveInsertsNewLayoutAndReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testListAllReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsNullForInvalidId":0.002,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSaveSettingsUpdatesHeaderAndFooter":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testDeleteTemplateReturnsFalseForAdminTemplate":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateByNameReturnsText":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsDefaultContainerForInvalidId":0.001,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsContainerWithTranslations":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDetailsForLanguageReturnsNullForInvalidData":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserWhenExists":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsErrorWhenLoginIsTaken":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsOkWhenAvailable":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveCreatesUserWithNormalizedSwitches":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithoutPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrue":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsFalseOnFailure":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsUserByLogin":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsSuccessForValidCredentials":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsNegativeOneForBlockedUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseAfterMaxAttempts":0.078,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.079,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.158,"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}} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 0f57855..730c8be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,3 +24,8 @@ Przed rozpoczęciem implementacji sprawdź aktualną zawartość: - `TESTING.md` To ma pomóc zachować spójność zmian i dokumentacji. + + +## INNE + +Przejdźmy teraz do refaktoringu wszystkiego co związane z https://shoppro.project-dc.pl/admin/articles_archive/, nowe widoki, klasy (usuwanie starych), poprawa routingu, przeszukanie innych klas pod względem zależności. Zapisz plan a później realizuj krok po kroku. \ No newline at end of file diff --git a/admin/templates/scontainers/container-edit.php b/admin/templates/scontainers/container-edit.php index e74e80c..40bf9e9 100644 --- a/admin/templates/scontainers/container-edit.php +++ b/admin/templates/scontainers/container-edit.php @@ -1,123 +1 @@ - - - -
- -
-
-
-
    - languages ) ): foreach ( $this -> languages as $lg ):?> - -
  • - - -
-
- languages ) ): foreach ( $this -> languages as $lg ):?> - -
- 'Tytuł', - 'name' => 'title[' . $lg['id'] . ']', - 'id' => 'title_' . $lg['id'], - 'value' => $this -> container['languages'][ $lg['id'] ]['title'], - 'inline' => true - ) - );?> - 'Treść', - 'name' => 'text[' . $lg['id'] . ']', - 'id' => 'text_' . $lg['id'], - 'value' => $this -> container['languages'][ $lg['id'] ]['text'], - 'inline' => true - ) - );?> - -
- - -
-
-
-
-
- 'Aktywny', - 'name' => 'status', - 'checked' => $this -> container['status'] == 1 or !$this -> container['id'] ? true : false - ) - );?> - 'Pokaż tytuł', - 'name' => 'show_title', - 'checked' => $this -> container['show_title'] == 1 ? true : false - ) - );?> -
-
-
- id = 'container-edit'; -$grid -> gdb_opt = $gdb; -$grid -> include_plugins = true; -$grid -> title = 'Edycja kontenera statycznego'; -$grid -> fields = [ - [ - 'db' => 'id', - 'type' => 'hidden', - 'value' => $this -> container['id'] - ] - ]; -$grid -> actions = [ - 'save' => [ 'url' => '/admin/scontainers/container_save/', 'back_url' => '/admin/scontainers/view_list/' ], - 'cancel' => [ 'url' => '/admin/scontainers/view_list/' ] - ]; -$grid -> external_code = $out; -$grid -> persist_edit = true; -$grid -> id_param = 'id'; - -echo $grid -> draw(); -?> - - \ No newline at end of file + $this->form]); ?> diff --git a/admin/templates/scontainers/containers-list.php b/admin/templates/scontainers/containers-list.php index 0573daa..d2ecb80 100644 --- a/admin/templates/scontainers/containers-list.php +++ b/admin/templates/scontainers/containers-list.php @@ -1,79 +1 @@ - gdb_opt = $gdb; -$grid -> sql = 'SELECT *' - . 'FROM ( ' - . 'SELECT ' - . 'id, status, ' - . '( SELECT title FROM pp_scontainers_langs AS psl, pp_langs AS pl WHERE lang_id = pl.id AND container_id = ps.id AND title != \'\' ORDER BY o ASC LIMIT 1 ) AS title ' - . 'FROM ' - . 'pp_scontainers AS ps ' - . ') AS q1 ' - . 'WHERE ' - . '1=1 [where] ' - . 'ORDER BY ' - . '[order_p1] [order_p2]'; -$grid -> sql_count = 'SELECT ' - . 'COUNT(0) FROM ( ' - . 'SELECT ' - . 'id, status, ' - . '( SELECT title FROM pp_scontainers_langs AS psl, pp_langs AS pl WHERE lang_id = pl.id AND container_id = ps.id AND title != \'\' ORDER BY o ASC LIMIT 1 ) AS title ' - . 'FROM ' - . 'pp_scontainers AS ps ' - . ') AS q1 ' - . 'WHERE ' - . '1=1 [where] '; -$grid -> debug = true; -$grid -> order = [ 'column' => 'id', 'type' => 'DESC' ]; -$grid -> search = [ - [ 'name' => 'Tytuł', 'db' => 'title', 'type' => 'text' ], - [ 'name' => 'Aktywny', 'db' => 'status', 'type' => 'select', 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ] ] - ]; -$grid -> columns_view = [ - [ - 'name' => 'Lp.', - 'th' => [ 'class' => 'g-lp' ], - 'td' => [ 'class' => 'g-center' ], - 'autoincrement' => true - ], - [ - 'name' => 'Tytuł', - 'db' => 'title', - 'php' => 'echo "[title]";', - 'sort' => true - ], - [ - 'name' => 'Kod', - 'php' => 'echo "[KONTENER:[id]]";' - ], - [ - 'name' => 'Aktywny', - 'db' => 'status', - 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ], - 'td' => [ 'class' => 'g-center' ] - ], - [ - 'name' => 'Edytuj', - 'action' => [ 'type' => 'edit', 'url' => '/admin/scontainers/container_edit/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ], - [ - 'name' => 'Usuń', - 'action' => [ 'type' => 'delete', 'url' => '/admin/scontainers/container_delete/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ] - ]; -$grid -> buttons = [ - [ - 'label' => 'Dodaj kontener', - 'url' => '/admin/scontainers/container_edit/', - 'icon' => 'fa-plus-circle', - 'class' => 'btn-success' - ] - ]; -echo $grid -> draw(); \ No newline at end of file + $this->viewModel]); ?> diff --git a/autoload/Domain/Scontainers/ScontainersRepository.php b/autoload/Domain/Scontainers/ScontainersRepository.php new file mode 100644 index 0000000..bc94b33 --- /dev/null +++ b/autoload/Domain/Scontainers/ScontainersRepository.php @@ -0,0 +1,311 @@ +db = $db; + } + + /** + * @return array{items: array>, total: int} + */ + public function listForAdmin( + array $filters, + string $sortColumn = 'id', + string $sortDir = 'DESC', + int $page = 1, + int $perPage = 15 + ): array { + $allowedSortColumns = [ + 'id' => 'q1.id', + 'title' => 'q1.title', + 'status' => 'q1.status', + ]; + + $sortSql = $allowedSortColumns[$sortColumn] ?? 'q1.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 = ['1 = 1']; + $params = []; + + $title = trim((string)($filters['title'] ?? '')); + if ($title !== '') { + if (strlen($title) > 255) { + $title = substr($title, 0, 255); + } + $where[] = 'q1.title LIKE :title'; + $params[':title'] = '%' . $title . '%'; + } + + $status = trim((string)($filters['status'] ?? '')); + if ($status === '0' || $status === '1') { + $where[] = 'q1.status = :status'; + $params[':status'] = (int)$status; + } + + $whereSql = implode(' AND ', $where); + $baseSelect = $this->baseListSelect(); + + $sqlCount = " + SELECT COUNT(0) + FROM ({$baseSelect}) AS q1 + WHERE {$whereSql} + "; + + $stmtCount = $this->db->query($sqlCount, $params); + $countRows = $stmtCount ? $stmtCount->fetchAll() : []; + $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; + + $sql = " + SELECT q1.* + FROM ({$baseSelect}) AS q1 + WHERE {$whereSql} + ORDER BY {$sortSql} {$sortDir}, q1.id DESC + LIMIT {$perPage} OFFSET {$offset} + "; + + $stmt = $this->db->query($sql, $params); + $items = $stmt ? $stmt->fetchAll() : []; + + return [ + 'items' => is_array($items) ? $items : [], + 'total' => $total, + ]; + } + + public function find(int $containerId): array + { + if ($containerId <= 0) { + return $this->defaultContainer(); + } + + $container = $this->db->get('pp_scontainers', '*', ['id' => $containerId]); + if (!is_array($container)) { + return $this->defaultContainer(); + } + + $container['languages'] = $this->translationsMap($containerId); + return $container; + } + + public function detailsForLanguage(int $containerId, string $langId): ?array + { + if ($containerId <= 0 || trim($langId) === '') { + return null; + } + + $container = $this->db->get('pp_scontainers', '*', ['id' => $containerId]); + if (!is_array($container)) { + return null; + } + + $translation = $this->db->get('pp_scontainers_langs', '*', [ + 'AND' => [ + 'container_id' => $containerId, + 'lang_id' => $langId, + ], + ]); + + $container['languages'] = is_array($translation) ? $translation : [ + 'lang_id' => $langId, + 'title' => '', + 'text' => '', + ]; + + return $container; + } + + public function save(array $data): ?int + { + $containerId = (int)($data['id'] ?? 0); + $status = $this->toSwitchValue($data['status'] ?? 0); + $showTitle = $this->toSwitchValue($data['show_title'] ?? 0); + $translations = $this->extractTranslations($data); + + if ($containerId <= 0) { + $this->db->insert('pp_scontainers', [ + 'status' => $status, + 'show_title' => $showTitle, + ]); + $containerId = (int)$this->db->id(); + if ($containerId <= 0) { + return null; + } + } else { + $this->db->update('pp_scontainers', [ + 'status' => $status, + 'show_title' => $showTitle, + ], [ + 'id' => $containerId, + ]); + } + + foreach ($translations as $langId => $row) { + $translationId = $this->db->get('pp_scontainers_langs', 'id', [ + 'AND' => [ + 'container_id' => $containerId, + 'lang_id' => $langId, + ], + ]); + + if ($translationId) { + $this->db->update('pp_scontainers_langs', [ + 'title' => (string)($row['title'] ?? ''), + 'text' => (string)($row['text'] ?? ''), + ], [ + 'id' => (int)$translationId, + ]); + } else { + $this->db->insert('pp_scontainers_langs', [ + 'container_id' => $containerId, + 'lang_id' => $langId, + 'title' => (string)($row['title'] ?? ''), + 'text' => (string)($row['text'] ?? ''), + ]); + } + } + + \S::delete_dir('../temp/'); + $this->clearFrontCache($containerId); + + return $containerId; + } + + public function delete(int $containerId): bool + { + if ($containerId <= 0) { + return false; + } + + $result = (bool)$this->db->delete('pp_scontainers', ['id' => $containerId]); + if ($result) { + $this->clearFrontCache($containerId); + } + + return $result; + } + + private function baseListSelect(): string + { + return " + SELECT + ps.id, + ps.status, + ( + SELECT psl.title + FROM pp_scontainers_langs AS psl + JOIN pp_langs AS pl ON psl.lang_id = pl.id + WHERE psl.container_id = ps.id + AND psl.title <> '' + ORDER BY pl.o ASC + LIMIT 1 + ) AS title + FROM pp_scontainers AS ps + "; + } + + private function clearFrontCache(int $containerId): void + { + if ($containerId <= 0 || !class_exists('\CacheHandler')) { + return; + } + + $cacheHandler = new \CacheHandler(); + $cacheKey = '\front\factory\Scontainers::scontainer_details:' . $containerId; + $cacheHandler->delete($cacheKey); + } + + /** + * @return array> + */ + private function translationsMap(int $containerId): array + { + $rows = $this->db->select('pp_scontainers_langs', '*', ['container_id' => $containerId]); + if (!is_array($rows)) { + return []; + } + + $result = []; + foreach ($rows as $row) { + $langId = (string)($row['lang_id'] ?? ''); + if ($langId !== '') { + $result[$langId] = $row; + } + } + + return $result; + } + + /** + * @return array> + */ + private function extractTranslations(array $data): array + { + $translations = []; + + if (isset($data['translations']) && is_array($data['translations'])) { + foreach ($data['translations'] as $langId => $row) { + if (!is_array($row)) { + continue; + } + + $safeLangId = trim((string)$langId); + if ($safeLangId === '') { + continue; + } + + $translations[$safeLangId] = [ + 'title' => (string)($row['title'] ?? ''), + 'text' => (string)($row['text'] ?? ''), + ]; + } + } + + $legacyTitles = isset($data['title']) && is_array($data['title']) ? $data['title'] : []; + $legacyTexts = isset($data['text']) && is_array($data['text']) ? $data['text'] : []; + + foreach ($legacyTitles as $langId => $title) { + $safeLangId = trim((string)$langId); + if ($safeLangId === '') { + continue; + } + + if (!isset($translations[$safeLangId])) { + $translations[$safeLangId] = [ + 'title' => '', + 'text' => '', + ]; + } + + $translations[$safeLangId]['title'] = (string)$title; + $translations[$safeLangId]['text'] = (string)($legacyTexts[$safeLangId] ?? ''); + } + + return $translations; + } + + private function toSwitchValue($value): int + { + return ($value === 'on' || $value === 1 || $value === '1' || $value === true) ? 1 : 0; + } + + private function defaultContainer(): array + { + return [ + 'id' => 0, + 'status' => 1, + 'show_title' => 0, + 'languages' => [], + ]; + } +} + diff --git a/autoload/admin/Controllers/ScontainersController.php b/autoload/admin/Controllers/ScontainersController.php new file mode 100644 index 0000000..083dfbf --- /dev/null +++ b/autoload/admin/Controllers/ScontainersController.php @@ -0,0 +1,297 @@ +repository = $repository; + $this->languagesRepository = $languagesRepository; + $this->formHandler = new FormRequestHandler(); + } + + public function list(): string + { + $sortableColumns = ['id', 'title', 'status']; + $filterDefinitions = [ + [ + 'key' => 'title', + 'label' => 'Tytul', + 'type' => 'text', + ], + [ + 'key' => 'status', + 'label' => 'Aktywny', + 'type' => 'select', + 'options' => [ + '' => '- aktywny -', + '1' => 'tak', + '0' => 'nie', + ], + ], + ]; + + $listRequest = \admin\Support\TableListRequestFactory::fromRequest( + $filterDefinitions, + $sortableColumns, + 'id' + ); + + $sortDir = $listRequest['sortDir']; + if (trim((string)\S::get('sort')) === '') { + $sortDir = 'DESC'; + } + + $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'] ?? 0); + $title = trim((string)($item['title'] ?? '')); + + $rows[] = [ + 'lp' => $lp++ . '.', + 'title' => '' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '', + 'code' => '[KONTENER:' . $id . ']', + 'status' => ((int)($item['status'] ?? 0) === 1) ? 'tak' : 'nie', + '_actions' => [ + [ + 'label' => 'Edytuj', + 'url' => '/admin/scontainers/container_edit/id=' . $id, + 'class' => 'btn btn-xs btn-primary', + ], + [ + 'label' => 'Usun', + 'url' => '/admin/scontainers/container_delete/id=' . $id, + 'class' => 'btn btn-xs btn-danger', + 'confirm' => 'Na pewno chcesz usunac wybrany kontener?', + ], + ], + ]; + } + + $total = (int)$result['total']; + $totalPages = max(1, (int)ceil($total / $listRequest['perPage'])); + + $viewModel = new PaginatedTableViewModel( + [ + ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false], + ['key' => 'title', 'sort_key' => 'title', 'label' => 'Tytul', 'sortable' => true, 'raw' => true], + ['key' => 'code', 'label' => 'Kod', 'sortable' => false], + ['key' => 'status', 'sort_key' => 'status', 'label' => 'Aktywny', 'class' => 'text-center', 'sortable' => true, 'raw' => 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/scontainers/view_list/', + 'Brak danych w tabeli.', + '/admin/scontainers/container_edit/', + 'Dodaj kontener' + ); + + return \Tpl::view('scontainers/containers-list', [ + 'viewModel' => $viewModel, + ]); + } + + public function view_list(): string + { + return $this->list(); + } + + public function edit(): string + { + $container = $this->repository->find((int)\S::get('id')); + $languages = $this->languagesRepository->languagesList(); + $validationErrors = $_SESSION['form_errors'][$this->formId()] ?? null; + if ($validationErrors) { + unset($_SESSION['form_errors'][$this->formId()]); + } + + return \Tpl::view('scontainers/container-edit', [ + 'form' => $this->buildFormViewModel($container, $languages, $validationErrors), + ]); + } + + public function container_edit(): string + { + return $this->edit(); + } + + public function save(): void + { + $legacyValues = \S::get('values'); + if ($legacyValues) { + $values = json_decode((string)$legacyValues, true); + $response = ['status' => 'error', 'msg' => 'Podczas zapisywania kontenera wystapil blad.']; + + if (is_array($values)) { + $savedId = $this->repository->save($values); + if (!empty($savedId)) { + $response = ['status' => 'ok', 'msg' => 'Kontener zostal zapisany.', 'id' => $savedId]; + } + } + + echo json_encode($response); + exit; + } + + $container = $this->repository->find((int)\S::get('id')); + $languages = $this->languagesRepository->languagesList(); + $form = $this->buildFormViewModel($container, $languages); + + $result = $this->formHandler->handleSubmit($form, $_POST); + if (!$result['success']) { + $_SESSION['form_errors'][$this->formId()] = $result['errors']; + echo json_encode(['success' => false, 'errors' => $result['errors']]); + exit; + } + + $data = $result['data']; + $savedId = $this->repository->save([ + 'id' => (int)($data['id'] ?? 0), + 'status' => $data['status'] ?? 0, + 'show_title' => $data['show_title'] ?? 0, + 'translations' => $data['translations'] ?? [], + ]); + + if ($savedId) { + echo json_encode([ + 'success' => true, + 'id' => $savedId, + 'message' => 'Kontener zostal zapisany.', + ]); + exit; + } + + echo json_encode([ + 'success' => false, + 'errors' => ['general' => 'Podczas zapisywania kontenera wystapil blad.'], + ]); + exit; + } + + public function container_save(): void + { + $this->save(); + } + + public function delete(): void + { + if ($this->repository->delete((int)\S::get('id'))) { + \S::alert('Kontener zostal usuniety.'); + } + + header('Location: /admin/scontainers/view_list/'); + exit; + } + + public function container_delete(): void + { + $this->delete(); + } + + private function buildFormViewModel(array $container, array $languages, ?array $errors = null): FormEditViewModel + { + $id = (int)($container['id'] ?? 0); + $isNew = $id <= 0; + + $data = [ + 'id' => $id, + 'status' => (int)($container['status'] ?? 1), + 'show_title' => (int)($container['show_title'] ?? 0), + 'languages' => is_array($container['languages'] ?? null) ? $container['languages'] : [], + ]; + + $fields = [ + FormField::hidden('id', $id), + FormField::langSection('translations', 'content', [ + FormField::text('title', [ + 'label' => 'Tytul', + ]), + FormField::editor('text', [ + 'label' => 'Tresc', + 'height' => 300, + ]), + ]), + FormField::switch('status', [ + 'label' => 'Aktywny', + 'tab' => 'settings', + 'value' => true, + ]), + FormField::switch('show_title', [ + 'label' => 'Pokaz tytul', + 'tab' => 'settings', + ]), + ]; + + $tabs = [ + new FormTab('content', 'Tresc', 'fa-file'), + new FormTab('settings', 'Ustawienia', 'fa-wrench'), + ]; + + $actionUrl = '/admin/scontainers/container_save/' . ($isNew ? '' : ('id=' . $id)); + $actions = [ + FormAction::save($actionUrl, '/admin/scontainers/view_list/'), + FormAction::cancel('/admin/scontainers/view_list/'), + ]; + + return new FormEditViewModel( + $this->formId(), + 'Edycja kontenera statycznego', + $data, + $fields, + $tabs, + $actions, + 'POST', + $actionUrl, + '/admin/scontainers/view_list/', + true, + [], + $languages, + $errors + ); + } + + private function formId(): string + { + return 'scontainers-container-edit'; + } +} + diff --git a/autoload/admin/controls/class.Pages.php b/autoload/admin/controls/class.Pages.php index afd9d64..80ae118 100644 --- a/autoload/admin/controls/class.Pages.php +++ b/autoload/admin/controls/class.Pages.php @@ -66,11 +66,28 @@ class Pages 'parent_id' => \S::get( 'pid' ), 'menu_id' => \S::get( 'menu_id' ), 'menus' => \admin\factory\Pages::menu_lists(), - 'layouts' => \admin\factory\Layouts::layouts_list(), + 'layouts' => self::layouts_for_page_edit( $GLOBALS['mdb'] ), 'languages' => ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->languagesList() ] ); } + private static function layouts_for_page_edit( $db ) + { + if ( class_exists( '\Domain\Layouts\LayoutsRepository' ) ) + { + $rows = ( new \Domain\Layouts\LayoutsRepository( $db ) ) -> listAll(); + return is_array( $rows ) ? $rows : []; + } + + if ( class_exists( '\admin\factory\Layouts' ) ) + { + $rows = \admin\factory\Layouts::layouts_list(); + return is_array( $rows ) ? $rows : []; + } + + return []; + } + public static function menu_save() { $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania menu wystąpił błąd. Proszę spróbować ponownie.' ]; diff --git a/autoload/admin/controls/class.Scontainers.php b/autoload/admin/controls/class.Scontainers.php deleted file mode 100644 index d06e63e..0000000 --- a/autoload/admin/controls/class.Scontainers.php +++ /dev/null @@ -1,40 +0,0 @@ - 'error', 'msg' => 'Podczas zapisywania kontenera wystąpił błąd. Proszę spróbować ponownie.' ]; - $values = json_decode( \S::get( 'values' ), true ); - - if ( $id = \admin\factory\Scontainers::container_save( $values['id'], $values['title'], $values['text'], $values['status'], $values['show_title'] ) ) - $response = [ 'status' => 'ok', 'msg' => 'Kontener został zapisany.', 'id' => $id ]; - - echo json_encode( $response ); - exit; - } - - public static function container_edit() - { - return \admin\view\Scontainers::container_edit( - \admin\factory\Scontainers::container_details( - \S::get( 'id' ) - ), - ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->languagesList() - ); - } - - public static function view_list() - { - return \admin\view\Scontainers::containers_list(); - } -} diff --git a/autoload/admin/controls/class.ShopProduct.php b/autoload/admin/controls/class.ShopProduct.php index d0181bc..6e1cb97 100644 --- a/autoload/admin/controls/class.ShopProduct.php +++ b/autoload/admin/controls/class.ShopProduct.php @@ -244,7 +244,7 @@ class ShopProduct 'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'id' ) ), 'languages' => ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->languagesList(), 'categories' => \admin\factory\ShopCategory::subcategories( null ), - 'layouts' => \admin\factory\Layouts::layouts_list(), + 'layouts' => self::layouts_for_product_edit( $mdb ), 'products' => \admin\factory\ShopProduct::products_list(), 'dlang' => \front\factory\Languages::default_language(), 'sets' => \shop\ProductSet::sets_list(), @@ -254,6 +254,23 @@ class ShopProduct ] ); } + 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 : []; + } + + if ( class_exists( '\admin\factory\Layouts' ) ) + { + $rows = \admin\factory\Layouts::layouts_list(); + return is_array( $rows ) ? $rows : []; + } + + return []; + } + // ajax_load_products ARCHIVE static public function ajax_load_products_archive() { diff --git a/autoload/admin/factory/class.Scontainers.php b/autoload/admin/factory/class.Scontainers.php index 438a87e..3f1d819 100644 --- a/autoload/admin/factory/class.Scontainers.php +++ b/autoload/admin/factory/class.Scontainers.php @@ -3,86 +3,30 @@ namespace admin\factory; class Scontainers { - public static function container_delete( $container_id ) + private static function repository(): \Domain\Scontainers\ScontainersRepository { global $mdb; - return $mdb -> delete( 'pp_scontainers', [ 'id' => (int)$container_id ] ); + return new \Domain\Scontainers\ScontainersRepository($mdb); } - public static function container_save( $container_id, $title, $text, $status, $show_title ) + public static function container_delete($container_id) { - global $mdb; - - if ( !$container_id ) - { - $mdb -> insert( 'pp_scontainers', [ - 'status' => $status == 'on' ? 1 : 0, - 'show_title' => $show_title == 'on' ? 1 : 0 - ] ); - - $id = $mdb -> id(); - - if ( $id ) - { - foreach ( $title as $key => $val ) - { - $mdb -> insert( 'pp_scontainers_langs', [ - 'container_id' => (int)$id, - 'lang_id' => $key, - 'title' => $title[$key], - 'text' => $text[$key] - ] ); - } - - \S::delete_dir( '../temp/' ); - - return $id; - } - } - else - { - $mdb -> update( 'pp_scontainers', [ - 'status' => $status == 'on' ? 1 : 0, - 'show_title' => $show_title == 'on' ? 1 : 0 - ], [ - 'id' => (int)$container_id - ] ); - - foreach ( $title as $key => $val ) - { - if ( $translation_id = $mdb -> get( 'pp_scontainers_langs', 'id', [ 'AND' => [ 'container_id' => $container_id, 'lang_id' => $key ] ] ) ) - $mdb -> update( 'pp_scontainers_langs', [ - 'lang_id' => $key, - 'title' => $title[$key], - 'text' => $text[$key] - ], [ - 'id' => $translation_id - ] ); - else - $mdb -> insert( 'pp_scontainers_langs', [ - 'container_id' => (int)$container_id, - 'lang_id' => $key, - 'title' => $title[$key], - 'text' => $text[$key] - ] ); - } - - \S::delete_dir( '../temp/' ); - - return $container_id; - } + return self::repository()->delete((int)$container_id); } - public static function container_details( $container_id ) + public static function container_save($container_id, $title, $text, $status, $show_title) { - global $mdb; - - $container = $mdb -> get( 'pp_scontainers', '*', [ 'id' => (int)$container_id ] ); - - $results = $mdb -> select( 'pp_scontainers_langs', '*', [ 'container_id' => (int)$container_id ] ); - if ( is_array( $results ) ) foreach ( $results as $row ) - $container['languages'][ $row['lang_id'] ] = $row; - - return $container; + return self::repository()->save([ + 'id' => (int)$container_id, + 'title' => is_array($title) ? $title : [], + 'text' => is_array($text) ? $text : [], + 'status' => $status, + 'show_title' => $show_title, + ]); } -} + + public static function container_details($container_id) + { + return self::repository()->find((int)$container_id); + } +} \ No newline at end of file diff --git a/autoload/admin/view/class.Scontainers.php b/autoload/admin/view/class.Scontainers.php deleted file mode 100644 index 486edb7..0000000 --- a/autoload/admin/view/class.Scontainers.php +++ /dev/null @@ -1,20 +0,0 @@ - container = $container; - $tpl -> languages = $languages; - return $tpl -> render( 'scontainers/container-edit' ); - } - - public static function containers_list() - { - $tpl = new \Tpl; - return $tpl -> render( 'scontainers/containers-list' ); - } -} diff --git a/autoload/front/factory/class.Scontainers.php b/autoload/front/factory/class.Scontainers.php index 91340fe..c3a7c1f 100644 --- a/autoload/front/factory/class.Scontainers.php +++ b/autoload/front/factory/class.Scontainers.php @@ -3,7 +3,7 @@ namespace front\factory; class Scontainers { - public static function scontainer_details( $scontainer_id ) + public static function scontainer_details($scontainer_id) { global $mdb, $lang; @@ -11,21 +11,29 @@ class Scontainers $cacheKey = "\front\factory\Scontainers::scontainer_details:$scontainer_id"; $objectData = $cacheHandler->get($cacheKey); - - if ( !$objectData ) - { - $scontainer = $mdb -> get( 'pp_scontainers', '*', [ 'id' => (int)$scontainer_id ] ); - $results = $mdb -> select( 'pp_scontainers_langs', '*', [ 'AND' => [ 'container_id' => (int)$scontainer_id, 'lang_id' => $lang[0] ] ] ); - if ( is_array( $results ) ) foreach ( $results as $row ) - $scontainer['languages'] = $row; - - $cacheHandler -> set( $cacheKey, $scontainer ); - } - else - { + if ($objectData) { return unserialize($objectData); } + $repository = new \Domain\Scontainers\ScontainersRepository($mdb); + $langId = (string)($lang[0] ?? 'pl'); + $scontainer = $repository->detailsForLanguage((int)$scontainer_id, $langId); + + if (!is_array($scontainer)) { + $scontainer = [ + 'id' => (int)$scontainer_id, + 'status' => 0, + 'show_title' => 0, + 'languages' => [ + 'lang_id' => $langId, + 'title' => '', + 'text' => '', + ], + ]; + } + + $cacheHandler->set($cacheKey, $scontainer); + return $scontainer; } -} +} \ No newline at end of file diff --git a/tests/Unit/Domain/Scontainers/ScontainersRepositoryTest.php b/tests/Unit/Domain/Scontainers/ScontainersRepositoryTest.php new file mode 100644 index 0000000..5348e52 --- /dev/null +++ b/tests/Unit/Domain/Scontainers/ScontainersRepositoryTest.php @@ -0,0 +1,63 @@ +createMock(\medoo::class); + $repository = new ScontainersRepository($mockDb); + + $container = $repository->find(0); + + $this->assertIsArray($container); + $this->assertSame(0, (int)$container['id']); + $this->assertSame(1, (int)$container['status']); + } + + public function testDeleteReturnsFalseForInvalidId(): void + { + $mockDb = $this->createMock(\medoo::class); + $repository = new ScontainersRepository($mockDb); + + $this->assertFalse($repository->delete(0)); + } + + public function testFindReturnsContainerWithTranslations(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_scontainers', '*', ['id' => 7]) + ->willReturn(['id' => 7, 'status' => 1, 'show_title' => 1]); + + $mockDb->expects($this->once()) + ->method('select') + ->with('pp_scontainers_langs', '*', ['container_id' => 7]) + ->willReturn([ + ['lang_id' => 'pl', 'title' => 'Tytul PL', 'text' => 'Tekst PL'], + ['lang_id' => 'en', 'title' => 'Title EN', 'text' => 'Text EN'], + ]); + + $repository = new ScontainersRepository($mockDb); + $container = $repository->find(7); + + $this->assertSame(7, (int)$container['id']); + $this->assertArrayHasKey('languages', $container); + $this->assertArrayHasKey('pl', $container['languages']); + $this->assertArrayHasKey('en', $container['languages']); + } + + public function testDetailsForLanguageReturnsNullForInvalidData(): void + { + $mockDb = $this->createMock(\medoo::class); + $repository = new ScontainersRepository($mockDb); + + $this->assertNull($repository->detailsForLanguage(0, 'pl')); + $this->assertNull($repository->detailsForLanguage(1, '')); + } +} + diff --git a/tests/Unit/admin/Controllers/ScontainersControllerTest.php b/tests/Unit/admin/Controllers/ScontainersControllerTest.php new file mode 100644 index 0000000..49cb2e5 --- /dev/null +++ b/tests/Unit/admin/Controllers/ScontainersControllerTest.php @@ -0,0 +1,59 @@ +repository = $this->createMock(ScontainersRepository::class); + $this->languagesRepository = $this->createMock(LanguagesRepository::class); + $this->controller = new ScontainersController($this->repository, $this->languagesRepository); + } + + public function testConstructorAcceptsDependencies(): void + { + $controller = new ScontainersController($this->repository, $this->languagesRepository); + $this->assertInstanceOf(ScontainersController::class, $controller); + } + + public function testHasMainActionMethods(): void + { + $this->assertTrue(method_exists($this->controller, 'list')); + $this->assertTrue(method_exists($this->controller, 'view_list')); + $this->assertTrue(method_exists($this->controller, 'edit')); + $this->assertTrue(method_exists($this->controller, 'save')); + $this->assertTrue(method_exists($this->controller, 'delete')); + } + + public function testActionMethodReturnTypes(): void + { + $reflection = new \ReflectionClass($this->controller); + + $this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('view_list')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('edit')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('delete')->getReturnType()); + } + + public function testConstructorRequiresRepositoryAndLanguagesRepository(): void + { + $reflection = new \ReflectionClass(ScontainersController::class); + $constructor = $reflection->getConstructor(); + $params = $constructor->getParameters(); + + $this->assertCount(2, $params); + $this->assertEquals('Domain\Scontainers\ScontainersRepository', $params[0]->getType()->getName()); + $this->assertEquals('Domain\Languages\LanguagesRepository', $params[1]->getType()->getName()); + } +} + diff --git a/updates/0.20/ver_0.259_files.txt b/updates/0.20/ver_0.259_files.txt new file mode 100644 index 0000000..ac3f2b8 --- /dev/null +++ b/updates/0.20/ver_0.259_files.txt @@ -0,0 +1,2 @@ +F: ../autoload/admin/controls/class.Scontainers.php +F: ../autoload/admin/view/class.Scontainers.php