From 3c50440cb29fb7468eb8877bc6b5aa6ee3c944e6 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Sun, 8 Feb 2026 18:12:54 +0100 Subject: [PATCH] feat: implement new universal form edit system and remove legacy banner classes --- .phpunit.result.cache | 2 +- REFACTORING_PLAN.md | 170 ++++++++++++++++++++++- autoload/admin/factory/class.Banners.php | 108 -------------- autoload/admin/view/class.Banners.php | 24 ---- updates/0.20/ver_0.249_files.txt | 2 + updates/changelog.php | 1 + 6 files changed, 173 insertions(+), 134 deletions(-) delete mode 100644 autoload/admin/factory/class.Banners.php delete mode 100644 autoload/admin/view/class.Banners.php diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 762a9c5..80a3900 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.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testDeleteReturnsTrue":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveInsertsNewBanner":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithRedis":0.001,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheRedisUnavailable":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithoutRedis":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheReturnStructure":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testCanBeInstantiated":0.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.001,"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,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveSetsStatusToMinusOne":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveReturnsFalseWhenUpdateFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderUpdatesImageOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderSkipsEmptyValues":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasBrowseListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasGalleryOrderSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testBrowseListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testGalleryOrderSaveMethodReturnType":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminUsesBoundParamsForTitleFilter":0}} \ 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.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 diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md index 8e78ee4..1e46834 100644 --- a/REFACTORING_PLAN.md +++ b/REFACTORING_PLAN.md @@ -343,6 +343,174 @@ vendor/bin/phpstan analyse autoload/Domain 5. **Category** 6. **ShopAttribute** +- **Form Edit System** - Nowy uniwersalny system formularzy edycji + - ✅ Klasy ViewModel: `FormFieldType`, `FormField`, `FormTab`, `FormAction`, `FormEditViewModel` + - ✅ Walidacja: `FormValidator` z obsługą reguł per pole i sekcje językowe + - ✅ Persist: `FormRequestHandler` - zapamiętywanie danych przy błędzie walidacji + - ✅ Renderer: `FormFieldRenderer` - renderowanie wszystkich typów pól + - ✅ Szablon: `admin/templates/components/form-edit.php` - uniwersalny layout + - ✅ BannerController - przerobiony na nowy system formularzy + - Wspierane typy pól: text, number, email, password, date, datetime, switch, select, textarea, editor, image, file, hidden, lang_section + - Obsługa zakładek (vertical) i sekcji językowych (horizontal) + - **Do zrobienia**: Przerobić pozostałe kontrolery (Articles, Settings, Product, Category, itd.) + --- *Rozpoczęto: 2025-02-05* -*Ostatnia aktualizacja: 2026-02-07* +*Ostatnia aktualizacja: 2026-02-08* + + +## Form Edit System - Dokumentacja użycia + +### Architektura + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Controller │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ edit() │ │ save() │ │ +│ │ - buduje VM │ │ - walidacja │ │ +│ │ - renderuje │ │ - zapis │ │ +│ └────────┬────────┘ └─────────────────┘ │ +└───────────┼─────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ FormEditViewModel │ +│ - title, formId, data, fields, tabs, actions │ +│ - validationErrors, persist, languages │ +└───────────┬─────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ components/form-edit.php (szablon) │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ FormFieldRenderer - renderuje każde pole │ │ +│ │ ├─ input, select, textarea, switch │ │ +│ │ ├─ date, datetime, editor, image │ │ +│ │ └─ lang_section (zagnieżdżone pola) │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Przykład użycia w kontrolerze + +```php +use admin\ViewModels\Forms\FormEditViewModel; +use admin\ViewModels\Forms\FormField; +use admin\ViewModels\Forms\FormTab; +use admin\ViewModels\Forms\FormAction; +use admin\Support\Forms\FormRequestHandler; + +class BannerController +{ + public function edit(): string + { + $banner = $this->repository->find($id); + $languages = \admin\factory\Languages::languages_list(); + + $viewModel = new FormEditViewModel( + formId: 'banner-edit', + title: 'Edycja banera', + data: $banner, + tabs: [ + new FormTab('settings', 'Ustawienia', 'fa-wrench'), + new FormTab('content', 'Zawartość', 'fa-file'), + ], + fields: [ + // Zakładka Ustawienia + FormField::text('name', [ + 'label' => 'Nazwa', + 'tab' => 'settings', + 'required' => true, + ]), + FormField::switch('status', [ + 'label' => 'Aktywny', + 'tab' => 'settings', + ]), + FormField::date('date_start', [ + 'label' => 'Data rozpoczęcia', + 'tab' => 'settings', + ]), + + // Sekcja językowa w zakładce Zawartość + FormField::langSection('translations', 'content', [ + FormField::image('src', ['label' => 'Obraz']), + FormField::text('url', ['label' => 'Url']), + FormField::editor('text', ['label' => 'Treść']), + ]), + ], + actions: [ + FormAction::save('/admin/banners/save', '/admin/banners'), + FormAction::cancel('/admin/banners'), + ], + languages: $languages, + persist: true, + ); + + return \Tpl::view('components/form-edit', ['form' => $viewModel]); + } + + public function save(): void + { + $formHandler = new FormRequestHandler(); + $viewModel = $this->buildFormViewModel(); // jak w edit() + + $result = $formHandler->handleSubmit($viewModel, $_POST); + + if (!$result['success']) { + // Błędy walidacji - zapisane automatycznie do sesji + echo json_encode(['success' => false, 'errors' => $result['errors']]); + exit; + } + + // Sukces - persist wyczyszczony automatycznie + $this->repository->save($result['data']); + echo json_encode(['success' => true]); + exit; + } +} +``` + +### Dostępne typy pól + +| Typ | Metoda | Opcje | +|-----|--------|-------| +| `text` | `FormField::text(name, ['label' => '...', 'required' => true])` | placeholder, help | +| `number` | `FormField::number(name, [...])` | - | +| `email` | `FormField::email(name, [...])` | walidacja formatu | +| `password` | `FormField::password(name, [...])` | - | +| `date` | `FormField::date(name, [...])` | datetimepicker | +| `datetime` | `FormField::datetime(name, [...])` | datetimepicker z czasem | +| `switch` | `FormField::switch(name, [...])` | checked (bool) | +| `select` | `FormField::select(name, ['options' => [...]])` | options: [key => label] | +| `textarea` | `FormField::textarea(name, ['rows' => 4])` | rows | +| `editor` | `FormField::editor(name, ['toolbar' => 'MyTool'])` | CKEditor | +| `image` | `FormField::image(name, ['filemanager' => true])` | filemanager URL | +| `file` | `FormField::file(name, [...])` | filemanager | +| `hidden` | `FormField::hidden(name, value)` | - | +| `lang_section` | `FormField::langSection(name, 'tab', [fields])` | pola per język | + +### Walidacja + +Walidacja jest automatyczna na podstawie właściwości pól: +- `required` - pole wymagane +- `type` = `email` - walidacja formatu e-mail +- `type` = `number` - walidacja liczby +- `type` = `date` - walidacja formatu YYYY-MM-DD + +Dla sekcji językowych walidacja jest powtarzana dla każdego aktywnego języka. + +### Persist (zapamiętywanie danych) + +Gdy `persist = true`: +1. Przy błędzie walidacji dane są zapisywane w `$_SESSION['form_persist'][$formId]` +2. Formularz automatycznie przywraca dane z sesji przy ponownym wyświetleniu +3. Po udanym zapisie sesja jest czyszczona automatycznie przez `FormRequestHandler` + +### Przerabianie istniejących formularzy + +1. **Kontroler** - zamień `view\Xxx::edit()` na `FormEditViewModel` +2. **Repository** - dostosuj `save()` do formatu z `FormRequestHandler` (lub dodaj wsparcie dla obu formatów) +3. **Szablon** - usuń stary szablon lub zostaw jako fallback +4. **Testy** - zaktualizuj testy jeśli zmienił się format danych + diff --git a/autoload/admin/factory/class.Banners.php b/autoload/admin/factory/class.Banners.php deleted file mode 100644 index 940c92d..0000000 --- a/autoload/admin/factory/class.Banners.php +++ /dev/null @@ -1,108 +0,0 @@ - delete( 'pp_banners', [ 'id' => (int) $banner_id ] ); - \S::delete_dir( '../temp/' ); - - return $result; - } - - public static function banner_save( $banner_id, $name, $status, $date_start, $date_end, $home_page, $src, $url, $html, $text ) - { - global $mdb; - - if ( !$banner_id ) - { - $mdb -> insert( 'pp_banners', [ - 'name' => $name, - 'status' => $status == 'on' ? 1 : 0, - 'date_start' => $date_start != '' ? $date_start : null, - 'date_end' => $date_end != '' ? $date_end : null, - 'home_page' => $home_page == 'on' ? 1 : 0 - ] ); - - $id = $mdb -> id(); - - if ( $id ) - { - foreach ( $src as $key => $val ) - { - $mdb -> insert( 'pp_banners_langs', [ - 'id_banner' => (int)$id, - 'id_lang' => $key, - 'src' => $src[$key], - 'url' => $url[$key], - 'html' => $html[$key], - 'text' => $text[$key] - ] ); - } - - \S::delete_dir( '../temp/' ); - - return $id; - } - } - else - { - $mdb -> update( 'pp_banners', [ - 'name' => $name, - 'status' => $status == 'on' ? 1 : 0, - 'date_start' => $date_start != '' ? $date_start : null, - 'date_end' => $date_end != '' ? $date_end : null, - 'home_page' => $home_page == 'on' ? 1 : 0 - ], [ - 'id' => (int)$banner_id - ] ); - - foreach ( $src as $key => $val ) - { - if ( $translation_id = $mdb -> get( 'pp_banners_langs', 'id', [ 'AND' => [ 'banner_id' => $banner_id, 'lang_id' => $key ] ] ) ) - $mdb -> update( 'pp_banners_langs', [ - 'id_lang' => $key, - 'src' => $src[$key], - 'url' => $url[$key], - 'html' => $html[$key], - 'text' => $text[$key] - ], [ - 'id' => $translation_id - ] ); - else - $mdb -> insert( 'pp_banners_langs', [ - 'id_banner' => (int)$banner_id, - 'id_lang' => $key, - 'src' => $src[$key], - 'url' => $url[$key], - 'html' => $html[$key], - 'text' => $text[$key] - ] ); - } - - \S::delete_dir( '../temp/' ); - - return $banner_id; - } - return false; - } - - public static function banner_details( $id_banner ) - { - global $mdb; - - $banner = $mdb -> get( 'pp_banners', '*', [ 'id' => (int)$id_banner ] ); - - $results = $mdb -> select( 'pp_banners_langs', '*', [ 'id_banner' => (int)$id_banner ] ); - if ( is_array( $results ) ) foreach ( $results as $row ) - $banner['languages'][$row['id_lang']] = $row; - - return $banner; - } - -} -?> diff --git a/autoload/admin/view/class.Banners.php b/autoload/admin/view/class.Banners.php deleted file mode 100644 index 3be6e25..0000000 --- a/autoload/admin/view/class.Banners.php +++ /dev/null @@ -1,24 +0,0 @@ -list(); - } - - public static function banner_edit( $banner, $languages ) - { - $tpl = new \Tpl; - $tpl -> banner = $banner; - $tpl -> languages = $languages; - return $tpl -> render( 'banners/banner-edit' ); - } -} -?> diff --git a/updates/0.20/ver_0.249_files.txt b/updates/0.20/ver_0.249_files.txt index e69de29..ef61eb1 100644 --- a/updates/0.20/ver_0.249_files.txt +++ b/updates/0.20/ver_0.249_files.txt @@ -0,0 +1,2 @@ +F: ../autoload/admin/view/class.Banners.php +F: ../autoload/admin/factory/class.Banners.php diff --git a/updates/changelog.php b/updates/changelog.php index 4891ce9..1afc2af 100644 --- a/updates/changelog.php +++ b/updates/changelog.php @@ -4,6 +4,7 @@ - FIX - filemanager: przywrocono dzialanie popupa wyboru obrazka z banera - UPDATE - komunikaty zapisu w nowym formularzu sa wyswietlane w stylu panelu (bez natywnego alertu JS) - UPDATE - lista banerow: dodano kolumne miniatury oraz podglad duzego obrazka w popup po najechaniu +- UPDATE - usunieto nieuzywane legacy klasy banerow: `admin\\view\\Banners`, `admin\\factory\\Banners`
ver. 0.248
- UPDATE - filtry w nowych tabelach dzialaja automatycznie na `onchange` - UPDATE - `components/table-list`: auto-submit formularza filtrow po zmianie pola (select, date, text)