refactor(shop-statuses): migrate to DI, restructure docs into docs/ folder (0.268)
- Migrate ShopStatuses module to Domain + DI architecture - Add ShopStatusRepository, ShopStatusesController with color picker - Convert front\factory\ShopStatuses to facade - Add FormFieldType::COLOR with HTML5 color picker - Move documentation files to docs/ folder (PROJECT_STRUCTURE, REFACTORING_PLAN, CHANGELOG, FORM_EDIT_SYSTEM, TESTING, DATABASE_STRUCTURE) - Tests: 254 tests, 736 assertions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
19
AGENTS.md
19
AGENTS.md
@@ -6,10 +6,12 @@ Gdy użytkownik napisze `KONIEC PRACY`, wykonaj kolejno:
|
||||
|
||||
1. Przeprowadzenie testów.
|
||||
2. Aktualizacja dokumentacji technicznej, jeśli zmiany tego wymagają:
|
||||
- `DATABASE_STRUCTURE.md`
|
||||
- `PROJECT_STRUCTURE.md`
|
||||
- `REFACTORING_PLAN.md`
|
||||
- `TESTING.md`
|
||||
- `docs/DATABASE_STRUCTURE.md`
|
||||
- `docs/PROJECT_STRUCTURE.md`
|
||||
- `docs/REFACTORING_PLAN.md`
|
||||
- `docs/FORM_EDIT_SYSTEM.md`
|
||||
- `docs/CHANGELOG.md`
|
||||
- `docs/TESTING.md`
|
||||
3. Przygotowanie aktualizacji (ZIP, plik z usuwanymi plikami, plik SQL jeśli wymagany).
|
||||
4. Commit.
|
||||
5. Push.
|
||||
@@ -18,10 +20,11 @@ Gdy użytkownik napisze `KONIEC PRACY`, wykonaj kolejno:
|
||||
|
||||
Przed rozpoczęciem implementacji sprawdź aktualną zawartość:
|
||||
|
||||
- `DATABASE_STRUCTURE.md`
|
||||
- `PROJECT_STRUCTURE.md`
|
||||
- `REFACTORING_PLAN.md`
|
||||
- `TESTING.md`
|
||||
- `docs/DATABASE_STRUCTURE.md`
|
||||
- `docs/PROJECT_STRUCTURE.md`
|
||||
- `docs/REFACTORING_PLAN.md`
|
||||
- `docs/CHANGELOG.md`
|
||||
- `docs/TESTING.md`
|
||||
|
||||
To ma pomóc zachować spójność zmian i dokumentacji.
|
||||
|
||||
|
||||
@@ -1,505 +0,0 @@
|
||||
# Struktura Projektu shopPRO
|
||||
|
||||
Dokumentacja struktury projektu shopPRO do szybkiego odniesienia.
|
||||
|
||||
## System Cache (Redis)
|
||||
|
||||
### Klasy odpowiedzialne za cache
|
||||
|
||||
#### RedisConnection
|
||||
- **Plik:** `autoload/class.RedisConnection.php`
|
||||
- **Opis:** Singleton zarządzający połączeniem z Redis
|
||||
- **Metody:**
|
||||
- `getInstance()` - pobiera instancję połączenia
|
||||
- `getConnection()` - zwraca obiekt Redis
|
||||
|
||||
#### CacheHandler
|
||||
- **Plik:** `autoload/class.CacheHandler.php`
|
||||
- **Opis:** Handler do obsługi cache Redis
|
||||
- **Metody:**
|
||||
- `get($key)` - pobiera wartość z cache
|
||||
- `set($key, $value, $ttl = 86400)` - zapisuje wartość do cache
|
||||
- `exists($key)` - sprawdza czy klucz istnieje
|
||||
- `delete($key)` - usuwa pojedynczy klucz
|
||||
- `deletePattern($pattern)` - usuwa klucze według wzorca
|
||||
|
||||
#### Klasa S (pomocnicza)
|
||||
- **Plik:** `autoload/class.S.php`
|
||||
- **Metody cache:**
|
||||
- `clear_redis_cache()` - czyści cały cache Redis (flushAll)
|
||||
- `clear_product_cache(int $product_id)` - czyści cache konkretnego produktu
|
||||
|
||||
### Wzorce kluczy Redis
|
||||
|
||||
#### Produkty
|
||||
```
|
||||
shop\product:{product_id}:{lang_id}:{permutation_hash}
|
||||
```
|
||||
- Przechowuje zserializowany obiekt produktu
|
||||
- TTL: 24 godziny (86400 sekund)
|
||||
- Klasa: `shop\Product::getFromCache()` - `autoload/shop/class.Product.php:121`
|
||||
|
||||
#### Opcje ilościowe produktu
|
||||
```
|
||||
\shop\Product::get_product_permutation_quantity_options:{product_id}:{permutation}
|
||||
```
|
||||
- Przechowuje informacje o ilości i komunikatach magazynowych
|
||||
- Klasa: `shop\Product::get_product_permutation_quantity_options()` - `autoload/shop/class.Product.php:549`
|
||||
|
||||
#### Zestawy produktów
|
||||
```
|
||||
\shop\Product::product_sets_when_add_to_basket:{product_id}
|
||||
```
|
||||
- Przechowuje produkty często kupowane razem
|
||||
- Klasa: `shop\Product::product_sets_when_add_to_basket()` - `autoload/shop/class.Product.php:316`
|
||||
|
||||
## Integracje z systemami zewnętrznymi (CRON)
|
||||
|
||||
### Plik: `cron.php`
|
||||
|
||||
#### Apilo
|
||||
- **Aktualizacja pojedynczego produktu:** synchronizacja cen i stanow
|
||||
- Czestotliwosc: Co 10 minut
|
||||
- **Synchronizacja cennika:** masowa aktualizacja cen z Apilo
|
||||
- Czestotliwosc: Co 1 godzine
|
||||
|
||||
**Uwaga:** Integracje Sellasist i Baselinker zostaly usuniete w ver. 0.263.
|
||||
|
||||
## Panel Administratora
|
||||
|
||||
### Routing
|
||||
- Główny katalog: `admin/`
|
||||
- Template główny: `admin/templates/site/main-layout.php`
|
||||
- Kontrolery (nowe): `autoload/admin/Controllers/`
|
||||
- Kontrolery legacy (fallback): `autoload/admin/controls/`
|
||||
|
||||
### Przycisk "Wyczyść cache"
|
||||
- **Lokalizacja UI:** `admin/templates/site/main-layout.php:172`
|
||||
- **JavaScript:** `admin/templates/site/main-layout.php:235-274`
|
||||
- **Endpoint AJAX:** `/admin/settings/clear_cache_ajax/`
|
||||
- **Kontroler:** `autoload/admin/Controllers/SettingsController.php:43-60`
|
||||
- **Działanie:**
|
||||
1. Pokazuje spinner "Czyszczę cache..."
|
||||
2. Czyści katalogi: `temp/`, `thumbs/`
|
||||
3. Wykonuje `flushAll()` na Redis
|
||||
4. Pokazuje "Cache wyczyszczony!" przez 2 sekundy
|
||||
5. Przywraca stan początkowy
|
||||
|
||||
## Struktura katalogów
|
||||
|
||||
```
|
||||
shopPRO/
|
||||
├── admin/ # Panel administratora
|
||||
│ ├── templates/ # Szablony widoków
|
||||
│ └── layout/ # Zasoby CSS/JS/ikony
|
||||
├── autoload/ # Klasy autoloadowane
|
||||
│ ├── admin/ # Klasy panelu admin
|
||||
│ │ ├── controls/ # Kontrolery
|
||||
│ │ └── factory/ # Fabryki/helpery
|
||||
│ ├── front/ # Klasy frontendu
|
||||
│ │ └── factory/ # Fabryki/helpery
|
||||
│ └── shop/ # Klasy sklepu
|
||||
├── libraries/ # Biblioteki zewnętrzne
|
||||
├── temp/ # Cache tymczasowy
|
||||
├── thumbs/ # Miniatury zdjęć
|
||||
└── cron.php # Zadania CRON
|
||||
|
||||
```
|
||||
|
||||
## Baza danych
|
||||
|
||||
### Główne tabele produktów
|
||||
- `pp_shop_products` - produkty główne
|
||||
- `pp_shop_products_langs` - tłumaczenia produktów
|
||||
- `pp_shop_products_images` - zdjęcia produktów
|
||||
- `pp_shop_products_categories` - kategorie produktów
|
||||
- `pp_shop_products_custom_fields` - pola własne produktów
|
||||
|
||||
### Tabele integracji
|
||||
- Kolumny w `pp_shop_products`:
|
||||
- `apilo_product_id`, `apilo_product_name`, `apilo_get_data_date`
|
||||
- Tabele ustawien:
|
||||
- `pp_shop_apilo_settings` (key-value)
|
||||
- `pp_shop_shoppro_settings` (key-value)
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
### Redis
|
||||
- Konfiguracja: `config.php` (zmienna `$config['redis']`)
|
||||
- Parametry: host, port, password
|
||||
|
||||
### Autoload
|
||||
- Funkcja: `__autoload_my_classes()` w `cron.php:6`
|
||||
- Wzorzec: `autoload/{namespace}/class.{ClassName}.php`
|
||||
|
||||
## Klasy pomocnicze
|
||||
|
||||
### \S (autoload/class.S.php)
|
||||
Główna klasa helper z metodami:
|
||||
- `seo($val)` - generowanie URL SEO
|
||||
- `normalize_decimal($val, $precision)` - normalizacja liczb
|
||||
- `send_email()` - wysyłanie emaili
|
||||
- `delete_dir($dir)` - usuwanie katalogów
|
||||
- `htacces()` - generowanie .htaccess i sitemap.xml
|
||||
|
||||
### Medoo
|
||||
- Plik: `libraries/medoo/medoo.php`
|
||||
- Zmienna: `$mdb`
|
||||
- ORM do operacji na bazie danych
|
||||
|
||||
## Najważniejsze wzorce
|
||||
|
||||
### Namespace'y
|
||||
- `\admin\Controllers\` - nowe kontrolery panelu admin (DI)
|
||||
- `\admin\controls\` - kontrolery legacy (fallback)
|
||||
- `\Domain\` - repozytoria/logika domenowa
|
||||
- `\admin\factory\` - helpery/fabryki admin
|
||||
- `\front\factory\` - helpery/fabryki frontend
|
||||
- `\shop\` - klasy sklepu (Product, Order, itp.)
|
||||
|
||||
### Cachowanie produktów
|
||||
```php
|
||||
// Pobranie produktu z cache
|
||||
$product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash);
|
||||
|
||||
// Czyszczenie cache produktu
|
||||
\S::clear_product_cache($product_id);
|
||||
|
||||
// Czyszczenie całego cache
|
||||
\S::clear_redis_cache();
|
||||
```
|
||||
|
||||
## Refaktoryzacja do Domain-Driven Architecture
|
||||
|
||||
### Nowa struktura (w trakcie migracji)
|
||||
```
|
||||
autoload/
|
||||
├── Domain/ # Nowa warstwa biznesowa (namespace \Domain\)
|
||||
│ ├── Product/
|
||||
│ │ └── ProductRepository.php # getQuantity, getPrice, getName, find, updateQuantity, archive, unarchive
|
||||
│ ├── Banner/
|
||||
│ │ └── BannerRepository.php # find, delete, save
|
||||
│ ├── Settings/
|
||||
│ │ └── SettingsRepository.php # saveSettings, getSettings (fasada → factory)
|
||||
│ └── Cache/
|
||||
│ └── CacheRepository.php # clearCache (dirs + Redis)
|
||||
├── admin/
|
||||
│ ├── Controllers/ # Nowe kontrolery (namespace \admin\Controllers\)
|
||||
│ │ ├── BannerController.php # DI, instancyjny
|
||||
│ │ ├── SettingsController.php # DI, instancyjny (clearCache, save, view)
|
||||
│ │ ├── ProductArchiveController.php # DI, instancyjny (list, unarchive)
|
||||
│ │ └── UsersController.php # DI, instancyjny (view_list, user_edit, user_save, user_delete, login_form, twofa)
|
||||
│ ├── class.Site.php # Router: nowy kontroler → fallback stary
|
||||
│ ├── controls/ # Stare kontrolery (niezależny fallback)
|
||||
│ ├── factory/ # Stare helpery (niezależny fallback)
|
||||
│ └── view/ # Widoki (statyczne - bez zmian)
|
||||
├── shop/ # Legacy - fasady do Domain
|
||||
└── front/factory/ # Legacy - stopniowo migrowane
|
||||
```
|
||||
|
||||
#### Aktualny stan migracji (uzupełnienie)
|
||||
- Dodane repozytorium: `Domain\Dictionaries\DictionariesRepository`
|
||||
- Dodane kontrolery DI: `admin\Controllers\DictionariesController`, `admin\Controllers\FilemanagerController`, `admin\Controllers\UsersController`
|
||||
- Dodane repozytorium: `Domain\User\UserRepository`
|
||||
- `Domain\Settings\SettingsRepository` działa bezpośrednio na DB (bez delegacji do `admin\factory\Settings`)
|
||||
|
||||
### Routing admin (admin\Site::route())
|
||||
1. Sprawdź mapę `$newControllers` → utwórz instancję z DI → wywołaj
|
||||
2. Jeśli nowy kontroler nie istnieje (`class_exists()` = false) → fallback na `admin\controls\`
|
||||
3. Stary kontroler jest NIEZALEŻNY od nowych klas (bezpieczny fallback)
|
||||
|
||||
### Dependency Injection
|
||||
Nowe klasy używają **Dependency Injection** zamiast `global` variables:
|
||||
```php
|
||||
// STARE
|
||||
global $mdb;
|
||||
$quantity = $mdb->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
|
||||
// NOWE
|
||||
$repository = new \Domain\Product\ProductRepository($mdb);
|
||||
$quantity = $repository->getQuantity($id);
|
||||
```
|
||||
|
||||
## Testowanie (tylko dla deweloperów)
|
||||
|
||||
**UWAGA:** Pliki testów NIE są częścią aktualizacji dla klientów!
|
||||
|
||||
### Narzędzia
|
||||
- **PHPUnit 9.6.34** - framework testowy
|
||||
- **test.bat** - uruchamianie testów
|
||||
- **composer.json** - autoloading PSR-4
|
||||
|
||||
### Struktura
|
||||
```
|
||||
tests/
|
||||
├── Unit/
|
||||
│ ├── Domain/
|
||||
│ │ ├── Product/ProductRepositoryTest.php # 11 testów
|
||||
│ │ ├── Banner/BannerRepositoryTest.php # 4 testy
|
||||
│ │ ├── Settings/SettingsRepositoryTest.php # 3 testy
|
||||
│ │ └── Cache/CacheRepositoryTest.php # 4 testy
|
||||
│ └── admin/
|
||||
│ └── Controllers/
|
||||
│ ├── SettingsControllerTest.php # 7 testów
|
||||
│ └── ProductArchiveControllerTest.php # 6 testów
|
||||
└── Integration/
|
||||
```
|
||||
Aktualnie w suite są też testy modułów `Dictionaries`, `Articles` i `Users` (repozytoria + kontrolery DI).
|
||||
**Łącznie: 119 tests, 256 assertions**
|
||||
|
||||
## Ostatnie modyfikacje
|
||||
|
||||
### 2026-02-10: Porządki po migracji i release 0.252 (ver. 0.252)
|
||||
- **UPDATE:** `ProductArchiveController` i szablony listy archiwum przepięte na nową tabelę (`components/table-list`)
|
||||
- **UPDATE:** CSS/JS dla list wydzielone do osobnych widoków `*-custom-script.php` (banery i archiwum produktów)
|
||||
- **UPDATE:** dodano `admin\Controllers\FilemanagerController` i przepięto filemanager na nowy routing
|
||||
- **FIX:** naprawiono błąd `Invalid Key` w filemanagerze
|
||||
- **CLEANUP:** usunięto legacy pliki: `autoload/admin/controls/class.Archive.php`, `autoload/admin/controls/class.Filemanager.php`, `autoload/admin/view/class.FileManager.php`, stare szablony `admin/templates/product_archive/*`
|
||||
- **RENAME:** folder szablonów `admin/templates/product_archive/` → `admin/templates/product-archive/`
|
||||
- Testy: 82 tests, 181 assertions
|
||||
|
||||
### 2026-02-09: Migracja Dictionaries (ver. 0.251)
|
||||
- **NEW:** `Domain\Dictionaries\DictionariesRepository` (listForAdmin, find, save, delete, allUnits)
|
||||
- **NEW:** `admin\Controllers\DictionariesController` (lista + formularz na nowych komponentach)
|
||||
- **UPDATE:** migracja słowników na `components/table-list` i `components/form-edit`
|
||||
- **FIX:** obsługa `lang_id` jako string (`pl`, `en`) w zapisie tłumaczeń
|
||||
- **CLEANUP:** usunięto legacy klasy Dictionaries (`admin\controls`, `admin\factory`, `front\factory`)
|
||||
- Testy: 82 tests, 181 assertions
|
||||
|
||||
### 2026-02-09: Refaktoryzacja Settings (ver. 0.250)
|
||||
- **UPDATE:** `Domain\Settings\SettingsRepository` ma bezpośredni dostęp do DB (bez delegacji do `admin\factory\Settings`)
|
||||
- **UPDATE:** przepięto użycia `admin\factory\Settings` na `Domain\Settings\SettingsRepository`
|
||||
- **CLEANUP:** usunięto legacy klasy Settings (`factory`, `controls`, `view`)
|
||||
- Testy: 82 tests, 181 assertions
|
||||
|
||||
### 2026-02-07: Usuniecie legacy kontrolera Articles (ver. 0.246)
|
||||
- **UPDATE:** usunieto `autoload/admin/controls/class.Articles.php`
|
||||
- **UPDATE:** `admin\Controllers\ArticlesController::galleryOrderSave()` uzywa `Domain\Article\ArticleRepository::saveGalleryOrder()`
|
||||
- **UPDATE:** `Domain\Article\ArticleRepository` - dodano `saveGalleryOrder(int $articleId, string $order): bool`
|
||||
- **UPDATE:** `admin\factory\Articles::gallery_order_save()` deleguje do `ArticleRepository::saveGalleryOrder()` (backward compatibility)
|
||||
- **FIX:** sortowanie list admin po reloadzie - `RewriteRule` dla `/admin/...` ma `QSA`
|
||||
- **FIX:** generator `\S::htacces()` komentuje dyrektywy `AddHandler|SetHandler|ForceType` (kompatybilnosc hostingu)
|
||||
- **UPDATE:** zrodlo generatora `libraries/htaccess.conf` dostosowane do powyzszych zmian
|
||||
- **WAZNE (deploy):** w paczce aktualizacji dodac `ver_X.XXX_files.txt` z wpisem:
|
||||
`F: ../autoload/admin/controls/class.Articles.php`
|
||||
- Testy: 65 tests, 131 assertions
|
||||
|
||||
### 2026-02-06: Migracja Articles::article_delete do DI (ver. 0.245)
|
||||
- **UPDATE:** `Domain\Article\ArticleRepository` - dodano `archive()` (ustawia status = -1)
|
||||
- **UPDATE:** `admin\Controllers\ArticlesController` - nowa akcja `delete()` z DI
|
||||
- **UPDATE:** Router `admin\Site` - dodano `'article_delete' => 'delete'` do `$actionMap`
|
||||
- **UPDATE:** `admin\factory\Articles::articles_set_archive()` deleguje do `ArticleRepository::archive()`
|
||||
- **UPDATE:** `admin\controls\Articles::article_delete()` oznaczone `@deprecated`
|
||||
- Testy: 59 tests, 123 assertions
|
||||
|
||||
### 2026-02-06: Migracja Articles::article_save do DI (ver. 0.244)
|
||||
- **UPDATE:** `Domain\Article\ArticleRepository` - dodano `save()` + prywatne helpery (`buildArticleRow`, `buildLangRow`, `saveTranslations`, `savePages`, `assignTempFiles`, `assignTempImages`, `deleteMarkedFiles`, `deleteMarkedImages`, `maxPageOrder`)
|
||||
- **UPDATE:** `admin\Controllers\ArticlesController` - nowa akcja `save()` z DI
|
||||
- **UPDATE:** Router `admin\Site` - dodano `'article_save' => 'save'` do `$actionMap`
|
||||
- **UPDATE:** `admin\factory\Articles::article_save()` deleguje do `ArticleRepository::save()` (backward compatibility)
|
||||
- **UPDATE:** `admin\controls\Articles::article_save()` oznaczone `@deprecated`
|
||||
- **UPDATE:** `tests/bootstrap.php` - dodano stub `S::seo()`
|
||||
- Testy: 57 tests, 119 assertions
|
||||
|
||||
### 2026-02-06: Articles cleanup moved to repository (ver. 0.243)
|
||||
- **UPDATE:** `Domain\Article\ArticleRepository` - added `deleteNonassignedImages()` and `deleteNonassignedFiles()`
|
||||
- **UPDATE:** `admin\Controllers\ArticlesController::edit()` uses repository cleanup methods
|
||||
- **UPDATE:** `admin\factory\Articles::delete_nonassigned_images()` and `delete_nonassigned_files()` delegate to repository (backward compatibility)
|
||||
- Testy: 50 tests, 95 assertions
|
||||
|
||||
### 2026-02-06: Migracja Articles::article_edit do DI (ver. 0.242)
|
||||
- **NOWE:** `Domain\Article\ArticleRepository` - repozytorium artykułów (`find()`)
|
||||
- **UPDATE:** `admin\Controllers\ArticlesController` - konstruktor DI + `edit()` używa repozytorium
|
||||
- **UPDATE:** Router `admin\Site` - factory dla `ArticlesController` z `ArticleRepository`
|
||||
- **UPDATE:** `admin\factory\Articles::article_details()` deleguje do `Domain\Article\ArticleRepository`
|
||||
- **UPDATE:** Stare kontrolery `admin\controls\Articles|Banners|Settings` - metody przejęte przez nowe kontrolery oznaczone `@deprecated`
|
||||
- Testy: 48 testów, 91 asercji
|
||||
|
||||
### 2026-02-06: Migracja ProductArchive (ver. 0.241)
|
||||
- **NOWE:** `admin\Controllers\ProductArchiveController` - kontroler archiwum produktów z DI
|
||||
- **NOWE:** `ProductRepository::archive()`, `unarchive()` - operacje archiwizacji w repozytorium
|
||||
- **RENAME:** `admin/templates/archive/` → `admin/templates/product_archive/`
|
||||
- **FIX:** SQL w `ajax_products_list_archive()` - puste wyszukiwanie generowało `name|ean|sku LIKE '%%'` (NULL bitwise OR filtrował wyniki)
|
||||
- **FIX:** Brakujący `archive = 1` w branchu bez wyszukiwania
|
||||
- **CLEANUP:** Usunięto zbędny JS z szablonu archiwum (apilo, baselinker, duplikowanie, edycja cen)
|
||||
- Stary kontroler `admin\controls\Archive` zachowany jako fallback
|
||||
- Testy: 50 tests, 95 assertions (+10 nowych)
|
||||
|
||||
### 2026-02-05: Migracja Settings + Cache (ver. 0.240)
|
||||
- **NOWE:** `Domain\Settings\SettingsRepository` - repozytorium ustawień (fasada → factory)
|
||||
- **NOWE:** `Domain\Cache\CacheRepository` - repozytorium cache (dirs + Redis)
|
||||
- **NOWE:** `admin\Controllers\SettingsController` - kontroler z DI (clearCache, save, view)
|
||||
- **FIX:** Brakujący `id="content"` w main-layout.php (komunikaty grid.js)
|
||||
- **FIX:** `persist_edit = true` w settings.php (komunikat po zapisie)
|
||||
- Stary kontroler `admin\controls\Settings` zachowany jako fallback
|
||||
- Testy: 29 testów, 60 asercji (+14 nowych)
|
||||
- Bootstrap testów: stuby klas systemowych (S, RedisConnection, Redis, CacheHandler)
|
||||
|
||||
### 2026-02-05: Migracja Banner + Product (ver. 0.239)
|
||||
- **NOWE:** `Domain\Banner\BannerRepository` - repozytorium banerów (find, delete, save)
|
||||
- **NOWE:** `admin\Controllers\BannerController` - pierwszy kontroler z DI
|
||||
- **NOWE:** Router z mapą `$newControllers` + fallback na stare kontrolery
|
||||
- **NOWE:** Autoloader PSR-4 fallback w 9 entry pointach
|
||||
- Zmigrowano: `get_product_price()` → `ProductRepository::getPrice()`
|
||||
- Zmigrowano: `get_product_name()` → `ProductRepository::getName()`
|
||||
- Testy: 15 testów, 31 asercji
|
||||
|
||||
### 2025-02-05: Refaktoryzacja - Product Repository (ver. 0.238)
|
||||
- **NOWE:** `Domain\Product\ProductRepository` - pierwsza klasa w nowej architekturze
|
||||
- **NOWE:** Dependency Injection zamiast `global $mdb`
|
||||
- **NOWE:** Testy jednostkowe (5 testów, 100% pokrycie)
|
||||
- Zmigrowano: `get_product_quantity()` → `ProductRepository::getQuantity()`
|
||||
- Kompatybilność: Stara klasa `shop\Product` działa jako fasada
|
||||
|
||||
### 2025-02-05: System cache produktów (ver. 0.237)
|
||||
- Automatyczne czyszczenie cache produktu po aktualizacji przez CRON
|
||||
- AJAX dla przycisku "Wyczyść cache" w panelu admin
|
||||
- Metody `delete()` i `deletePattern()` w CacheHandler
|
||||
- Metoda `clear_product_cache()` w klasie S
|
||||
|
||||
---
|
||||
*Dokument aktualizowany: 2026-02-12*
|
||||
|
||||
|
||||
### 2026-02-12: Migracja Users (/admin/users) (ver. 0.253)
|
||||
- **NOWE:** `Domain\User\UserRepository` - repozytorium uzytkownikow (CRUD, check_login, logon, details, 2FA)
|
||||
- **NOWE:** `admin\Controllers\UsersController` - kontroler DI dla akcji `view_list`, `user_edit`, `user_save`, `user_delete`, `login_form`, `twofa`
|
||||
- **UPDATE:** `admin\Site` - dodany factory wpis dla modulu `Users` w mapie nowych kontrolerow
|
||||
- **UPDATE:** `admin\factory\Users` - fasada deleguje logike do `Domain\User\UserRepository`
|
||||
- **UPDATE:** `admin/ajax/users.php` - `check_login` korzysta bezposrednio z `UserRepository`
|
||||
- **CLEANUP:** usuniety `autoload/admin/controls/class.Users.php` (brak fallback - nowy kontroler obsluguje wszystkie akcje)
|
||||
- Testy: 119 tests, 256 assertions
|
||||
|
||||
---
|
||||
*Dokument aktualizowany: 2026-02-12*
|
||||
- **UPDATE:** widoki Users przeniesione z `grid/gridEdit` na `components/table-list` i `components/form-edit`
|
||||
|
||||
## Aktualizacja 2026-02-12 (finalizacja Users)
|
||||
- Modu<64> users dzia<69>a na `Domain\\User\\UserRepository` + `admin\\Controllers\\UsersController`.
|
||||
- Usuni<6E>to legacy klasy: `autoload/admin/controls/class.Users.php`, `autoload/admin/factory/class.Users.php`, `autoload/admin/view/class.Users.php`.
|
||||
- Walidacja: przy w<><77>czonym 2FA pole `twofa_email` jest wymagane.
|
||||
- Widoki users przeniesione na `components/table-list` i `components/form-edit`.
|
||||
- **NOWE:** `Domain\\Languages\\LanguagesRepository` - repozytorium jezykow i tlumaczen (lista, zapis, usuwanie, max_order)
|
||||
- **NOWE:** `admin\\Controllers\\LanguagesController` - kontroler DI (`list/view_list`, `language_*`, `translation_*`)
|
||||
- **UPDATE:** modul Languages przepiety z `grid/gridEdit` na `components/table-list` i `components/form-edit`
|
||||
- **CLEANUP:** usuniete legacy klasy `autoload/admin/controls/class.Languages.php`, `autoload/admin/view/class.Languages.php`
|
||||
- Testy: 130 tests, 301 assertions
|
||||
|
||||
## Aktualizacja 2026-02-12 (Languages final)
|
||||
- Dodano `Domain\\Languages\\LanguagesRepository` oraz `admin\\Controllers\\LanguagesController`.
|
||||
- Modul `/admin/languages/` (jezyki + tlumaczenia) dziala na nowym routingu DI.
|
||||
- Widoki jezykow przepiete na `components/table-list` i `components/form-edit`.
|
||||
- Usunieto legacy: `autoload/admin/controls/class.Languages.php`, `autoload/admin/view/class.Languages.php`.
|
||||
|
||||
## Aktualizacja 2026-02-12 (ver. 0.255)
|
||||
- UPDATE: admin/Controllers/SettingsController, BannerController, DictionariesController, ArticlesController pobieraja listy jezykow przez Domain/Languages/LanguagesRepository (DI), bez zaleznosci od admin/factory/Languages.
|
||||
- UPDATE: w admin/Site fabryki DI dla Articles, Banners, Settings, Dictionaries przekazuja rowniez LanguagesRepository.
|
||||
- UPDATE: legacy admin/controls/* oraz admin/factory/Shop* przepiete z admin/factory/Languages::languages_list() na bezposrednie wywolania LanguagesRepository.
|
||||
- FIX: autoload/admin/factory/class.Languages.php uzywa pelnego znacznika <?php (kompatybilnosc serwerow z short_open_tag=Off).
|
||||
- Testy: 130 tests, 303 assertions
|
||||
## Aktualizacja 2026-02-12 (ver. 0.256)
|
||||
- NOWE: `Domain\\Layouts\\LayoutsRepository` (find/save/delete/listForAdmin, menusWithPages, categoriesTree).
|
||||
- NOWE: `admin\\Controllers\\LayoutsController` (DI) dla akcji `list`, `layout_edit`, `layout_save`, `layout_delete`.
|
||||
- UPDATE: `/admin/layouts/view_list/` przepiete z legacy `grid` na `components/table-list` (`PaginatedTableViewModel` + `TableListRequestFactory`).
|
||||
- UPDATE: `admin/templates/layouts/layout-edit.php` korzysta z danych przekazywanych z repozytorium (bez wywolan `admin\\factory\\Pages` i `admin\\factory\\ShopCategory` w widoku).
|
||||
- NOWE: `admin/templates/layouts/subcategories-list.php` (rekurencyjny partial bez zaleznosci od legacy factory).
|
||||
- UPDATE: `Domain\\Languages\\LanguagesRepository` ma wspolna metode `defaultLanguageId()` wykorzystywana m.in. w `LayoutsController`.
|
||||
- CLEANUP: usuniete legacy klasy `autoload/admin/controls/class.Layouts.php`, `autoload/admin/view/class.Layouts.php`; `admin/factory/class.Layouts.php` dziala jako fasada do `Domain\\Layouts\\LayoutsRepository`.
|
||||
- UPDATE: `admin\\Controllers\\ArticlesController` pobiera layouty przez `Domain\\Layouts\\LayoutsRepository` (DI).
|
||||
- Testy: 141 tests, 336 assertions
|
||||
|
||||
## Aktualizacja 2026-02-12 (ver. 0.257)
|
||||
- NOWE: `Domain\\Newsletter\\NewsletterRepository` (subskrybenci, szablony, ustawienia, kolejka wysylki).
|
||||
- NOWE: `Domain\\Newsletter\\NewsletterPreviewRenderer` (render podgladu newslettera).
|
||||
- NOWE: `admin\\Controllers\\NewsletterController` (DI) dla akcji `emails_list`, `prepare`, `send`, `preview`, `settings*`, `email_template*`.
|
||||
- UPDATE: `/admin/newsletter/*` przepiete z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`.
|
||||
- UPDATE: nowy endpoint podgladu `/admin/newsletter/preview/` (bez `admin/ajax.php`).
|
||||
- UPDATE: `admin\\Site` ma fabryke DI dla modulu `Newsletter`.
|
||||
- UPDATE: `admin\\factory\\Newsletter` dziala jako fasada do `Domain\\Newsletter\\NewsletterRepository`.
|
||||
- UPDATE: `front\\factory\\Newsletter` nie korzysta z `admin\\view\\Newsletter`.
|
||||
- CLEANUP: usuniete legacy klasy `autoload/admin/controls/class.Newsletter.php`, `autoload/admin/view/class.Newsletter.php`.
|
||||
- Testy: 150 tests, 372 assertions
|
||||
|
||||
## Aktualizacja 2026-02-12 (ver. 0.258)
|
||||
- UPDATE: modul `/admin/newsletter/` - tymczasowo wylaczono akcje `prepare/send/preview` (Wysylka - przygotowanie).
|
||||
- UPDATE: modul `/admin/newsletter/` - tymczasowo wylaczono liste `email_templates_user` (Szablony uzytkownika).
|
||||
- UPDATE: lista i edycja szablonow newslettera w panelu ograniczona do szablonow administracyjnych (`is_admin = 1`).
|
||||
- CLEANUP: usuniete nieuzywane widoki: `admin/templates/newsletter/prepare.php`, `admin/templates/newsletter/preview.php`, `admin/templates/newsletter/email-templates-user.php`.
|
||||
|
||||
## Aktualizacja 2026-02-12 (ver. 0.259)
|
||||
- NOWE: `Domain\Scontainers\ScontainersRepository` (listForAdmin/find/save/delete/detailsForLanguage + czyszczenie cache frontu).
|
||||
- NOWE: `admin\Controllers\ScontainersController` (DI) dla akcji `list/view_list`, `container_edit`, `container_save`, `container_delete`.
|
||||
- UPDATE: `/admin/scontainers/*` przepiete z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`.
|
||||
- UPDATE: `admin\Site` ma fabryke DI dla modulu `Scontainers` oraz mapowanie akcji `container_*` -> `edit/save/delete`.
|
||||
- UPDATE: `admin\factory\Scontainers` dziala jako fasada do `Domain\Scontainers\ScontainersRepository`.
|
||||
- UPDATE: `front\factory\Scontainers` korzysta z `Domain\Scontainers\ScontainersRepository`.
|
||||
- CLEANUP: usuniete legacy klasy `autoload/admin/controls/class.Scontainers.php`, `autoload/admin/view/class.Scontainers.php`.
|
||||
- Testy: 158 tests, 397 assertions.
|
||||
|
||||
## Aktualizacja 2026-02-12 (ver. 0.260)
|
||||
- NOWE: `Domain\Article\ArticleRepository` rozszerzone o `listArchivedForAdmin()`, `restore()`, `deletePermanently()`.
|
||||
- NOWE: `admin\Controllers\ArticlesArchiveController` (DI) dla akcji `list/view_list`, `article_restore`, `article_delete`.
|
||||
- UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ArticlesArchive` oraz mapowanie akcji `article_restore -> restore`.
|
||||
- UPDATE: `/admin/articles_archive/view_list/` przepiete z legacy `grid` na `components/table-list`.
|
||||
- CLEANUP: usuniete legacy klasy `autoload/admin/controls/class.ArticlesArchive.php`, `autoload/admin/factory/class.ArticlesArchive.php`, `autoload/admin/view/class.ArticlesArchive.php`.
|
||||
- Testy: 165 tests, 424 assertions.
|
||||
|
||||
### 2026-02-13: Refaktoryzacja /admin/articles (ver. 0.261)
|
||||
- **UPDATE:** routing DI dla `ArticlesController` obsluguje akcje AJAX: `article_image_alt_change`, `article_file_name_change`, `article_image_delete`, `article_file_delete`.
|
||||
- **UPDATE:** widok `admin/templates/articles/article-edit.php` korzysta z endpointow `/admin/articles/*` zamiast `admin/ajax.php?a=article_*`.
|
||||
- **UPDATE:** lista artykulow nie korzysta juz z `admin\factory\Articles::article_pages` (etykiety stron z `Domain\Article\ArticleRepository`).
|
||||
- **CLEANUP:** usuniete legacy pliki `autoload/admin/view/class.Articles.php` i `admin/ajax/articles.php`; odpiecie include w `admin/ajax.php`.
|
||||
- Testy: 176 tests, 439 assertions.
|
||||
|
||||
### 2026-02-13: Articles edit UX i sortowanie zalacznikow (ver. 0.261)
|
||||
- **UPDATE:** `Domain\Article\ArticleRepository` - dodane `saveFilesOrder()` oraz obsluga `files_order` podczas `save()` (pierwszy zapis zachowuje kolejnosc).
|
||||
- **UPDATE:** routing DI (`admin\Site::$actionMap`) rozszerzony o `files_order_save -> filesOrderSave`.
|
||||
- **UPDATE:** `admin\Controllers\ArticlesController` - nowa akcja AJAX `filesOrderSave()`.
|
||||
- **UPDATE:** `admin/templates/articles/article-edit-custom-script.php` - drag&drop sortowania listy zalacznikow + synchronizacja hidden input `files_order`.
|
||||
- **UPDATE:** potwierdzenia usuwania zdjec i zalacznikow w edycji artykulu ujednolicone wizualnie z dialogiem usuwania z listy (jquery-confirm, `table-list-confirm-dialog`).
|
||||
- **FIX:** dodane ladowanie biblioteki `jquery-impromptu` w widoku edycji artykulu (kompatybilnosc dla `$.prompt`).
|
||||
- Testy: 178 tests, 443 assertions.
|
||||
|
||||
## Aktualizacja 2026-02-13 (Pages)
|
||||
- NOWE: `Domain\\Pages\\PagesRepository` (menu/page CRUD, drzewo stron, sortowanie, SEO, endpointy pomocnicze).
|
||||
- NOWE: `admin\\Controllers\\PagesController` (DI) dla modulu `/admin/pages/*`.
|
||||
- UPDATE: widoki `admin/templates/pages/*` dzialaja bez `admin\\factory\\Pages` i `admin\\view\\Pages`.
|
||||
- UPDATE: routing DI (`admin\\Site`) ma fabryke kontrolera `Pages`.
|
||||
- UPDATE: zalezne endpointy `cookie_*` i `generate_seo_link` przepiete na `/admin/pages/*`.
|
||||
- CLEANUP: usuniete legacy pliki `autoload/admin/controls/class.Pages.php`, `autoload/admin/view/class.Pages.php`, `autoload/admin/factory/class.Pages.php`, `admin/ajax/pages.php`.
|
||||
|
||||
## Aktualizacja 2026-02-13 (Integrations refactor, ver. 0.263)
|
||||
- NOWE: `Domain\Integrations\IntegrationsRepository` (settings Apilo/ShopPRO, OAuth, product linking, API fetch).
|
||||
- NOWE: `admin\Controllers\IntegrationsController` (DI) dla akcji Apilo (settings, authorization, fetch lists, product CRUD) i ShopPRO (settings, product import).
|
||||
- UPDATE: `admin\factory\Integrations` jako fasada delegujaca do repozytorium (tylko Apilo + ShopPRO).
|
||||
- CLEANUP: **usunieto integracje Sellasist i Baselinker z calego projektu** - kontrolery, factory, szablony, referencje w cron.php, Order, ShopStatuses, ShopTransport, ShopPaymentMethod, ShopProduct, config.php, front/factory/*.
|
||||
- CLEANUP: usuniete pliki: `autoload/admin/controls/class.Integrations.php`, `autoload/admin/controls/class.Baselinker.php`, `autoload/admin/factory/class.Baselinker.php`, `autoload/front/factory/class.Shop.php`, `autoload/shop/class.ShopStatus.php`, szablony sellasist/baselinker.
|
||||
- Testy: **OK (212 tests, 577 assertions)**.
|
||||
|
||||
## Aktualizacja 2026-02-13 (ShopPromotion refactor, ver. 0.264)
|
||||
- NOWE: `Domain\Promotion\PromotionRepository` (listForAdmin, find, save, delete, categoriesTree + invalidacja cache aktywnych promocji).
|
||||
- NOWE: `admin\Controllers\ShopPromotionController` (DI) dla akcji `list`, `edit`, `save`, `delete`.
|
||||
- UPDATE: modul `/admin/shop_promotion/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`.
|
||||
- NOWE: widoki `admin/templates/shop-promotion/promotions-list.php`, `admin/templates/shop-promotion/promotion-edit.php`.
|
||||
- NOWE: partiale drzewa kategorii: `admin/templates/shop-promotion/promotion-categories-selector.php`, `admin/templates/shop-promotion/promotion-categories-tree.php`.
|
||||
- NOWE: `admin/templates/shop-promotion/promotion-edit-custom-script.php` (logika warunkow promocji + drzewo kategorii).
|
||||
- CLEANUP: usuniete legacy klasy/pliki `autoload/admin/controls/class.ShopPromotion.php`, `autoload/admin/factory/class.ShopPromotion.php`, `admin/templates/shop-promotion/view-list.php`.
|
||||
- UPDATE: menu admin wskazuje kanoniczny URL `/admin/shop_promotion/list/`.
|
||||
- Testy: **OK (222 tests, 609 assertions)**.
|
||||
|
||||
## Aktualizacja 2026-02-13 (ShopPromotion poprawki, ver. 0.265)
|
||||
- UPDATE: dodano pole `Data od` (`date_from`) w module `/admin/shop_promotion` (repozytorium, formularz i lista).
|
||||
- UPDATE: `shop\Promotion::get_active_promotions()` uwzglednia `date_from` (`NULL` lub `<= dzisiaj`) obok `date_to`.
|
||||
- FIX: edycja promocji zapisuje update zamiast insert (stabilne przekazanie `id` przez hidden field + fallback `id` z URL w `save()`).
|
||||
- Testy: **OK (222 tests, 614 assertions)**.
|
||||
|
||||
## Aktualizacja 2026-02-13 (ShopCoupon refactor, ver. 0.266)
|
||||
- NOWE: `Domain\Coupon\CouponRepository` (`listForAdmin`, `find`, `save`, `delete`, `categoriesTree`).
|
||||
- NOWE: `admin\Controllers\ShopCouponController` (DI) dla akcji `list`, `edit`, `save`, `delete`.
|
||||
- UPDATE: zachowana kompatybilnosc aliasow legacy akcji (`view_list`, `coupon_edit`, `coupon_save`, `coupon_delete`) w nowym kontrolerze.
|
||||
- UPDATE: modul `/admin/shop_coupon/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`.
|
||||
- NOWE: widoki/partiale `shop-coupon/coupons-list`, `shop-coupon/coupon-edit-new`, `shop-coupon/coupon-categories-selector`, `shop-coupon/coupon-categories-tree`, `shop-coupon/coupon-edit-custom-script`.
|
||||
- CLEANUP: usuniete legacy klasy/pliki `autoload/admin/controls/class.ShopCoupon.php`, `autoload/admin/factory/class.ShopCoupon.php`, `admin/templates/shop-coupon/view-list.php`, `admin/templates/shop-coupon/coupon-edit.php`.
|
||||
- UPDATE: menu admin wskazuje kanoniczny URL `/admin/shop_coupon/list/`.
|
||||
- FIX: ujednolicone zachowanie drzewek i styl checkboxow miedzy widokami `/admin/shop_coupon/edit/*` i `/admin/layouts/edit/*` (strzalki, focus, iCheck).
|
||||
- Testy: **OK (235 tests, 682 assertions)**.
|
||||
@@ -1,873 +0,0 @@
|
||||
# Plan Refaktoryzacji shopPRO - Domain-Driven Architecture
|
||||
|
||||
## Cel
|
||||
Stopniowe przeniesienie logiki biznesowej do architektury warstwowej:
|
||||
- **Domain/** - logika biznesowa (core)
|
||||
- **Admin/** - warstwa administratora
|
||||
- **Frontend/** - warstwa użytkownika
|
||||
- **Shared/** - współdzielone narzędzia
|
||||
|
||||
## Docelowa struktura
|
||||
|
||||
```
|
||||
autoload/
|
||||
├── Domain/ # Logika biznesowa (CORE) - namespace \Domain\
|
||||
│ ├── Product/
|
||||
│ │ ├── ProductRepository.php # ✅ Zmigrowane (getQuantity, getPrice, getName, find, updateQuantity, archive, unarchive)
|
||||
│ │ ├── ProductService.php # Logika biznesowa (przyszłość)
|
||||
│ │ └── ProductCacheService.php # Cache produktu (przyszłość)
|
||||
│ ├── Banner/
|
||||
│ │ └── BannerRepository.php # ✅ Zmigrowane (find, delete, save)
|
||||
│ ├── Settings/
|
||||
│ │ └── SettingsRepository.php # ✅ Zmigrowane (saveSettings, getSettings) - bezposrednio DB
|
||||
│ ├── Dictionaries/
|
||||
│ │ └── DictionariesRepository.php # ✅ Zmigrowane (listForAdmin, find, save, delete, allUnits)
|
||||
│ ├── Cache/
|
||||
│ │ └── CacheRepository.php # ✅ Zmigrowane (clearCache)
|
||||
│ ├── Order/
|
||||
│ ├── Category/
|
||||
│ └── ...
|
||||
│
|
||||
├── admin/ # Warstwa administratora (istniejący katalog!)
|
||||
│ ├── Controllers/ # Nowe kontrolery - namespace \admin\Controllers\
|
||||
│ │ ├── ArticlesController.php
|
||||
│ │ ├── BannerController.php
|
||||
│ │ ├── DictionariesController.php
|
||||
│ │ ├── FilemanagerController.php
|
||||
│ │ ├── ProductArchiveController.php
|
||||
│ │ └── SettingsController.php
|
||||
│ ├── controls/ # Stare kontrolery (legacy fallback)
|
||||
│ ├── factory/ # Stare helpery (legacy)
|
||||
│ └── view/ # Widoki (statyczne - OK bez zmian)
|
||||
│
|
||||
├── Frontend/ # Warstwa użytkownika (przyszłość)
|
||||
│ ├── Controllers/
|
||||
│ └── Services/
|
||||
│
|
||||
├── Shared/ # Współdzielone narzędzia
|
||||
│ ├── Cache/
|
||||
│ │ ├── CacheHandler.php
|
||||
│ │ └── RedisConnection.php
|
||||
│ └── Helpers/
|
||||
│ └── S.php
|
||||
│
|
||||
└── [LEGACY] # Stare klasy (stopniowo deprecated)
|
||||
├── shop/
|
||||
├── admin/factory/
|
||||
└── front/factory/
|
||||
```
|
||||
|
||||
### WAŻNE: Konwencja namespace → katalog (Linux case-sensitive!)
|
||||
- `\Domain\` → `autoload/Domain/` (duże D - nowy katalog)
|
||||
- `\admin\Controllers\` → `autoload/admin/Controllers/` (małe a - istniejący katalog)
|
||||
- NIE używać `\Admin\` (duże A) bo na serwerze Linux katalog to `admin/` (małe a)
|
||||
|
||||
## Zasady migracji
|
||||
|
||||
### 1. Stopniowość
|
||||
- Przenosimy **jedną funkcję na raz**
|
||||
- Zachowujemy kompatybilność wsteczną
|
||||
- Stare klasy działają jako fasady do nowych
|
||||
|
||||
### 2. Dependency Injection zamiast statycznych metod
|
||||
```php
|
||||
// ❌ STARE - statyczne
|
||||
class Product {
|
||||
public static function getQuantity($id) {
|
||||
global $mdb;
|
||||
return $mdb->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NOWE - instancje z DI
|
||||
class ProductRepository {
|
||||
private $db;
|
||||
|
||||
public function __construct($db) {
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function getQuantity($id) {
|
||||
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Fasady dla kompatybilności
|
||||
```php
|
||||
// Stara klasa wywołuje nową
|
||||
namespace shop;
|
||||
|
||||
class Product {
|
||||
public static function getQuantity($id) {
|
||||
global $mdb;
|
||||
$repo = new \Domain\Product\ProductRepository($mdb);
|
||||
return $repo->getQuantity($id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Proces migracji funkcji
|
||||
|
||||
### Krok 1: Wybór funkcji
|
||||
- Wybierz prostą funkcję statyczną
|
||||
- Sprawdź jej zależności
|
||||
- Przeanalizuj gdzie jest używana
|
||||
|
||||
### Krok 2: Stworzenie nowej struktury
|
||||
- Utwórz folder `Domain/{Module}/`
|
||||
- Stwórz odpowiednią klasę (Repository/Service/Entity)
|
||||
- Przenieś logikę
|
||||
|
||||
### Krok 3: Znalezienie użyć
|
||||
```bash
|
||||
grep -r "Product::getQuantity" .
|
||||
```
|
||||
|
||||
### Krok 4: Aktualizacja wywołań
|
||||
- Opcja A: Bezpośrednie wywołanie nowej klasy
|
||||
- Opcja B: Fasada w starej klasie (zalecane na początek)
|
||||
|
||||
### Krok 5: Testy
|
||||
- Napisz test jednostkowy dla nowej funkcji
|
||||
- Sprawdź czy stare wywołania działają
|
||||
|
||||
## Status migracji
|
||||
|
||||
### ✅ Zmigrowane moduły
|
||||
- **Cache** (częściowo)
|
||||
- ✅ CacheHandler - ma delete/deletePattern
|
||||
- ✅ RedisConnection - singleton
|
||||
- ✅ S::clear_product_cache() - nowa metoda
|
||||
|
||||
### 🔄 Status modułów
|
||||
- **Product**
|
||||
- ✅ get_product_quantity() - **ZMIGROWANE** (2025-02-05) 🎉
|
||||
- Nowa klasa: `Domain\Product\ProductRepository::getQuantity()`
|
||||
- Fasada w: `shop\Product::get_product_quantity()`
|
||||
- Test: `tests/Unit/Domain/Product/ProductRepositoryTest.php`
|
||||
- Testy: ✅ 5/5 przechodzą
|
||||
- Aktualizacja: ver. 0.238
|
||||
- Użycie DI: ✅ Konstruktor przyjmuje `$db`
|
||||
- ✅ get_product_price() - **ZMIGROWANE** (2026-02-05) 🎉
|
||||
- Nowa metoda: `Domain\Product\ProductRepository::getPrice()`
|
||||
- Fasada w: `shop\Product::get_product_price()`
|
||||
- Testy: ✅ 4 nowe testy (cena regularna, promocyjna, promo wyższa, nie znaleziono)
|
||||
- Użycie: `front\factory\ShopPromotion` (linia 132)
|
||||
- Aktualizacja: ver. 0.239
|
||||
- ✅ get_product_name() - **ZMIGROWANE** (2026-02-05) 🎉
|
||||
- Nowa metoda: `Domain\Product\ProductRepository::getName()`
|
||||
- Fasada w: `shop\Product::get_product_name()`
|
||||
- Testy: ✅ 2 nowe testy (nazwa znaleziona, nie znaleziona)
|
||||
- Użycie: brak aktywnych wywołań (przygotowane na przyszłość)
|
||||
- Aktualizacja: ver. 0.239
|
||||
- ✅ archive() / unarchive() - **ZMIGROWANE** (2026-02-06) 🎉
|
||||
- Nowe metody: `Domain\Product\ProductRepository::archive()`, `unarchive()`
|
||||
- Nowy kontroler: `admin\Controllers\ProductArchiveController` (DI, instancyjny)
|
||||
- Szablony: `admin/templates/product-archive/` (rename z `archive/`, dawniej `product_archive/`)
|
||||
- Testy: ✅ 4 nowe testy repozytorium + 6 testów kontrolera
|
||||
- FIX: SQL bug w `ajax_products_list_archive()` (puste wyszukiwanie + brak `archive = 1`)
|
||||
- Dalsze porządki: ver. 0.252 (nowy table-list, wydzielony custom script, usunięte legacy pliki)
|
||||
- Aktualizacja: ver. 0.241 / 0.252
|
||||
- [ ] is_product_on_promotion() - NASTĘPNA 👉
|
||||
|
||||
- **Banner** (DEMO pełnej migracji kontrolera)
|
||||
- ✅ BannerRepository - **ZMIGROWANE** (2026-02-05) 🎉
|
||||
- Nowa klasa: `Domain\Banner\BannerRepository` (find, delete, save, saveTranslations)
|
||||
- Nowy kontroler: `admin\Controllers\BannerController` (DI, instancyjny)
|
||||
- Router: `admin\Site::route()` → sprawdza nowy kontroler → fallback na stary
|
||||
- Testy: ✅ 4 testy (find z tłumaczeniami, not found, delete, save)
|
||||
- Stary kontroler `admin\controls\Banners` działa jako niezależny fallback
|
||||
- Stara factory `admin\factory\Banners` zachowana bez zmian (fallback)
|
||||
- Aktualizacja: ver. 0.239
|
||||
|
||||
- **Articles** (migracja kontrolera - etap edit/details)
|
||||
- ✅ ArticleRepository::find() - **ZMIGROWANE** (2026-02-06) 🎉
|
||||
- Nowa klasa: `Domain\Article\ArticleRepository` (find: artykul + relacje)
|
||||
- Nowy kontroler: `admin\Controllers\ArticlesController` (DI, instancyjny)
|
||||
- Zmigrowana akcja: `article_edit` -> `edit` (mapowanie w `admin\Site::$actionMap`)
|
||||
- Kompatybilność: `admin\factory\Articles::article_details()` deleguje do nowego repozytorium
|
||||
- Legacy cleanup: metody przejęte przez nowe kontrolery oznaczone `@deprecated` w `admin\controls\Articles|Banners|Settings`
|
||||
- Testy repozytorium rozszerzone o czyszczenie nieprzypisanych plik<69>w/zdj<64><6A>
|
||||
- Aktualizacja: ver. 0.243
|
||||
- ✅ ArticleRepository::save() - **ZMIGROWANE** (2026-02-06) 🎉
|
||||
- Metoda `save()` z prywatnych helperow (buildArticleRow, buildLangRow, saveTranslations, savePages, assignTempFiles, assignTempImages, deleteMarkedFiles, deleteMarkedImages)
|
||||
- Zmigrowana akcja: `article_save` -> `save` (mapowanie w `admin\Site::$actionMap`)
|
||||
- Kompatybilnosc: `admin\factory\Articles::article_save()` deleguje do repozytorium
|
||||
- Testy: 7 nowych testow save (create, update, translations, pages, marked delete)
|
||||
- Aktualizacja: ver. 0.244
|
||||
- ✅ ArticleRepository::archive() - **ZMIGROWANE** (2026-02-06) 🎉
|
||||
- Metoda `archive()` (ustawia status = -1)
|
||||
- Zmigrowana akcja: `article_delete` -> `delete` (mapowanie w `admin\Site::$actionMap`)
|
||||
- Kompatybilnosc: `admin\factory\Articles::articles_set_archive()` deleguje do repozytorium
|
||||
- Testy: 2 nowe testy archive (success, failure)
|
||||
- Aktualizacja: ver. 0.245
|
||||
- ✅ ArticlesController::browseList() - **ZMIGROWANE** (2026-02-07) 🎉
|
||||
- Nowa metoda kontrolera: `browseList()` (DI, instancyjna)
|
||||
- Zmigrowana akcja: `browse_list` -> `browseList` (mapowanie w `admin\Site::$actionMap`)
|
||||
- Legacy cleanup: usuniety `autoload/admin/controls/class.Articles.php` (brak fallback dla modułu Articles)
|
||||
- Testy: 2 nowe testy kontraktu kontrolera (method exists + return type)
|
||||
- ✅ ArticlesController::galleryOrderSave() - **ZMIGROWANE** (2026-02-07) 🎉
|
||||
- Nowa metoda kontrolera: `galleryOrderSave()` (AJAX)
|
||||
- Zmigrowana akcja: `gallery_order_save` -> `galleryOrderSave` (mapowanie w `admin\Site::$actionMap`)
|
||||
- Implementacja: używa `Domain\Article\ArticleRepository::saveGalleryOrder()`
|
||||
- Testy: 2 nowe testy kontraktu kontrolera (method exists + return type)
|
||||
- ✅ Usuniecie legacy kontrolera Articles - **ZMIGROWANE** (2026-02-07) 🎉
|
||||
- Usuniety plik: `autoload/admin/controls/class.Articles.php`
|
||||
- Wymaganie dla aktualizacji: dodac wpis do `ver_X.XXX_files.txt`
|
||||
- Wpis do usuniecia: `F: ../autoload/admin/controls/class.Articles.php`
|
||||
- ✅ Stabilizacja generatora `.htaccess` - **ZMIGROWANE** (2026-02-07) 🎉
|
||||
- FIX: regula admin ma `QSA` (query string dla sortowania/filtrow)
|
||||
- FIX: `\S::htacces()` komentuje `AddHandler|SetHandler|ForceType` dla zgodnosci z hostingiem
|
||||
- UPDATE: `libraries/htaccess.conf` zaktualizowany, aby poprawki nie znikaly po regeneracji
|
||||
|
||||
- **Settings** (migracja kontrolera)
|
||||
- ✅ SettingsRepository - **ZMIGROWANE** (2026-02-05) 🎉
|
||||
- Nowa klasa: `Domain\Settings\SettingsRepository` (saveSettings, getSettings)
|
||||
- Aktualny stan: bezpośredni dostęp do DB (usunięta delegacja do `admin\factory\Settings`) - ver. 0.250
|
||||
- Nowy kontroler: `admin\Controllers\SettingsController` (DI, instancyjny)
|
||||
- Testy: ✅ 3 testy (instancja, metody)
|
||||
- Stary kontroler `admin\controls\Settings` zachowany jako fallback
|
||||
- Aktualizacja: ver. 0.240 / 0.250
|
||||
- ✅ CacheRepository - **ZMIGROWANE** (2026-02-05) 🎉
|
||||
- Nowa klasa: `Domain\Cache\CacheRepository` (clearCache)
|
||||
- Używa `\S::delete_dir()` + `\RedisConnection`
|
||||
- Testy: ✅ 4 testy (z Redis, bez Redis, niedostępny, struktura)
|
||||
- Aktualizacja: ver. 0.240
|
||||
|
||||
- **Dictionaries** (migracja kontrolera i repozytorium)
|
||||
- ✅ DictionariesRepository - **ZMIGROWANE** (2026-02-09) 🎉
|
||||
- Nowa klasa: `Domain\Dictionaries\DictionariesRepository` (listForAdmin, find, save, delete, allUnits)
|
||||
- Nowy kontroler: `admin\Controllers\DictionariesController` (DI, instancyjny)
|
||||
- Migracja na nowe komponenty: `components/table-list` + `components/form-edit`
|
||||
- Legacy cleanup: usunięto klasy z `admin\controls`, `admin\factory`, `front\factory`
|
||||
- Aktualizacja: ver. 0.251
|
||||
|
||||
- **Filemanager** (migracja routingu)
|
||||
- ✅ FilemanagerController - **ZMIGROWANE** (2026-02-10) 🎉
|
||||
- Nowy kontroler: `admin\Controllers\FilemanagerController`
|
||||
- Naprawa błędu: `Invalid Key`
|
||||
- Legacy cleanup: usunięto `autoload/admin/controls/class.Filemanager.php` i `autoload/admin/view/class.FileManager.php`
|
||||
- Aktualizacja: ver. 0.252
|
||||
|
||||
- **Users** (migracja kontrolera i repozytorium)
|
||||
- ✅ UserRepository - **ZMIGROWANE** (2026-02-12) 🎉
|
||||
- Nowa klasa: `Domain\User\UserRepository` (find, getById, save, delete, checkLogin, logon, details, updateById, sendTwofaCode, verifyTwofaCode)
|
||||
- Nowy kontroler: `admin\Controllers\UsersController` (DI, instancyjny: view_list, user_edit, user_save, user_delete, login_form, twofa)
|
||||
- Router: `admin\Site` - factory wpis dla modulu `Users`
|
||||
- Fasada: `admin\factory\Users` deleguje do repozytorium (backward compatibility dla login/2FA flow)
|
||||
- AJAX: `admin/ajax/users.php` - `check_login` oparty o `UserRepository`
|
||||
- Legacy cleanup: usuniety `autoload/admin/controls/class.Users.php`
|
||||
- Testy: 25 testow repozytorium (CRUD, logon, 2FA, checkLogin) + 12 testow kontrolera (kontrakty + normalizeUser)
|
||||
|
||||
- **Integrations** (migracja kontrolera i repozytorium + cleanup Sellasist/Baselinker)
|
||||
- ✅ IntegrationsRepository - **ZMIGROWANE** (2026-02-13) 🎉
|
||||
- Nowa klasa: `Domain\Integrations\IntegrationsRepository` (settings Apilo/ShopPRO, OAuth, product linking, API fetch)
|
||||
- Nowy kontroler: `admin\Controllers\IntegrationsController` (DI, instancyjny)
|
||||
- Router: `admin\Site` - factory wpis dla modulu `Integrations`
|
||||
- Fasada: `admin\factory\Integrations` deleguje do repozytorium (tylko Apilo + ShopPRO)
|
||||
- **CLEANUP:** usunieto integracje Sellasist i Baselinker z calego projektu
|
||||
- Usuniete pliki: `controls/Integrations`, `controls/Baselinker`, `factory/Baselinker`, `front/factory/Shop`, `shop/ShopStatus`, szablony sellasist/baselinker
|
||||
- Wyczyszczone referencje w: cron.php, Order, ShopStatuses, ShopTransport, ShopPaymentMethod, ShopProduct, config.php, front/factory/*
|
||||
- Testy: 16 nowych testow (repozytorium) + 10 testow kontrolera
|
||||
- Aktualizacja: ver. 0.263
|
||||
|
||||
### 📋 Do zrobienia
|
||||
- Order
|
||||
- Category
|
||||
- ShopAttribute
|
||||
- ShopProduct (factory)
|
||||
|
||||
## Testowanie
|
||||
|
||||
### Framework: PHPUnit
|
||||
Instalacja:
|
||||
```bash
|
||||
composer require --dev phpunit/phpunit
|
||||
```
|
||||
|
||||
### Struktura testów
|
||||
```
|
||||
tests/
|
||||
├── Unit/
|
||||
│ ├── Domain/
|
||||
│ │ ├── Article/ArticleRepositoryTest.php
|
||||
│ │ ├── Banner/BannerRepositoryTest.php
|
||||
│ │ ├── Cache/CacheRepositoryTest.php
|
||||
│ │ ├── Dictionaries/DictionariesRepositoryTest.php
|
||||
│ │ ├── Product/ProductRepositoryTest.php
|
||||
│ │ ├── Settings/SettingsRepositoryTest.php
|
||||
│ │ ├── User/UserRepositoryTest.php
|
||||
│ │ └── Integrations/IntegrationsRepositoryTest.php
|
||||
│ └── admin/
|
||||
│ └── Controllers/
|
||||
│ ├── ArticlesControllerTest.php
|
||||
│ ├── DictionariesControllerTest.php
|
||||
│ ├── IntegrationsControllerTest.php
|
||||
│ ├── ProductArchiveControllerTest.php
|
||||
│ ├── SettingsControllerTest.php
|
||||
│ └── UsersControllerTest.php
|
||||
└── Integration/
|
||||
```
|
||||
**Łącznie: 212 testów, 577 asercji**
|
||||
|
||||
### Przykład testu
|
||||
```php
|
||||
// tests/Unit/Domain/Product/ProductRepositoryTest.php
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Domain\Product\ProductRepository;
|
||||
|
||||
class ProductRepositoryTest extends TestCase {
|
||||
public function testGetQuantityReturnsCorrectValue() {
|
||||
// Arrange
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->method('get')->willReturn(10);
|
||||
|
||||
$repo = new ProductRepository($mockDb);
|
||||
|
||||
// Act
|
||||
$quantity = $repo->getQuantity(123);
|
||||
|
||||
// Assert
|
||||
$this->assertEquals(10, $quantity);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Zasady kodu
|
||||
|
||||
### 1. SOLID Principles
|
||||
- **S**ingle Responsibility - jedna klasa = jedna odpowiedzialność
|
||||
- **O**pen/Closed - otwarty na rozszerzenia, zamknięty na modyfikacje
|
||||
- **L**iskov Substitution - podklasy mogą zastąpić nadklasy
|
||||
- **I**nterface Segregation - wiele małych interfejsów
|
||||
- **D**ependency Inversion - zależności od abstrakcji
|
||||
|
||||
### 2. Nazewnictwo
|
||||
- **Entity** - `Product.php` (reprezentuje obiekt domenowy)
|
||||
- **Repository** - `ProductRepository.php` (dostęp do danych)
|
||||
- **Service** - `ProductService.php` (logika biznesowa)
|
||||
- **Controller** - `ProductController.php` (obsługa requestów)
|
||||
|
||||
### 3. Type Hinting
|
||||
```php
|
||||
// ✅ DOBRE
|
||||
public function getQuantity(int $id): ?int {
|
||||
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
|
||||
// ❌ ZŁE
|
||||
public function getQuantity($id) {
|
||||
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
```
|
||||
|
||||
## Narzędzia pomocnicze
|
||||
|
||||
### Autoloader (produkcja)
|
||||
Autoloader w 9 entry pointach obsługuje dwie konwencje:
|
||||
1. `autoload/{namespace}/class.{ClassName}.php` (legacy)
|
||||
2. `autoload/{namespace}/{ClassName}.php` (PSR-4, fallback)
|
||||
|
||||
Entry pointy: `index.php`, `ajax.php`, `api.php`, `cron.php`, `cron-turstmate.php`, `download.php`, `admin/index.php`, `admin/ajax.php`, `cron/cron-xml.php`
|
||||
|
||||
### Static Analysis
|
||||
```bash
|
||||
composer require --dev phpstan/phpstan
|
||||
vendor/bin/phpstan analyse autoload/Domain
|
||||
```
|
||||
|
||||
## Kolejność refaktoryzacji (priorytet)
|
||||
|
||||
1. **Cache** ✅
|
||||
2. **Product** (w trakcie)
|
||||
- ✅ getQuantity (ver. 0.238)
|
||||
- ✅ getPrice (ver. 0.239)
|
||||
- ✅ getName (ver. 0.239)
|
||||
- ✅ archive / unarchive (ver. 0.241)
|
||||
- [ ] is_product_on_promotion - NASTĘPNA 👉
|
||||
- [ ] getFromCache
|
||||
- [ ] getProductImg
|
||||
3. **Banner** ✅ (pełna migracja kontrolera, ver. 0.239)
|
||||
4. **Settings** ✅ (pełna migracja repo/kontrolera + cleanup legacy, ver. 0.250)
|
||||
5. **Dictionaries** ✅ (repo + kontroler + form/table, ver. 0.251)
|
||||
6. **ProductArchive** ✅ (migracja kontrolera + cleanup szablonów, ver. 0.252)
|
||||
7. **Filemanager** ✅ (migracja routingu + fix `Invalid Key`, ver. 0.252)
|
||||
8. **Users** ✅ (repo + kontroler + 2FA + legacy cleanup, ver. 0.253)
|
||||
9. **Pages** ✅ (repo + kontroler + drzewo stron + AJAX endpoints, ver. 0.262)
|
||||
10. **Integrations** ✅ (repo + kontroler + cleanup Sellasist/Baselinker, ver. 0.263)
|
||||
11. **Order**
|
||||
12. **Category**
|
||||
13. **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/formularze (Product, Category, Pages, itd.)
|
||||
|
||||
---
|
||||
*Rozpoczęto: 2025-02-05*
|
||||
*Ostatnia aktualizacja: 2026-02-12*
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
## Aktualizacja 2026-02-12 - Users
|
||||
|
||||
### Users (migracja kontrolera i repozytorium)
|
||||
- **NOWE:** `Domain\User\UserRepository` (delete, find, save, checkLogin, logon, details, sendTwofaCode, verifyTwofaCode)
|
||||
- **NOWE:** `admin\Controllers\UsersController` (view_list, user_edit, user_save, user_delete)
|
||||
- **UPDATE:** Router `admin\Site` - nowy kontroler DI dla modu<64>u `Users`
|
||||
- **UPDATE:** `admin\factory\Users` jako fasada delegujaca do repozytorium
|
||||
- **UPDATE:** `admin/ajax/users.php` - endpoint `check_login` oparty o `UserRepository`
|
||||
- Testy po zmianie: 95 tests, 204 assertions
|
||||
- **UPDATE:** UsersController: `view_list` + `user_edit` migrowane na nowy system list/form (table-list + form-edit)
|
||||
|
||||
## Aktualizacja 2026-02-12 (finalizacja Users)
|
||||
- Users: pelna migracja na nowa architekture (Domain + DI Controller), bez fallbacku do legacy kontrolera/factory/view.
|
||||
- `UsersController` obsluguje: `list/view_list`, `user_edit`, `user_save`, `user_delete`, `login_form`, `twofa`.
|
||||
- Dodano walidacje warunkowa: `twofa_email` wymagany gdy `twofa_enabled = 1`.
|
||||
- Widoki users migrowane z `grid/gridEdit` na `table-list` i `form-edit`.
|
||||
|
||||
## Aktualizacja 2026-02-12 - Languages
|
||||
- **NOWE:** `Domain\\Languages\\LanguagesRepository` (languages + translations CRUD/list)
|
||||
- **NOWE:** `admin\\Controllers\\LanguagesController` (DI)
|
||||
- **UPDATE:** `admin\\Site` - nowy kontroler DI dla modulu `Languages`
|
||||
- **UPDATE:** `admin\\factory\\Languages` jako fasada delegujaca do repozytorium
|
||||
- **UPDATE:** widoki `languages/*` migrowane na `components/table-list` i `components/form-edit`
|
||||
- **CLEANUP:** usunieto legacy `admin\\controls\\Languages` i `admin\\view\\Languages`
|
||||
- Testy po zmianie: 130 tests, 301 assertions
|
||||
|
||||
## Aktualizacja 2026-02-12 (Languages final)
|
||||
- **NOWE:** `Domain\\Languages\\LanguagesRepository` (list/save/delete dla jezykow i tlumaczen)
|
||||
- **NOWE:** `admin\\Controllers\\LanguagesController` (DI) dla akcji `view_list/list`, `language_*`, `translation_*`
|
||||
- **UPDATE:** `admin\\factory\\Languages` jako fasada delegujaca do repozytorium
|
||||
- **CLEANUP:** usunieto legacy `admin\\controls\\Languages` oraz `admin\\view\\Languages`
|
||||
- **UPDATE:** poprawki globalne `components/table-list` dla krotkich kolumn/filtr<74>w
|
||||
|
||||
## Aktualizacja 2026-02-12 (ver. 0.255)
|
||||
- UPDATE: SettingsController, BannerController, DictionariesController, ArticlesController pobieraja liste jezykow przez Domain/Languages/LanguagesRepository (DI) zamiast legacy admin/factory/Languages.
|
||||
- UPDATE: router DI (admin/Site) przekazuje LanguagesRepository do kontrolerow Articles, Banners, Settings, Dictionaries.
|
||||
- UPDATE: pozostale aktywne odwolania legacy (admin/controls, admin/factory/Shop*) zostaly przepiete na LanguagesRepository.
|
||||
- FIX: autoload/admin/factory/class.Languages.php poprawione na <?php (zgodnosc z short_open_tag=Off).
|
||||
- Testy po zmianie: 130 tests, 303 assertions.
|
||||
## Aktualizacja 2026-02-12 (ver. 0.256)
|
||||
- **Layouts** - **ZMIGROWANE** (2026-02-12) ??
|
||||
- NOWE: `Domain\\Layouts\\LayoutsRepository` (find, save, delete, listForAdmin, menusWithPages, categoriesTree)
|
||||
- NOWE: `admin\\Controllers\\LayoutsController` (DI)
|
||||
- UPDATE: lista `/admin/layouts/view_list/` migrowana na `components/table-list`
|
||||
- UPDATE: widok `layouts/layout-edit` korzysta z danych dostarczonych przez repozytorium (bez wywolan legacy factory)
|
||||
- NOWE: partial `admin/templates/layouts/subcategories-list.php`
|
||||
- CLEANUP: usuniete `autoload/admin/controls/class.Layouts.php` i `autoload/admin/view/class.Layouts.php`
|
||||
- KOMPATYBILNOSC: `autoload/admin/factory/class.Layouts.php` deleguje do repozytorium
|
||||
|
||||
- **Languages**
|
||||
- UPDATE: `Domain\\Languages\\LanguagesRepository::defaultLanguageId()` jako wspolna metoda do pobierania jezyka domyslnego
|
||||
|
||||
- **Articles**
|
||||
- UPDATE: `admin\\Controllers\\ArticlesController` korzysta z `Domain\\Layouts\\LayoutsRepository` (DI) dla listy layoutow
|
||||
|
||||
- Testy po zmianie: **141 tests, 336 assertions**
|
||||
|
||||
## Aktualizacja 2026-02-12 (ver. 0.257)
|
||||
- **Newsletter** - **ZMIGROWANE** (2026-02-12)
|
||||
- NOWE: `Domain\\Newsletter\\NewsletterRepository` (listy admin, szablony, ustawienia, kolejka wysylki)
|
||||
- NOWE: `Domain\\Newsletter\\NewsletterPreviewRenderer` (wspolny render podgladu)
|
||||
- NOWE: `admin\\Controllers\\NewsletterController` (DI)
|
||||
- UPDATE: routing DI (`admin\\Site`) rozszerzony o modul `Newsletter`
|
||||
- UPDATE: widoki `/admin/newsletter/*` migrowane na `components/table-list` i `components/form-edit`
|
||||
- UPDATE: `admin\\factory\\Newsletter` jako fasada do repozytorium
|
||||
- UPDATE: `front\\factory\\Newsletter` bez zaleznosci od `admin\\view\\Newsletter`
|
||||
- CLEANUP: usuniete `autoload/admin/controls/class.Newsletter.php`, `autoload/admin/view/class.Newsletter.php`
|
||||
|
||||
- Testy po zmianie: **150 tests, 372 assertions**
|
||||
|
||||
## Aktualizacja 2026-02-12 (ver. 0.258)
|
||||
- **Newsletter**
|
||||
- UPDATE: tymczasowo wylaczono flow `prepare/send/preview` (wymaga przebudowy).
|
||||
- UPDATE: tymczasowo wylaczono modul `Szablony uzytkownika`.
|
||||
- UPDATE: aktywna obsluga tylko szablonow administracyjnych (`is_admin = 1`).
|
||||
- CLEANUP: usuniete nieuzywane widoki `prepare.php`, `preview.php`, `email-templates-user.php`.
|
||||
|
||||
## Aktualizacja 2026-02-12 (ver. 0.259)
|
||||
- **Scontainers** - **ZMIGROWANE** (2026-02-12)
|
||||
- NOWE: `Domain\Scontainers\ScontainersRepository` (listForAdmin, find, save, delete, detailsForLanguage)
|
||||
- NOWE: `admin\Controllers\ScontainersController` (DI)
|
||||
- UPDATE: `/admin/scontainers/view_list/` migrowane na `components/table-list`
|
||||
- UPDATE: `/admin/scontainers/container_edit/` migrowane na `components/form-edit`
|
||||
- UPDATE: `admin\factory\Scontainers` jako fasada do repozytorium
|
||||
- UPDATE: `front\factory\Scontainers` korzysta z repozytorium domenowego
|
||||
- CLEANUP: usuniete `autoload/admin/controls/class.Scontainers.php` i `autoload/admin/view/class.Scontainers.php`
|
||||
- Testy po zmianie: **158 tests, 397 assertions**
|
||||
|
||||
## Aktualizacja 2026-02-12 (ver. 0.260)
|
||||
- **ArticlesArchive** - **ZMIGROWANE** (2026-02-12)
|
||||
- NOWE: `admin\Controllers\ArticlesArchiveController` (DI)
|
||||
- UPDATE: `Domain\Article\ArticleRepository` rozszerzone o `listArchivedForAdmin()`, `restore()`, `deletePermanently()`
|
||||
- UPDATE: `/admin/articles_archive/view_list/` migrowane na `components/table-list`
|
||||
- UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ArticlesArchive` + mapowanie `article_restore -> restore`
|
||||
- CLEANUP: usuniete `autoload/admin/controls/class.ArticlesArchive.php`, `autoload/admin/factory/class.ArticlesArchive.php`, `autoload/admin/view/class.ArticlesArchive.php`
|
||||
- Testy po zmianie: **165 tests, 424 assertions**
|
||||
|
||||
## Plan 2026-02-13 - Refaktoryzacja `/admin/articles/`
|
||||
- [ ] Przeniesc zaleznosci listy artykulow z `admin\factory\Articles` do `Domain\Article\ArticleRepository` (etykiety stron, operacje pomocnicze).
|
||||
- [ ] Dodac akcje routowane przez `admin\Controllers\ArticlesController` dla operacji AJAX (`article_image_alt_change`, `article_file_name_change`, `article_image_delete`, `article_file_delete`).
|
||||
- [ ] Przepiac widok `admin/templates/articles/article-edit.php` z `/admin/ajax.php` na endpointy `/admin/articles/*`.
|
||||
- [ ] Usunac legacy `admin\view\Articles` i zastapic rekurencje podstron przez `Tpl::view('articles/subpages-list', ...)`.
|
||||
- [ ] Usunac `admin/ajax/articles.php` oraz odpiac include z `admin/ajax.php`.
|
||||
- [ ] Przeszukac projekt pod pozostale zaleznosci i uruchomic testy modulu Articles.
|
||||
|
||||
## Aktualizacja 2026-02-13 (ver. 0.261)
|
||||
- **Articles** - dalsza refaktoryzacja `/admin/articles/`
|
||||
- UPDATE: `Domain\Article\ArticleRepository` rozszerzone o metody UI/admin: `pagesSummaryForArticles()`, `updateImageAlt()`, `updateFileName()`, `markImageToDelete()`, `markFileToDelete()`.
|
||||
- UPDATE: `admin\Controllers\ArticlesController` obsluguje nowe akcje routingu: `article_image_alt_change`, `article_file_name_change`, `article_image_delete`, `article_file_delete`.
|
||||
- UPDATE: lista artykulow (`list`) nie korzysta juz z `admin\factory\Articles::article_pages()`.
|
||||
- UPDATE: `admin/templates/articles/article-edit.php` przepiete z `/admin/ajax.php?a=article_*` na endpointy `/admin/articles/article_*/`.
|
||||
- UPDATE: rekurencja podstron w widoku oparta o `Tpl::view('articles/subpages-list', ...)` (bez `admin\view\Articles`).
|
||||
- CLEANUP: usuniete legacy pliki `autoload/admin/view/class.Articles.php` oraz `admin/ajax/articles.php`; `admin/ajax.php` nie includuje juz `ajax/articles.php`.
|
||||
- Testy po zmianie: **176 tests, 439 assertions**.
|
||||
|
||||
## Aktualizacja 2026-02-13 (ver. 0.261)
|
||||
- **Articles (/admin/articles)**
|
||||
- UPDATE: `Domain\Article\ArticleRepository` rozszerzone o `saveFilesOrder()` oraz zapis `files_order` przy `save()` (eliminuje koniecznosc drugiego zapisu po sortowaniu).
|
||||
- UPDATE: routing DI (`admin\Site`) rozszerzony o mapowanie `files_order_save -> filesOrderSave`.
|
||||
- UPDATE: `admin\Controllers\ArticlesController` - nowa akcja AJAX `filesOrderSave`.
|
||||
- UPDATE: widok `admin/templates/articles/article-edit-custom-script.php` - drag&drop dla listy zalacznikow + hidden input `files_order`.
|
||||
- UPDATE: potwierdzenia usuwania zdjec i zalacznikow przepiete na `jquery-confirm` ze stylem `table-list-confirm-dialog` (jak na liscie artykulow).
|
||||
- FIX: dolaczona biblioteka `jquery-impromptu` w widoku edycji artykulu dla kompatybilnosci.
|
||||
- Testy po zmianie: **178 tests, 443 assertions**.
|
||||
|
||||
## Plan 2026-02-13 - Refaktoryzacja `/admin/pages/`
|
||||
- [x] Dodac `Domain\Pages\PagesRepository` (CRUD menu/stron, drzewo stron, sortowanie, SEO, operacje AJAX).
|
||||
- [x] Dodac `admin\Controllers\PagesController` (DI) i przepiac routing `/admin/pages/*` na nowy kontroler.
|
||||
- [x] Przebudowac widoki `admin/templates/pages/*` tak, aby nie korzystaly z `admin\factory\Pages` i `admin\view\Pages`.
|
||||
- [x] Przepiac endpointy AJAX z `/admin/ajax.php?a=*` na `/admin/pages/*` (`save_pages_order`, `save_articles_order`, `generate_seo_link`, `cookie_*`).
|
||||
- [x] Przeszukac i zaktualizowac zaleznosci w innych modulach (`articles`, `layouts`, helpery) powiazane z Pages.
|
||||
- [x] Usunac legacy klasy/pliki Pages (`autoload/admin/controls/class.Pages.php`, `autoload/admin/view/class.Pages.php`, `autoload/admin/factory/class.Pages.php`, `admin/ajax/pages.php`) po odpieciu zaleznosci.
|
||||
- [x] Dodac/uzupelnic testy (`PagesRepository`, `PagesController`) i uruchomic testy.
|
||||
|
||||
## Aktualizacja 2026-02-13 - Pages (/admin/pages)
|
||||
- NOWE: `Domain\Pages\PagesRepository` (CRUD menu/stron, drzewo stron, porzadkowanie, SEO link, URL preview, cookies tree-state).
|
||||
- NOWE: `admin\Controllers\PagesController` (DI) dla akcji: `view_list/list`, `browse_list`, `pages_url_browser`, `menu_*`, `page_*`, `save_*_order`, `generate_seo_link`, `cookie_*`.
|
||||
- UPDATE: `/admin/pages/*` dziala bez legacy `admin\controls\Pages` i `admin\view\Pages`.
|
||||
- UPDATE: widoki `admin/templates/pages/*` przepiete na dane z kontrolera/repozytorium (bez `admin\factory\Pages`).
|
||||
- UPDATE: endpointy zalezne od Pages w innych modulach (`articles`, `layouts`, `shop-category`, `shop-product`) przepiete z `admin/ajax.php?a=*` na `/admin/pages/*`.
|
||||
- CLEANUP: usuniete `autoload/admin/controls/class.Pages.php`, `autoload/admin/view/class.Pages.php`, `autoload/admin/factory/class.Pages.php`, `admin/ajax/pages.php`; `admin/ajax.php` nie includuje juz `ajax/pages.php`.
|
||||
- Testy: **OK (186 tests, 478 assertions)**.
|
||||
|
||||
## Aktualizacja 2026-02-13 - Integrations (/admin/integrations)
|
||||
- NOWE: `Domain\Integrations\IntegrationsRepository` (settings Apilo/ShopPRO, OAuth, product linking, API fetch lists, product search/create, ShopPRO import).
|
||||
- NOWE: `admin\Controllers\IntegrationsController` (DI) dla akcji: `apilo_settings`, `apilo_settings_save`, `apilo_authorization`, `get_platform_list`, `get_status_types_list`, `get_carrier_account_list`, `get_payment_types_list`, `apilo_create_product`, `apilo_product_search`, `apilo_product_select_save`, `apilo_product_select_delete`, `shoppro_settings`, `shoppro_settings_save`, `shoppro_product_import`.
|
||||
- UPDATE: `admin\factory\Integrations` jako fasada delegujaca do `Domain\Integrations\IntegrationsRepository` (tylko Apilo + ShopPRO).
|
||||
- **CLEANUP: usunieto integracje Sellasist i Baselinker z calego projektu:**
|
||||
- Usuniete klasy: `admin\controls\Integrations`, `admin\controls\Baselinker`, `admin\factory\Baselinker`, `front\factory\Shop`, `shop\ShopStatus`
|
||||
- Usuniete szablony: `integrations/sellasist-settings.php`, `integrations/baselinker-settings.php`, `admin/templates/baselinker/`
|
||||
- Wyczyszczone referencje w: `cron.php`, `cron/cron-xml.php`, `shop\Order`, `admin\controls\ShopStatuses`, `admin\controls\ShopTransport`, `admin\controls\ShopPaymentMethod`, `admin\controls\ShopProduct`, `admin\factory\ShopStatuses`, `admin\factory\ShopTransport`, `admin\factory\ShopProduct`, `front\factory\ShopStatuses`, `front\factory\ShopTransport`, `front\factory\ShopPaymentMethod`, `front\factory\ShopProduct`, `front\factory\ShopOrder`, `shop\Product`, `config.php`
|
||||
- Wyczyszczone szablony: `shop-statuses/*`, `shop-transport/*`, `shop-payment-method/*`, `shop-product/*`, `site/main-layout.php`
|
||||
- Testy: **OK (212 tests, 577 assertions)**.
|
||||
|
||||
## Plan 2026-02-13 - Refaktoryzacja `/admin/shop_promotion/` (HITL)
|
||||
- [x] Etap 1 (analiza i kontrakt): potwierdzic docelowy kontrakt URL i kompatybilnosc wsteczna:
|
||||
- kontrakt docelowy: tylko `/admin/shop_promotion/list/`, `/admin/shop_promotion/edit/`, `/admin/shop_promotion/save/`, `/admin/shop_promotion/delete/`
|
||||
- brak kompatybilnosci ze starymi URL i aliasami akcji (`view_list`, `promotion_delete`)
|
||||
- [x] Etap 2 (Domain): dodac `Domain\Promotion\PromotionRepository`:
|
||||
- `listForAdmin(filters, sort, dir, page, perPage)` z whitelist sortowania i bind params
|
||||
- `find(int $id)` + domyslne dane dla nowego formularza
|
||||
- `save(array $data): ?int` (insert/update, normalizacja switchy, JSON dla kategorii)
|
||||
- `delete(int $id): bool`
|
||||
- `categoriesTree(?int $parentId): array` (drzewo kategorii z tlumaczeniami, bez zaleznosci od `admin\factory\ShopCategory`)
|
||||
- [x] Etap 3 (Admin Controller + routing DI): dodac `admin\Controllers\ShopPromotionController` i przepiac routing:
|
||||
- rejestracja factory w `admin\Site::$newControllers` pod modulem `ShopPromotion`
|
||||
- akcje: `list`, `edit`, `save`, `delete`
|
||||
- zachowac obsluge legacy payload (`values` JSON) oraz obsluge `form-edit` (`$_POST`)
|
||||
- [x] Etap 4 (widoki): przepiac modul z `grid/gridEdit` na nowe komponenty:
|
||||
- nowy widok listy oparty o `components/table-list` (filtry: nazwa, aktywny)
|
||||
- nowy widok edycji oparty o `components/form-edit` (+ pola custom dla drzew kategorii)
|
||||
- nowe partiale dla drzewa kategorii w module `shop-promotion` (usuniecie zaleznosci od `shop-product/subcategories-list`)
|
||||
- nowy `shop-promotion/promotion-edit-custom-script.php` (warunkowe pola po `condition_type`, obsluga drzewa kategorii)
|
||||
- [x] Etap 5 (zaleznosci i cleanup): przeszukac i odpiac legacy zaleznosci:
|
||||
- menu admin: link kanoniczny na `/admin/shop_promotion/list/`
|
||||
- usunac legacy pliki po pelnym przepieciu:
|
||||
- `autoload/admin/controls/class.ShopPromotion.php`
|
||||
- `autoload/admin/factory/class.ShopPromotion.php`
|
||||
- `admin/templates/shop-promotion/view-list.php` (grid)
|
||||
- `admin/templates/shop-promotion/promotion-edit.php` (gridEdit)
|
||||
- sprawdzic pozostale odwolania `ShopPromotion` i `shop_promotion/view_list` w calym repo
|
||||
- [x] Etap 6 (testy): dodac/uzupelnic testy:
|
||||
- `tests/Unit/Domain/Promotion/PromotionRepositoryTest.php`
|
||||
- `tests/Unit/admin/Controllers/ShopPromotionControllerTest.php`
|
||||
- uruchomic minimum: nowe testy modulu + pelny `composer test`
|
||||
- [x] Etap 7 (dokumentacja po wdrozeniu): zaktualizowac:
|
||||
- `DATABASE_STRUCTURE.md` (dodac `pp_shop_promotion`, jesli nadal brak)
|
||||
- `PROJECT_STRUCTURE.md`
|
||||
- `REFACTORING_PLAN.md` (sekcja "Aktualizacja ...")
|
||||
- `TESTING.md` (nowy wynik suite)
|
||||
|
||||
## Aktualizacja 2026-02-13 (ver. 0.264)
|
||||
- **ShopPromotion** - migracja `/admin/shop_promotion` na Domain + DI + nowe widoki
|
||||
- NOWE: `Domain\Promotion\PromotionRepository` (`listForAdmin`, `find`, `save`, `delete`, `categoriesTree`, invalidacja cache aktywnych promocji)
|
||||
- NOWE: `admin\Controllers\ShopPromotionController` (DI) z akcjami `list`, `edit`, `save`, `delete`
|
||||
- UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopPromotion`
|
||||
- UPDATE: modul `/admin/shop_promotion/*` dziala na `components/table-list` i `components/form-edit`
|
||||
- NOWE: widoki/partiale `shop-promotion/promotions-list`, `shop-promotion/promotion-edit`, `shop-promotion/promotion-categories-selector`, `shop-promotion/promotion-categories-tree`, `shop-promotion/promotion-edit-custom-script`
|
||||
- CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopPromotion.php`, `autoload/admin/factory/class.ShopPromotion.php`, `admin/templates/shop-promotion/view-list.php`
|
||||
- UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_promotion/list/`
|
||||
- Testy po zmianie: **OK (222 tests, 609 assertions)**.
|
||||
|
||||
## Aktualizacja 2026-02-13 (ver. 0.265)
|
||||
- **ShopPromotion** - stabilizacja po migracji
|
||||
- UPDATE: dodane `date_from` w `Domain\Promotion\PromotionRepository` (save/find/list/sort)
|
||||
- UPDATE: `admin\Controllers\ShopPromotionController` rozszerzony o pole `Data od` na formularzu i kolumne `Data od` na liscie
|
||||
- UPDATE: `shop\Promotion::get_active_promotions()` filtruje aktywnosc po `date_from` i `date_to`
|
||||
- FIX: zapis edycji promocji nie tworzy nowego rekordu (hidden `id` + fallback `id` z URL)
|
||||
- TEST: rozszerzono `PromotionRepositoryTest` o asercje `date_from`
|
||||
- Testy po zmianie: **OK (222 tests, 614 assertions)**.
|
||||
|
||||
## Plan 2026-02-13 - Refaktoryzacja `/admin/shop_coupon/` (HITL)
|
||||
- [x] Etap 1 (analiza i kontrakt URL/routingu):
|
||||
- potwierdzic docelowy kontrakt URL: `/admin/shop_coupon/list/`, `/admin/shop_coupon/edit/`, `/admin/shop_coupon/save/`, `/admin/shop_coupon/delete/`
|
||||
- decyzja: utrzymujemy aliasy legacy (`view_list`, `coupon_edit`, `coupon_save`, `coupon_delete`) w nowym kontrolerze jako kompatybilnosc wsteczna, przy jednoczesnym przejsciu menu i nowych widokow na URL kanoniczne
|
||||
- sprawdzic mapowanie modulu `ShopCoupon` w `admin\Site` (DI factory + fallback)
|
||||
- [x] Etap 2 (Domain):
|
||||
- dodac `Domain\Coupon\CouponRepository`:
|
||||
- `listForAdmin(filters, sort, dir, page, perPage)` (whitelist sortowania + paginacja)
|
||||
- `find(int $id)` (domyslne dane dla nowego formularza)
|
||||
- `save(array $data): ?int` (insert/update, normalizacja switchy, JSON dla `categories`)
|
||||
- `delete(int $id): bool`
|
||||
- `categoriesTree(?int $parentId): array` (drzewo kategorii bez zaleznosci od `admin\factory\ShopCategory`)
|
||||
- [x] Etap 3 (Admin Controller + routing DI):
|
||||
- dodac `admin\Controllers\ShopCouponController` z akcjami `list`, `edit`, `save`, `delete`
|
||||
- przepiac routing DI w `admin\Site::$newControllers` dla modulu `ShopCoupon`
|
||||
- zachowac obsluge legacy payload `values` JSON i nowego payload `$_POST` z `components/form-edit`
|
||||
- [x] Etap 4 (widoki):
|
||||
- przepiac liste z `grid` na `components/table-list` (filtry: nazwa, aktywny, uzyty, wyslany)
|
||||
- przepiac edycje z `gridEdit` na `components/form-edit`
|
||||
- dodac partiale drzewa kategorii w module `shop-coupon` (usuniecie zaleznosci od `shop-product/subcategories-list`)
|
||||
- dodac `shop-coupon/coupon-edit-custom-script.php` (obsluga drzewa kategorii i zachowania formularza)
|
||||
- [x] Etap 5 (cleanup i zaleznosci):
|
||||
- usunac legacy po pelnym przepieciu:
|
||||
- `autoload/admin/controls/class.ShopCoupon.php`
|
||||
- `autoload/admin/factory/class.ShopCoupon.php`
|
||||
- `admin/templates/shop-coupon/view-list.php` (wersja grid)
|
||||
- `admin/templates/shop-coupon/coupon-edit.php` (wersja gridEdit)
|
||||
- przepiac menu admin na kanoniczny URL `/admin/shop_coupon/list/`
|
||||
- przeszukac repo i usunac pozostale odwolania do `shop_coupon/view_list` i legacy klas `admin\controls\ShopCoupon`, `admin\factory\ShopCoupon`
|
||||
- [x] Etap 6 (testy):
|
||||
- dodac `tests/Unit/Domain/Coupon/CouponRepositoryTest.php`
|
||||
- dodac `tests/Unit/admin/Controllers/ShopCouponControllerTest.php`
|
||||
- uruchomic testy modulu + pelny `composer test`
|
||||
- [x] Etap 7 (dokumentacja i release note):
|
||||
- zaktualizowac `DATABASE_STRUCTURE.md` (dodac `pp_shop_coupon`)
|
||||
- zaktualizowac `PROJECT_STRUCTURE.md`
|
||||
- zaktualizowac `REFACTORING_PLAN.md` (sekcja "Aktualizacja ...")
|
||||
- zaktualizowac `TESTING.md` (nowy wynik suite + nowe testy)
|
||||
- dopisac wpis w `updates/changelog.php`
|
||||
|
||||
### Tryb HITL dla realizacji
|
||||
- Po kazdym etapie (1-7) zatrzymanie i krotkie podsumowanie diffu do akceptacji przed kolejnym krokiem.
|
||||
|
||||
### Postep 2026-02-13 (ShopCoupon)
|
||||
- Etap 2 zakonczony:
|
||||
- NOWE: `autoload/Domain/Coupon/CouponRepository.php`
|
||||
- Zakres: `listForAdmin`, `find`, `save`, `delete`, `categoriesTree`
|
||||
- Walidacja: `php -l` OK
|
||||
- Etap 3 zakonczony:
|
||||
- NOWE: `autoload/admin/Controllers/ShopCouponController.php`
|
||||
- UPDATE: `autoload/admin/class.Site.php` - rejestracja DI factory dla modulu `ShopCoupon`
|
||||
- Kompatybilnosc: dodane aliasy akcji `view_list`, `coupon_edit`, `coupon_save`, `coupon_delete`
|
||||
- Walidacja: `php -l` OK
|
||||
- Etap 4 zakonczony:
|
||||
- NOWE widoki: `admin/templates/shop-coupon/coupons-list.php`, `admin/templates/shop-coupon/coupon-edit-new.php`
|
||||
- NOWE partiale: `admin/templates/shop-coupon/coupon-categories-selector.php`, `admin/templates/shop-coupon/coupon-categories-tree.php`
|
||||
- NOWY skrypt: `admin/templates/shop-coupon/coupon-edit-custom-script.php`
|
||||
- UPDATE: `ShopCouponController::edit()` buduje `FormEditViewModel` (zakladki ustawienia/kategorie)
|
||||
- Walidacja: `php -l` OK
|
||||
- Etap 5 zakonczony:
|
||||
- CLEANUP: usuniete pliki legacy:
|
||||
- `autoload/admin/controls/class.ShopCoupon.php`
|
||||
- `autoload/admin/factory/class.ShopCoupon.php`
|
||||
- `admin/templates/shop-coupon/view-list.php`
|
||||
- `admin/templates/shop-coupon/coupon-edit.php`
|
||||
- UPDATE: menu admin (`admin/templates/site/main-layout.php`) wskazuje kanoniczny URL `/admin/shop_coupon/list/`
|
||||
- WERYFIKACJA: brak odwolan do `shop_coupon/view_list`, `admin\controls\ShopCoupon`, `admin\factory\ShopCoupon` w kodzie
|
||||
- Etap 6 zakonczony:
|
||||
- NOWE testy:
|
||||
- `tests/Unit/Domain/Coupon/CouponRepositoryTest.php` (8 testow)
|
||||
- `tests/Unit/admin/Controllers/ShopCouponControllerTest.php` (5 testow)
|
||||
- Test modulu: `OK (8 tests, 49 assertions)`
|
||||
- Pelny suite: `OK (235 tests, 682 assertions)`
|
||||
- Etap 7 zakonczony:
|
||||
- UPDATE: dokumentacja techniczna zaktualizowana (`DATABASE_STRUCTURE.md`, `PROJECT_STRUCTURE.md`, `TESTING.md`)
|
||||
- UPDATE: dopisany release note w `updates/changelog.php` (ver. 0.266)
|
||||
|
||||
## Aktualizacja 2026-02-13 (ver. 0.266)
|
||||
- **ShopCoupon** - migracja `/admin/shop_coupon` na Domain + DI + nowe widoki
|
||||
- NOWE: `Domain\Coupon\CouponRepository` (`listForAdmin`, `find`, `save`, `delete`, `categoriesTree`)
|
||||
- NOWE: `admin\Controllers\ShopCouponController` (DI) z akcjami `list`, `edit`, `save`, `delete`
|
||||
- UPDATE: kompatybilnosc aliasow legacy (`view_list`, `coupon_edit`, `coupon_save`, `coupon_delete`) obslugiwana przez nowy kontroler
|
||||
- UPDATE: modul `/admin/shop_coupon/*` dziala na `components/table-list` i `components/form-edit`
|
||||
- NOWE: widoki/partiale `shop-coupon/coupons-list`, `shop-coupon/coupon-edit-new`, `shop-coupon/coupon-categories-selector`, `shop-coupon/coupon-categories-tree`, `shop-coupon/coupon-edit-custom-script`
|
||||
- CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopCoupon.php`, `autoload/admin/factory/class.ShopCoupon.php`, `admin/templates/shop-coupon/view-list.php`, `admin/templates/shop-coupon/coupon-edit.php`
|
||||
- UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_coupon/list/`
|
||||
- FIX: po akceptacji HITL ujednolicone UI drzewek i checkboxow miedzy kuponami i layoutami (spojne strzalki, brak nieestetycznego focusu, iCheck dla checkboxow)
|
||||
- Testy po zmianie: **OK (235 tests, 682 assertions)**.
|
||||
@@ -1,79 +1 @@
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/ckeditor/ckeditor.js"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/ckeditor/adapters/jquery.js"></script>
|
||||
<?
|
||||
global $db;
|
||||
$apilo_status_types_list[''] = '--- wybierz status apilo.com ---';
|
||||
foreach ( $this -> apilo_order_status_list as $apilo_status )
|
||||
{
|
||||
$apilo_status_types_list[ $apilo_status['id'] ] = $apilo_status['name'];
|
||||
}
|
||||
ob_start();
|
||||
?>
|
||||
<div id="settings-tabs">
|
||||
<ul class="resp-tabs-list settings-tabs">
|
||||
<li><i class="fa fa-file"></i>Ogólne</li>
|
||||
</ul>
|
||||
<div class="resp-tabs-container settings-tabs">
|
||||
<div>
|
||||
<?= \Html::input( [
|
||||
'label' => 'Status',
|
||||
'class' => 'require',
|
||||
'name' => 'name',
|
||||
'id' => 'name',
|
||||
'readonly' => true,
|
||||
'value' => $this -> status['status']
|
||||
] );?>
|
||||
<?= \Html::input( [
|
||||
'label' => 'Kolor',
|
||||
'name' => 'color',
|
||||
'id' => 'color',
|
||||
'value' => $this -> status['color']
|
||||
] );?>
|
||||
<?= \Html::select( [
|
||||
'label' => 'Status z Apilo',
|
||||
'name' => 'apilo_status_id',
|
||||
'id' => 'apilo_status_id',
|
||||
'values' => $apilo_status_types_list,
|
||||
'value' => $this -> status['apilo_status_id']
|
||||
] );?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?
|
||||
$out = ob_get_clean();
|
||||
|
||||
$grid = new \gridEdit;
|
||||
$grid -> id = 'status-edit';
|
||||
$grid -> gdb_opt = $gdb;
|
||||
$grid -> include_plugins = true;
|
||||
$grid -> title = 'Edycja statusu zamówienia';
|
||||
$grid -> fields = [
|
||||
[
|
||||
'db' => 'id',
|
||||
'type' => 'hidden',
|
||||
'value' => $this -> status['id']
|
||||
]
|
||||
];
|
||||
$grid -> actions = [
|
||||
'save' => [ 'url' => '/admin/shop_statuses/status_save/', 'back_url' => '/admin/shop_statuses/view_list/' ],
|
||||
'cancel' => [ 'url' => '/admin/shop_statuses/view_list/' ]
|
||||
];
|
||||
$grid -> external_code = $out;
|
||||
$grid -> persist_edit = true;
|
||||
$grid -> id_param = 'id';
|
||||
|
||||
echo $grid -> draw();
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
$( function()
|
||||
{
|
||||
disable_menu();
|
||||
|
||||
$( '#settings-tabs' ).easyResponsiveTabs({
|
||||
width: 'auto',
|
||||
fit: true,
|
||||
tabidentify: 'settings-tabs',
|
||||
type: 'vertical'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?= \Tpl::view('components/form-edit', ['form' => $this->form]); ?>
|
||||
|
||||
@@ -1,38 +1 @@
|
||||
<?php
|
||||
global $gdb;
|
||||
foreach ( $this -> apilo_order_status_list as $apilo_status )
|
||||
{
|
||||
$apilo_order_status_list[ $apilo_status['id'] ] = $apilo_status['name'];
|
||||
}
|
||||
|
||||
$grid = new \grid( 'pp_shop_statuses' );
|
||||
$grid -> gdb_opt = $gdb;
|
||||
$grid -> debug = true;
|
||||
$grid -> order = [ 'column' => 'o', 'type' => 'ASC' ];
|
||||
$grid -> search = [
|
||||
[ 'name' => 'Status', 'db' => 'status', 'type' => 'text' ]
|
||||
];
|
||||
$grid -> columns_view = [
|
||||
[
|
||||
'name' => 'Lp.',
|
||||
'th' => [ 'class' => 'g-lp' ],
|
||||
'td' => [ 'class' => 'g-center' ],
|
||||
'autoincrement' => true
|
||||
], [
|
||||
'name' => 'Status',
|
||||
'db' => 'status'
|
||||
], [
|
||||
'name' => 'Kolor',
|
||||
'db' => 'color'
|
||||
], [
|
||||
'name' => 'Status Apilo',
|
||||
'db' => 'apilo_status_id',
|
||||
'replace' => [ 'array' => $apilo_order_status_list ],
|
||||
], [
|
||||
'name' => 'Edytuj',
|
||||
'action' => [ 'type' => 'edit', 'url' => '/admin/shop_statuses/status_edit/id=[id]' ],
|
||||
'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ],
|
||||
'td' => [ 'class' => 'g-center' ]
|
||||
]
|
||||
];
|
||||
echo $grid -> draw();
|
||||
<?= \Tpl::view('components/table-list', ['list' => $this->viewModel]); ?>
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<li><a href="/admin/shop_transport/view_list/"><img src="/admin/layout/icon/icon-menu/bus.svg">Rodzaje transportu</a></li>
|
||||
<li><a href="/admin/shop_payment_method/view_list/"><img src="/admin/layout/icon/icon-menu/coins-fill.svg">Metody płatności</a></li>
|
||||
<li>
|
||||
<a href="/admin/shop_statuses/view_list/"><i class="fa fa-bars"></i>Statusy zamówień</a>
|
||||
<a href="/admin/shop_statuses/list/"><i class="fa fa-bars"></i>Statusy zamówień</a>
|
||||
</li>
|
||||
<li><a href="/admin/shop_coupon/list/"><img src="/admin/layout/icon/icon-menu/piggy-bank-coins.svg">Kody rabatowe</a></li>
|
||||
<li><a href="/admin/shop_promotion/list/"><img src="/admin/layout/icon/icon-menu/burst-sale.svg">Promocje</a></li>
|
||||
|
||||
181
autoload/Domain/ShopStatus/ShopStatusRepository.php
Normal file
181
autoload/Domain/ShopStatus/ShopStatusRepository.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
namespace Domain\ShopStatus;
|
||||
|
||||
class ShopStatusRepository
|
||||
{
|
||||
private const MAX_PER_PAGE = 100;
|
||||
|
||||
private $db;
|
||||
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{items: array<int, array<string, mixed>>, total: int}
|
||||
*/
|
||||
public function listForAdmin(
|
||||
array $filters,
|
||||
string $sortColumn = 'o',
|
||||
string $sortDir = 'ASC',
|
||||
int $page = 1,
|
||||
int $perPage = 15
|
||||
): array {
|
||||
$allowedSortColumns = [
|
||||
'id' => 'ss.id',
|
||||
'status' => 'ss.status',
|
||||
'color' => 'ss.color',
|
||||
'o' => 'ss.o',
|
||||
'apilo_status_id' => 'ss.apilo_status_id',
|
||||
];
|
||||
|
||||
$sortSql = $allowedSortColumns[$sortColumn] ?? 'ss.o';
|
||||
$sortDir = strtoupper(trim($sortDir)) === 'DESC' ? 'DESC' : 'ASC';
|
||||
$page = max(1, $page);
|
||||
$perPage = min(self::MAX_PER_PAGE, max(1, $perPage));
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$where = ['1 = 1'];
|
||||
$params = [];
|
||||
|
||||
$status = trim((string)($filters['status'] ?? ''));
|
||||
if ($status !== '') {
|
||||
if (strlen($status) > 255) {
|
||||
$status = substr($status, 0, 255);
|
||||
}
|
||||
$where[] = 'ss.status LIKE :status';
|
||||
$params[':status'] = '%' . $status . '%';
|
||||
}
|
||||
|
||||
$whereSql = implode(' AND ', $where);
|
||||
|
||||
$sqlCount = "
|
||||
SELECT COUNT(0)
|
||||
FROM pp_shop_statuses AS ss
|
||||
WHERE {$whereSql}
|
||||
";
|
||||
|
||||
$stmtCount = $this->db->query($sqlCount, $params);
|
||||
$countRows = $stmtCount ? $stmtCount->fetchAll() : [];
|
||||
$total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
ss.id,
|
||||
ss.status,
|
||||
ss.color,
|
||||
ss.o,
|
||||
ss.apilo_status_id
|
||||
FROM pp_shop_statuses AS ss
|
||||
WHERE {$whereSql}
|
||||
ORDER BY {$sortSql} {$sortDir}, ss.id ASC
|
||||
LIMIT {$perPage} OFFSET {$offset}
|
||||
";
|
||||
|
||||
$stmt = $this->db->query($sql, $params);
|
||||
$items = $stmt ? $stmt->fetchAll() : [];
|
||||
|
||||
if (!is_array($items)) {
|
||||
$items = [];
|
||||
}
|
||||
|
||||
foreach ($items as &$item) {
|
||||
$item['id'] = (int)($item['id'] ?? 0);
|
||||
$item['apilo_status_id'] = $item['apilo_status_id'] !== null
|
||||
? (int)$item['apilo_status_id']
|
||||
: null;
|
||||
$item['o'] = (int)($item['o'] ?? 0);
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
public function find(int $statusId): ?array
|
||||
{
|
||||
if ($statusId < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$status = $this->db->get('pp_shop_statuses', '*', ['id' => $statusId]);
|
||||
if (!is_array($status)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$status['id'] = (int)($status['id'] ?? 0);
|
||||
$status['apilo_status_id'] = $status['apilo_status_id'] !== null
|
||||
? (int)$status['apilo_status_id']
|
||||
: null;
|
||||
$status['o'] = (int)($status['o'] ?? 0);
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function save(int $statusId, array $data): int
|
||||
{
|
||||
if ($statusId < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$row = [
|
||||
'color' => trim((string)($data['color'] ?? '')),
|
||||
'apilo_status_id' => isset($data['apilo_status_id']) && $data['apilo_status_id'] !== ''
|
||||
? (int)$data['apilo_status_id']
|
||||
: null,
|
||||
];
|
||||
|
||||
$this->db->update('pp_shop_statuses', $row, ['id' => $statusId]);
|
||||
|
||||
return $statusId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera Apilo status ID dla danego statusu sklepowego.
|
||||
* Odpowiednik front\factory\ShopStatuses::get_apilo_status_id()
|
||||
*/
|
||||
public function getApiloStatusId(int $statusId): ?int
|
||||
{
|
||||
$value = $this->db->get('pp_shop_statuses', 'apilo_status_id', ['id' => $statusId]);
|
||||
return $value !== null && $value !== false ? (int)$value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera shop status ID na podstawie ID statusu integracji.
|
||||
* Odpowiednik front\factory\ShopStatuses::get_shop_status_by_integration_status_id()
|
||||
*/
|
||||
public function getByIntegrationStatusId(string $integration, int $integrationStatusId): ?int
|
||||
{
|
||||
if ($integration === 'apilo') {
|
||||
$value = $this->db->get('pp_shop_statuses', 'id', [
|
||||
'apilo_status_id' => $integrationStatusId,
|
||||
]);
|
||||
return $value !== null && $value !== false ? (int)$value : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca liste wszystkich statusow (id => nazwa) posortowanych wg kolejnosci.
|
||||
* Odpowiednik shop\Order::order_statuses()
|
||||
*/
|
||||
public function allStatuses(): array
|
||||
{
|
||||
$results = $this->db->select('pp_shop_statuses', ['id', 'status'], [
|
||||
'ORDER' => ['o' => 'ASC'],
|
||||
]);
|
||||
|
||||
$statuses = [];
|
||||
if (is_array($results)) {
|
||||
foreach ($results as $row) {
|
||||
$statuses[(int)$row['id']] = $row['status'];
|
||||
}
|
||||
}
|
||||
|
||||
return $statuses;
|
||||
}
|
||||
}
|
||||
260
autoload/admin/Controllers/ShopStatusesController.php
Normal file
260
autoload/admin/Controllers/ShopStatusesController.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
namespace admin\Controllers;
|
||||
|
||||
use Domain\ShopStatus\ShopStatusRepository;
|
||||
use admin\ViewModels\Common\PaginatedTableViewModel;
|
||||
use admin\ViewModels\Forms\FormAction;
|
||||
use admin\ViewModels\Forms\FormEditViewModel;
|
||||
use admin\ViewModels\Forms\FormField;
|
||||
use admin\ViewModels\Forms\FormTab;
|
||||
|
||||
class ShopStatusesController
|
||||
{
|
||||
private ShopStatusRepository $repository;
|
||||
|
||||
public function __construct(ShopStatusRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function list(): string
|
||||
{
|
||||
$sortableColumns = ['id', 'status', 'color', 'o', 'apilo_status_id'];
|
||||
$filterDefinitions = [
|
||||
[
|
||||
'key' => 'status',
|
||||
'label' => 'Status',
|
||||
'type' => 'text',
|
||||
],
|
||||
];
|
||||
|
||||
$listRequest = \admin\Support\TableListRequestFactory::fromRequest(
|
||||
$filterDefinitions,
|
||||
$sortableColumns,
|
||||
'o'
|
||||
);
|
||||
|
||||
$sortDir = $listRequest['sortDir'];
|
||||
if (trim((string)\S::get('sort')) === '') {
|
||||
$sortDir = 'ASC';
|
||||
}
|
||||
|
||||
$result = $this->repository->listForAdmin(
|
||||
$listRequest['filters'],
|
||||
$listRequest['sortColumn'],
|
||||
$sortDir,
|
||||
$listRequest['page'],
|
||||
$listRequest['perPage']
|
||||
);
|
||||
|
||||
$apiloStatusList = $this->getApiloStatusList();
|
||||
|
||||
$rows = [];
|
||||
$lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1;
|
||||
foreach ($result['items'] as $item) {
|
||||
$id = (int)($item['id'] ?? 0);
|
||||
$statusName = trim((string)($item['status'] ?? ''));
|
||||
$color = trim((string)($item['color'] ?? ''));
|
||||
$apiloStatusId = $item['apilo_status_id'] ?? null;
|
||||
|
||||
$apiloStatusLabel = '';
|
||||
if ($apiloStatusId !== null && isset($apiloStatusList[$apiloStatusId])) {
|
||||
$apiloStatusLabel = $apiloStatusList[$apiloStatusId];
|
||||
}
|
||||
|
||||
$colorHtml = $color !== ''
|
||||
? '<span style="display:inline-block;width:20px;height:20px;background:' . htmlspecialchars($color, ENT_QUOTES, 'UTF-8') . ';border:1px solid #ccc;vertical-align:middle;margin-right:5px;"></span> ' . htmlspecialchars($color, ENT_QUOTES, 'UTF-8')
|
||||
: '-';
|
||||
|
||||
$rows[] = [
|
||||
'lp' => $lp++ . '.',
|
||||
'status' => '<a href="/admin/shop_statuses/edit/id=' . $id . '">' . htmlspecialchars($statusName, ENT_QUOTES, 'UTF-8') . '</a>',
|
||||
'color' => $colorHtml,
|
||||
'apilo_status' => htmlspecialchars($apiloStatusLabel, ENT_QUOTES, 'UTF-8'),
|
||||
'_actions' => [
|
||||
[
|
||||
'label' => 'Edytuj',
|
||||
'url' => '/admin/shop_statuses/edit/id=' . $id,
|
||||
'class' => 'btn btn-xs btn-primary',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$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' => 'status', 'sort_key' => 'status', 'label' => 'Status', 'sortable' => true, 'raw' => true],
|
||||
['key' => 'color', 'sort_key' => 'color', 'label' => 'Kolor', 'sortable' => true, 'raw' => true],
|
||||
['key' => 'apilo_status', 'sort_key' => 'apilo_status_id', 'label' => 'Status Apilo', 'sortable' => true],
|
||||
],
|
||||
$rows,
|
||||
$listRequest['viewFilters'],
|
||||
[
|
||||
'column' => $listRequest['sortColumn'],
|
||||
'dir' => $sortDir,
|
||||
],
|
||||
[
|
||||
'page' => $listRequest['page'],
|
||||
'per_page' => $listRequest['perPage'],
|
||||
'total' => $total,
|
||||
'total_pages' => $totalPages,
|
||||
],
|
||||
array_merge($listRequest['queryFilters'], [
|
||||
'sort' => $listRequest['sortColumn'],
|
||||
'dir' => $sortDir,
|
||||
'per_page' => $listRequest['perPage'],
|
||||
]),
|
||||
$listRequest['perPageOptions'],
|
||||
$sortableColumns,
|
||||
'/admin/shop_statuses/list/',
|
||||
'Brak danych w tabeli.'
|
||||
);
|
||||
|
||||
return \Tpl::view('shop-statuses/view-list', [
|
||||
'viewModel' => $viewModel,
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(): string
|
||||
{
|
||||
$status = $this->repository->find((int)\S::get('id'));
|
||||
if ($status === null) {
|
||||
\S::alert('Status nie zostal znaleziony.');
|
||||
header('Location: /admin/shop_statuses/list/');
|
||||
exit;
|
||||
}
|
||||
|
||||
$apiloStatusList = $this->getApiloStatusList();
|
||||
|
||||
return \Tpl::view('shop-statuses/status-edit', [
|
||||
'form' => $this->buildFormViewModel($status, $apiloStatusList),
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$legacyValues = \S::get('values');
|
||||
|
||||
if ($legacyValues) {
|
||||
$values = json_decode((string)$legacyValues, true);
|
||||
$response = [
|
||||
'status' => 'error',
|
||||
'msg' => 'Podczas zapisywania statusu wystapil blad. Prosze sprobowac ponownie.',
|
||||
];
|
||||
|
||||
if (is_array($values)) {
|
||||
$statusId = (int)($values['id'] ?? 0);
|
||||
$id = $this->repository->save($statusId, $values);
|
||||
if ($id !== null && $id >= 0) {
|
||||
$response = [
|
||||
'status' => 'ok',
|
||||
'msg' => 'Status zostal zapisany.',
|
||||
'id' => (int)$id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
$payload = $_POST;
|
||||
$statusId = isset($payload['id']) && $payload['id'] !== '' ? (int)$payload['id'] : null;
|
||||
if ($statusId === null) {
|
||||
$statusId = (int)\S::get('id');
|
||||
}
|
||||
|
||||
$id = $this->repository->save($statusId, $payload);
|
||||
if ($id !== null && $id >= 0) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'id' => (int)$id,
|
||||
'message' => 'Status zostal zapisany.',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'errors' => ['general' => 'Podczas zapisywania statusu wystapil blad.'],
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
private function buildFormViewModel(array $status, array $apiloStatusList): FormEditViewModel
|
||||
{
|
||||
$id = (int)($status['id'] ?? 0);
|
||||
|
||||
$apiloOptions = ['' => '--- wybierz status apilo.com ---'];
|
||||
foreach ($apiloStatusList as $apiloId => $apiloName) {
|
||||
$apiloOptions[$apiloId] = $apiloName;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'id' => $id,
|
||||
'status' => (string)($status['status'] ?? ''),
|
||||
'color' => (string)($status['color'] ?? ''),
|
||||
'apilo_status_id' => $status['apilo_status_id'] ?? '',
|
||||
];
|
||||
|
||||
$fields = [
|
||||
FormField::hidden('id', $id),
|
||||
FormField::text('status', [
|
||||
'label' => 'Status',
|
||||
'tab' => 'settings',
|
||||
'readonly' => true,
|
||||
]),
|
||||
FormField::color('color', [
|
||||
'label' => 'Kolor',
|
||||
'tab' => 'settings',
|
||||
]),
|
||||
FormField::select('apilo_status_id', [
|
||||
'label' => 'Status z Apilo',
|
||||
'tab' => 'settings',
|
||||
'options' => $apiloOptions,
|
||||
]),
|
||||
];
|
||||
|
||||
$tabs = [
|
||||
new FormTab('settings', 'Ustawienia', 'fa-wrench'),
|
||||
];
|
||||
|
||||
$actionUrl = '/admin/shop_statuses/save/id=' . $id;
|
||||
$actions = [
|
||||
FormAction::save($actionUrl, '/admin/shop_statuses/list/'),
|
||||
FormAction::cancel('/admin/shop_statuses/list/'),
|
||||
];
|
||||
|
||||
return new FormEditViewModel(
|
||||
'status-edit',
|
||||
'Edycja statusu zamowienia',
|
||||
$data,
|
||||
$fields,
|
||||
$tabs,
|
||||
$actions,
|
||||
'POST',
|
||||
$actionUrl,
|
||||
'/admin/shop_statuses/list/',
|
||||
true,
|
||||
['id' => $id]
|
||||
);
|
||||
}
|
||||
|
||||
private function getApiloStatusList(): array
|
||||
{
|
||||
$list = [];
|
||||
$raw = @unserialize(\admin\factory\Integrations::apilo_settings('status-types-list'));
|
||||
if (is_array($raw)) {
|
||||
foreach ($raw as $apiloStatus) {
|
||||
if (isset($apiloStatus['id'], $apiloStatus['name'])) {
|
||||
$list[(int)$apiloStatus['id']] = (string)$apiloStatus['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
@@ -308,6 +308,36 @@ class FormFieldRenderer
|
||||
'value="' . htmlspecialchars($value ?? '') . '">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderuje pole koloru (color picker + text input)
|
||||
*/
|
||||
public function renderColor(FormField $field): string
|
||||
{
|
||||
$value = $this->form->getFieldValue($field);
|
||||
$error = $this->form->getError($field->name);
|
||||
$colorValue = htmlspecialchars($value ?? '#000000', ENT_QUOTES, 'UTF-8');
|
||||
$fieldName = htmlspecialchars($field->name, ENT_QUOTES, 'UTF-8');
|
||||
$fieldId = htmlspecialchars($field->id, ENT_QUOTES, 'UTF-8');
|
||||
$label = htmlspecialchars($field->label, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$html = '<div class="form-group row">';
|
||||
$html .= '<label class="col-lg-4 control-label">' . $label . ':</label>';
|
||||
$html .= '<div class="col-lg-8">';
|
||||
$html .= '<div style="display:flex;align-items:center;gap:8px;">';
|
||||
$html .= '<input type="color" id="' . $fieldId . '_picker" value="' . $colorValue . '" style="width:40px;height:34px;padding:2px;border:1px solid #ccc;cursor:pointer;" />';
|
||||
$html .= '<input type="text" name="' . $fieldName . '" id="' . $fieldId . '" value="' . $colorValue . '" class="form-control" style="max-width:150px;" />';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= '<script>$(function(){'
|
||||
. 'var $p=$("#' . $fieldId . '_picker"),$t=$("#' . $fieldId . '");'
|
||||
. '$p.on("input",function(){$t.val(this.value);});'
|
||||
. '$t.on("input",function(){var v=this.value;if(/^#[0-9a-fA-F]{6}$/.test(v))$p.val(v);});'
|
||||
. '});</script>';
|
||||
|
||||
return $this->wrapWithError($html, $error);
|
||||
}
|
||||
|
||||
public function renderCustom(FormField $field): string
|
||||
{
|
||||
return (string)($field->customHtml ?? '');
|
||||
|
||||
@@ -268,6 +268,21 @@ class FormField
|
||||
);
|
||||
}
|
||||
|
||||
public static function color(string $name, array $config = []): self
|
||||
{
|
||||
return new self(
|
||||
$name,
|
||||
FormFieldType::COLOR,
|
||||
$config['label'] ?? '',
|
||||
$config['value'] ?? null,
|
||||
$config['tab'] ?? 'default',
|
||||
$config['required'] ?? false,
|
||||
$config['attributes'] ?? [],
|
||||
[],
|
||||
$config['help'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
public static function hidden(string $name, $value = null): self
|
||||
{
|
||||
return new self(
|
||||
|
||||
@@ -21,4 +21,5 @@ class FormFieldType
|
||||
public const HIDDEN = 'hidden';
|
||||
public const LANG_SECTION = 'lang_section';
|
||||
public const CUSTOM = 'custom';
|
||||
public const COLOR = 'color';
|
||||
}
|
||||
|
||||
@@ -332,6 +332,13 @@ class Site
|
||||
new \Domain\Integrations\IntegrationsRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopStatuses' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopStatusesController(
|
||||
new \Domain\ShopStatus\ShopStatusRepository( $mdb )
|
||||
);
|
||||
},
|
||||
];
|
||||
|
||||
return self::$newControllers;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?
|
||||
namespace admin\controls;
|
||||
|
||||
class ShopStatuses {
|
||||
|
||||
// status_save
|
||||
public static function status_save()
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania statusu wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
$values = json_decode( \S::get( 'values' ), true );
|
||||
|
||||
if ( $id = \admin\factory\ShopStatuses::status_save( $values['id'], $values['color'], $values['apilo_status_id'] ) )
|
||||
$response = [ 'status' => 'ok', 'msg' => 'Status został zapisany.', 'id' => $id ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
// status_edit
|
||||
public static function status_edit()
|
||||
{
|
||||
return \Tpl::view( 'shop-statuses/status-edit', [
|
||||
'status' => \admin\factory\ShopStatuses::get_status( \S::get( 'id' ) ),
|
||||
'apilo_order_status_list' => unserialize( \admin\factory\Integrations::apilo_settings( 'status-types-list' ) ),
|
||||
] );
|
||||
}
|
||||
|
||||
static public function view_list()
|
||||
{
|
||||
return \Tpl::view( 'shop-statuses/view-list', [
|
||||
'apilo_order_status_list' => unserialize( \admin\factory\Integrations::apilo_settings( 'status-types-list' ) ),
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace admin\factory;
|
||||
/**
|
||||
* Fasada kompatybilnosci wstecznej.
|
||||
* Deleguje do Domain\Integrations\IntegrationsRepository.
|
||||
* Uzywane przez: cron.php, shop\Order, admin\controls\ShopStatuses, admin\controls\ShopTransport, admin\controls\ShopPaymentMethod, admin\controls\ShopProduct.
|
||||
* Uzywane przez: cron.php, shop\Order, admin\Controllers\ShopStatusesController, admin\controls\ShopTransport, admin\controls\ShopPaymentMethod, admin\controls\ShopProduct.
|
||||
*/
|
||||
class Integrations {
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<?
|
||||
namespace admin\factory;
|
||||
|
||||
class ShopStatuses {
|
||||
// get_status
|
||||
public static function get_status( $id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_shop_statuses', '*', [ 'id' => $id ] );
|
||||
}
|
||||
|
||||
// status_save
|
||||
public static function status_save( $status_id, $color, $apilo_status_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$mdb -> update( 'pp_shop_statuses', [
|
||||
'color' => $color,
|
||||
'apilo_status_id' => $apilo_status_id ? $apilo_status_id : null,
|
||||
], [ 'id' => $status_id ] );
|
||||
|
||||
return $status_id;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?
|
||||
<?php
|
||||
namespace front\factory;
|
||||
|
||||
class ShopStatuses {
|
||||
@@ -6,15 +6,15 @@ class ShopStatuses {
|
||||
// get_apilo_status_id
|
||||
static public function get_apilo_status_id( $status_id ) {
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_shop_statuses', 'apilo_status_id', [ 'id' => $status_id ] );
|
||||
$repo = new \Domain\ShopStatus\ShopStatusRepository( $mdb );
|
||||
return $repo->getApiloStatusId( (int)$status_id );
|
||||
}
|
||||
|
||||
// get_shop_status_by_integration_status_id
|
||||
static public function get_shop_status_by_integration_status_id( $integration, $integration_status_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $integration == 'apilo' )
|
||||
return $mdb -> get( 'pp_shop_statuses', 'id', [ 'apilo_status_id' => $integration_status_id ] );
|
||||
$repo = new \Domain\ShopStatus\ShopStatusRepository( $mdb );
|
||||
return $repo->getByIntegrationStatusId( (string)$integration, (int)$integration_status_id );
|
||||
}
|
||||
}
|
||||
315
docs/CHANGELOG.md
Normal file
315
docs/CHANGELOG.md
Normal file
@@ -0,0 +1,315 @@
|
||||
# Changelog shopPRO
|
||||
|
||||
Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.267 (2026-02-14) - ShopStatuses
|
||||
|
||||
- **ShopStatuses** - migracja `/admin/shop_statuses` na Domain + DI + nowe widoki
|
||||
- NOWE: `Domain\ShopStatus\ShopStatusRepository` (`listForAdmin`, `find`, `save`, `getApiloStatusId`, `getByIntegrationStatusId`, `allStatuses`)
|
||||
- NOWE: `admin\Controllers\ShopStatusesController` (DI) z akcjami `list`, `edit`, `save` (bez aliasow legacy)
|
||||
- NOWE: typ pola `FormFieldType::COLOR` + `FormField::color()` + `FormFieldRenderer::renderColor()` (color picker HTML5 zsynchronizowany z polem tekstowym)
|
||||
- UPDATE: modul `/admin/shop_statuses/*` dziala na `components/table-list` i `components/form-edit`
|
||||
- UPDATE: `front\factory\ShopStatuses` jako fasada delegujaca do `Domain\ShopStatus\ShopStatusRepository`
|
||||
- UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_statuses/list/`
|
||||
- CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopStatuses.php`, `autoload/admin/factory/class.ShopStatuses.php`
|
||||
- UWAGA: statusy maja ID od 0 - kluczowe dla walidacji (find/save uzywaja `$id < 0`)
|
||||
- Testy: **OK (254 tests, 736 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.266 (2026-02-13) - ShopCoupon
|
||||
|
||||
- **ShopCoupon** - migracja `/admin/shop_coupon` na Domain + DI + nowe widoki
|
||||
- NOWE: `Domain\Coupon\CouponRepository` (`listForAdmin`, `find`, `save`, `delete`, `categoriesTree`)
|
||||
- NOWE: `admin\Controllers\ShopCouponController` (DI) z akcjami `list`, `edit`, `save`, `delete`
|
||||
- UPDATE: kompatybilnosc aliasow legacy (`view_list`, `coupon_edit`, `coupon_save`, `coupon_delete`) obslugiwana przez nowy kontroler
|
||||
- UPDATE: modul `/admin/shop_coupon/*` dziala na `components/table-list` i `components/form-edit`
|
||||
- NOWE: widoki/partiale `shop-coupon/coupons-list`, `shop-coupon/coupon-edit-new`, `shop-coupon/coupon-categories-selector`, `shop-coupon/coupon-categories-tree`, `shop-coupon/coupon-edit-custom-script`
|
||||
- CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopCoupon.php`, `autoload/admin/factory/class.ShopCoupon.php`, `admin/templates/shop-coupon/view-list.php`, `admin/templates/shop-coupon/coupon-edit.php`
|
||||
- UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_coupon/list/`
|
||||
- FIX: ujednolicone UI drzewek i checkboxow miedzy kuponami i layoutami
|
||||
- Testy: **OK (235 tests, 682 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.265 (2026-02-13) - ShopPromotion poprawki
|
||||
|
||||
- **ShopPromotion** - stabilizacja po migracji
|
||||
- UPDATE: dodane `date_from` w `Domain\Promotion\PromotionRepository` (save/find/list/sort)
|
||||
- UPDATE: `admin\Controllers\ShopPromotionController` rozszerzony o pole `Data od` na formularzu i kolumne `Data od` na liscie
|
||||
- UPDATE: `shop\Promotion::get_active_promotions()` filtruje aktywnosc po `date_from` i `date_to`
|
||||
- FIX: zapis edycji promocji nie tworzy nowego rekordu (hidden `id` + fallback `id` z URL)
|
||||
- TEST: rozszerzono `PromotionRepositoryTest` o asercje `date_from`
|
||||
- Testy: **OK (222 tests, 614 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.264 (2026-02-13) - ShopPromotion
|
||||
|
||||
- **ShopPromotion** - migracja `/admin/shop_promotion` na Domain + DI + nowe widoki
|
||||
- NOWE: `Domain\Promotion\PromotionRepository` (`listForAdmin`, `find`, `save`, `delete`, `categoriesTree`, invalidacja cache aktywnych promocji)
|
||||
- NOWE: `admin\Controllers\ShopPromotionController` (DI) z akcjami `list`, `edit`, `save`, `delete`
|
||||
- UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopPromotion`
|
||||
- UPDATE: modul `/admin/shop_promotion/*` dziala na `components/table-list` i `components/form-edit`
|
||||
- NOWE: widoki/partiale `shop-promotion/promotions-list`, `shop-promotion/promotion-edit`, `shop-promotion/promotion-categories-selector`, `shop-promotion/promotion-categories-tree`, `shop-promotion/promotion-edit-custom-script`
|
||||
- CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopPromotion.php`, `autoload/admin/factory/class.ShopPromotion.php`, `admin/templates/shop-promotion/view-list.php`
|
||||
- UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_promotion/list/`
|
||||
- Testy: **OK (222 tests, 609 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.263 (2026-02-13) - Integrations + cleanup Sellasist/Baselinker
|
||||
|
||||
- NOWE: `Domain\Integrations\IntegrationsRepository` (settings Apilo/ShopPRO, OAuth, product linking, API fetch)
|
||||
- NOWE: `admin\Controllers\IntegrationsController` (DI) dla akcji Apilo i ShopPRO
|
||||
- UPDATE: `admin\factory\Integrations` jako fasada delegujaca do repozytorium
|
||||
- **CLEANUP: usunieto integracje Sellasist i Baselinker z calego projektu:**
|
||||
- Usuniete klasy: `admin\controls\Integrations`, `admin\controls\Baselinker`, `admin\factory\Baselinker`, `front\factory\Shop`, `shop\ShopStatus`
|
||||
- Usuniete szablony: `integrations/sellasist-settings.php`, `integrations/baselinker-settings.php`, `admin/templates/baselinker/`
|
||||
- Wyczyszczone referencje w: `cron.php`, `cron/cron-xml.php`, `shop\Order`, kontrolery/factory/front Shop*
|
||||
- Testy: **OK (212 tests, 577 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.262 (2026-02-13) - Pages
|
||||
|
||||
- NOWE: `Domain\Pages\PagesRepository` (CRUD menu/stron, drzewo stron, sortowanie, SEO)
|
||||
- NOWE: `admin\Controllers\PagesController` (DI) dla akcji menu/page/AJAX
|
||||
- UPDATE: widoki `admin/templates/pages/*` przepiete na dane z kontrolera/repozytorium
|
||||
- UPDATE: endpointy AJAX przepiete z `admin/ajax.php?a=*` na `/admin/pages/*`
|
||||
- CLEANUP: usuniete legacy `controls/Pages`, `view/Pages`, `factory/Pages`, `ajax/pages.php`
|
||||
- Testy: **OK (186 tests, 478 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.261 (2026-02-13) - Articles (dalsza refaktoryzacja)
|
||||
|
||||
- UPDATE: `Domain\Article\ArticleRepository` rozszerzone o metody UI/admin i `saveFilesOrder()`
|
||||
- UPDATE: `admin\Controllers\ArticlesController` obsluguje AJAX: `article_image_alt_change`, `article_file_name_change`, `article_image_delete`, `article_file_delete`, `filesOrderSave`
|
||||
- UPDATE: lista artykulow nie korzysta juz z `admin\factory\Articles::article_pages()`
|
||||
- UPDATE: widok edycji przepiety z `/admin/ajax.php` na `/admin/articles/*`
|
||||
- UPDATE: drag&drop sortowania listy zalacznikow
|
||||
- CLEANUP: usuniete `autoload/admin/view/class.Articles.php` i `admin/ajax/articles.php`
|
||||
- Testy: **OK (178 tests, 443 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.260 (2026-02-12) - ArticlesArchive
|
||||
|
||||
- NOWE: `admin\Controllers\ArticlesArchiveController` (DI)
|
||||
- UPDATE: `Domain\Article\ArticleRepository` rozszerzone o `listArchivedForAdmin()`, `restore()`, `deletePermanently()`
|
||||
- UPDATE: `/admin/articles_archive/view_list/` migrowane na `components/table-list`
|
||||
- CLEANUP: usuniete legacy `controls/ArticlesArchive`, `factory/ArticlesArchive`, `view/ArticlesArchive`
|
||||
- Testy: **OK (165 tests, 424 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.259 (2026-02-12) - Scontainers
|
||||
|
||||
- NOWE: `Domain\Scontainers\ScontainersRepository` (listForAdmin, find, save, delete, detailsForLanguage)
|
||||
- NOWE: `admin\Controllers\ScontainersController` (DI)
|
||||
- UPDATE: `/admin/scontainers/*` migrowane na `components/table-list` i `components/form-edit`
|
||||
- UPDATE: `admin\factory\Scontainers` i `front\factory\Scontainers` jako fasady
|
||||
- CLEANUP: usuniete `controls/Scontainers`, `view/Scontainers`
|
||||
- Testy: **OK (158 tests, 397 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.258 (2026-02-12) - Newsletter (stabilizacja)
|
||||
|
||||
- UPDATE: tymczasowo wylaczono flow `prepare/send/preview` (wymaga przebudowy)
|
||||
- UPDATE: tymczasowo wylaczono modul `Szablony uzytkownika`
|
||||
- UPDATE: aktywna obsluga tylko szablonow administracyjnych (`is_admin = 1`)
|
||||
- CLEANUP: usuniete nieuzywane widoki `prepare.php`, `preview.php`, `email-templates-user.php`
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.257 (2026-02-12) - Newsletter
|
||||
|
||||
- NOWE: `Domain\Newsletter\NewsletterRepository` (subskrybenci, szablony, ustawienia, kolejka wysylki)
|
||||
- NOWE: `Domain\Newsletter\NewsletterPreviewRenderer` (render podgladu)
|
||||
- NOWE: `admin\Controllers\NewsletterController` (DI)
|
||||
- UPDATE: `/admin/newsletter/*` migrowane na `components/table-list` i `components/form-edit`
|
||||
- UPDATE: `admin\factory\Newsletter` jako fasada; `front\factory\Newsletter` bez `admin\view\Newsletter`
|
||||
- CLEANUP: usuniete `controls/Newsletter`, `view/Newsletter`
|
||||
- Testy: **OK (150 tests, 372 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.256 (2026-02-12) - Layouts
|
||||
|
||||
- NOWE: `Domain\Layouts\LayoutsRepository` (find, save, delete, listForAdmin, menusWithPages, categoriesTree)
|
||||
- NOWE: `admin\Controllers\LayoutsController` (DI)
|
||||
- UPDATE: lista `/admin/layouts/view_list/` migrowana na `components/table-list`
|
||||
- UPDATE: widok `layouts/layout-edit` korzysta z danych z repozytorium
|
||||
- NOWE: partial `admin/templates/layouts/subcategories-list.php`
|
||||
- UPDATE: `Domain\Languages\LanguagesRepository::defaultLanguageId()` jako wspolna metoda
|
||||
- UPDATE: `ArticlesController` korzysta z `LayoutsRepository` (DI)
|
||||
- CLEANUP: usuniete `controls/Layouts`, `view/Layouts`; `factory/Layouts` jako fasada
|
||||
- Testy: **OK (141 tests, 336 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.255 (2026-02-12) - Languages DI cleanup
|
||||
|
||||
- UPDATE: SettingsController, BannerController, DictionariesController, ArticlesController pobieraja liste jezykow przez `Domain/Languages/LanguagesRepository` (DI)
|
||||
- UPDATE: router DI przekazuje `LanguagesRepository` do kontrolerow
|
||||
- UPDATE: legacy `admin/controls`, `admin/factory/Shop*` przepiete na `LanguagesRepository`
|
||||
- FIX: `admin/factory/class.Languages.php` poprawione na `<?php` (short_open_tag=Off)
|
||||
- Testy: **OK (130 tests, 303 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.254 (2026-02-12) - Languages
|
||||
|
||||
- NOWE: `Domain\Languages\LanguagesRepository` (languages + translations CRUD/list)
|
||||
- NOWE: `admin\Controllers\LanguagesController` (DI)
|
||||
- UPDATE: widoki `languages/*` migrowane na `components/table-list` i `components/form-edit`
|
||||
- CLEANUP: usuniete `controls/Languages`, `view/Languages`; `factory/Languages` jako fasada
|
||||
- Testy: **OK (130 tests, 301 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.253 (2026-02-12) - Users
|
||||
|
||||
- NOWE: `Domain\User\UserRepository` (CRUD, logon, 2FA, checkLogin)
|
||||
- NOWE: `admin\Controllers\UsersController` (DI: view_list, user_edit, user_save, user_delete, login_form, twofa)
|
||||
- UPDATE: `admin\factory\Users` jako fasada; `admin/ajax/users.php` oparty o `UserRepository`
|
||||
- UPDATE: widoki users migrowane na `components/table-list` i `components/form-edit`
|
||||
- UPDATE: walidacja warunkowa: `twofa_email` wymagany gdy `twofa_enabled = 1`
|
||||
- CLEANUP: usuniete `controls/Users`, `factory/Users`, `view/Users`
|
||||
- Testy: **OK (119 tests, 256 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.252 (2026-02-10) - ProductArchive + Filemanager
|
||||
|
||||
- UPDATE: `ProductArchiveController` i szablony przepiete na `components/table-list`
|
||||
- UPDATE: CSS/JS dla list wydzielone do osobnych widoków `*-custom-script.php`
|
||||
- NOWE: `admin\Controllers\FilemanagerController` - przepieto filemanager na nowy routing
|
||||
- FIX: naprawiono błąd `Invalid Key` w filemanagerze
|
||||
- CLEANUP: usunieto legacy `controls/Archive`, `controls/Filemanager`, `view/FileManager`, stare szablony
|
||||
- RENAME: `admin/templates/product_archive/` → `admin/templates/product-archive/`
|
||||
- Testy: **OK (82 tests, 181 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.251 (2026-02-09) - Dictionaries
|
||||
|
||||
- NOWE: `Domain\Dictionaries\DictionariesRepository` (listForAdmin, find, save, delete, allUnits)
|
||||
- NOWE: `admin\Controllers\DictionariesController` (lista + formularz na nowych komponentach)
|
||||
- UPDATE: migracja słowników na `components/table-list` i `components/form-edit`
|
||||
- FIX: obsługa `lang_id` jako string (`pl`, `en`) w zapisie tłumaczeń
|
||||
- CLEANUP: usunięto legacy klasy Dictionaries (`admin\controls`, `admin\factory`, `front\factory`)
|
||||
- Testy: **OK (82 tests, 181 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.250 (2026-02-09) - Settings (refaktoryzacja)
|
||||
|
||||
- UPDATE: `Domain\Settings\SettingsRepository` ma bezpośredni dostęp do DB (bez delegacji do `admin\factory\Settings`)
|
||||
- UPDATE: przepięto użycia `admin\factory\Settings` na `Domain\Settings\SettingsRepository`
|
||||
- CLEANUP: usunięto legacy klasy Settings (`factory`, `controls`, `view`)
|
||||
- Testy: **OK (82 tests, 181 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.246 (2026-02-07) - Articles legacy cleanup
|
||||
|
||||
- CLEANUP: usunięto `autoload/admin/controls/class.Articles.php`
|
||||
- UPDATE: `admin\Controllers\ArticlesController::galleryOrderSave()` uzywa `ArticleRepository::saveGalleryOrder()`
|
||||
- FIX: sortowanie list admin po reloadzie - `RewriteRule` dla `/admin/...` ma `QSA`
|
||||
- FIX: generator `\S::htacces()` komentuje dyrektywy `AddHandler|SetHandler|ForceType`
|
||||
- Testy: **OK (65 tests, 131 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.245 (2026-02-06) - Articles::archive
|
||||
|
||||
- UPDATE: `Domain\Article\ArticleRepository` - dodano `archive()` (status = -1)
|
||||
- UPDATE: `admin\Controllers\ArticlesController` - nowa akcja `delete()` z DI
|
||||
- UPDATE: `admin\factory\Articles::articles_set_archive()` deleguje do repozytorium
|
||||
- Testy: **OK (59 tests, 123 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.244 (2026-02-06) - Articles::save
|
||||
|
||||
- UPDATE: `Domain\Article\ArticleRepository` - dodano `save()` z prywatnych helperow
|
||||
- UPDATE: `admin\Controllers\ArticlesController` - nowa akcja `save()` z DI
|
||||
- UPDATE: `tests/bootstrap.php` - dodano stub `S::seo()`
|
||||
- Testy: **OK (57 tests, 119 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.243 (2026-02-06) - Articles cleanup
|
||||
|
||||
- UPDATE: `Domain\Article\ArticleRepository` - dodano `deleteNonassignedImages()` i `deleteNonassignedFiles()`
|
||||
- UPDATE: `admin\Controllers\ArticlesController::edit()` uses repository cleanup methods
|
||||
- Testy: **OK (50 tests, 95 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.242 (2026-02-06) - Articles::edit
|
||||
|
||||
- NOWE: `Domain\Article\ArticleRepository` (find: artykuł + relacje)
|
||||
- UPDATE: `admin\Controllers\ArticlesController` - konstruktor DI + `edit()` używa repozytorium
|
||||
- UPDATE: `admin\factory\Articles::article_details()` deleguje do repozytorium
|
||||
- Testy: **OK (48 tests, 91 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.241 (2026-02-06) - ProductArchive
|
||||
|
||||
- NOWE: `admin\Controllers\ProductArchiveController` (DI)
|
||||
- NOWE: `ProductRepository::archive()`, `unarchive()`
|
||||
- RENAME: `admin/templates/archive/` → `admin/templates/product_archive/`
|
||||
- FIX: SQL w `ajax_products_list_archive()` (puste wyszukiwanie + brak `archive = 1`)
|
||||
- Testy: **OK (50 tests, 95 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.240 (2026-02-05) - Settings + Cache
|
||||
|
||||
- NOWE: `Domain\Settings\SettingsRepository` (fasada → factory)
|
||||
- NOWE: `Domain\Cache\CacheRepository` (dirs + Redis)
|
||||
- NOWE: `admin\Controllers\SettingsController` (DI: clearCache, save, view)
|
||||
- FIX: Brakujący `id="content"` w main-layout.php
|
||||
- FIX: `persist_edit = true` w settings.php
|
||||
- Testy: **OK (29 tests, 60 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.239 (2026-02-05) - Banner + Product
|
||||
|
||||
- NOWE: `Domain\Banner\BannerRepository` (find, delete, save)
|
||||
- NOWE: `admin\Controllers\BannerController` - pierwszy kontroler z DI
|
||||
- NOWE: Router z mapą `$newControllers` + fallback na stare kontrolery
|
||||
- NOWE: Autoloader PSR-4 fallback w 9 entry pointach
|
||||
- Zmigrowano: `get_product_price()` → `ProductRepository::getPrice()`
|
||||
- Zmigrowano: `get_product_name()` → `ProductRepository::getName()`
|
||||
- Testy: **OK (15 tests, 31 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.238 (2025-02-05) - Product Repository
|
||||
|
||||
- NOWE: `Domain\Product\ProductRepository` - pierwsza klasa w nowej architekturze
|
||||
- NOWE: Dependency Injection zamiast `global $mdb`
|
||||
- NOWE: Testy jednostkowe (5 testów, 100% pokrycie)
|
||||
- Zmigrowano: `get_product_quantity()` → `ProductRepository::getQuantity()`
|
||||
- Kompatybilność: Stara klasa `shop\Product` działa jako fasada
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.237 (2025-02-05) - System cache produktów
|
||||
|
||||
- Automatyczne czyszczenie cache produktu po aktualizacji przez CRON
|
||||
- AJAX dla przycisku "Wyczyść cache" w panelu admin
|
||||
- Metody `delete()` i `deletePattern()` w CacheHandler
|
||||
- Metoda `clear_product_cache()` w klasie S
|
||||
|
||||
---
|
||||
*Dokument aktualizowany: 2026-02-14*
|
||||
@@ -391,3 +391,20 @@ Ustawienia integracji ShopPRO (key-value).
|
||||
**Uzywane w:** `Domain\Integrations\IntegrationsRepository`, `admin\Controllers\IntegrationsController`, `admin\factory\Integrations`
|
||||
|
||||
**Aktualizacja 2026-02-13:** modul `/admin/integrations/` korzysta z `Domain\Integrations\IntegrationsRepository` (DI kontroler + fasada legacy). Usunieto integracje Sellasist i Baselinker.
|
||||
|
||||
## pp_shop_statuses
|
||||
Statusy zamowien sklepu (modul `/admin/shop_statuses`). Statusy sa predefiniowane - brak dodawania/usuwania, mozliwa edycja koloru i mapowania Apilo.
|
||||
|
||||
| Kolumna | Opis |
|
||||
|---------|------|
|
||||
| id | PK (zaczyna sie od 0!) |
|
||||
| status | Nazwa statusu (read-only) |
|
||||
| color | Kolor statusu (hex, np. #ff0000) |
|
||||
| o | Kolejnosc wyswietlania |
|
||||
| apilo_status_id | ID statusu w Apilo (NULL gdy brak mapowania) |
|
||||
| baselinker_status_id | DEPRECATED (usuniety w ver. 0.263) |
|
||||
| sellasist_status_id | DEPRECATED (usuniety w ver. 0.263) |
|
||||
|
||||
**Uzywane w:** `Domain\ShopStatus\ShopStatusRepository`, `admin\Controllers\ShopStatusesController`, `front\factory\ShopStatuses`, `shop\Order`, `cron.php`
|
||||
|
||||
**Aktualizacja 2026-02-14 (ver. 0.267):** modul `/admin/shop_statuses` korzysta z `Domain\ShopStatus\ShopStatusRepository` przez `admin\Controllers\ShopStatusesController`. Usunieto legacy klasy `admin\controls\ShopStatuses` i `admin\factory\ShopStatuses`. `front\factory\ShopStatuses` dziala jako fasada do repozytorium.
|
||||
172
docs/FORM_EDIT_SYSTEM.md
Normal file
172
docs/FORM_EDIT_SYSTEM.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# 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) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Pliki systemu
|
||||
|
||||
| Plik | Opis |
|
||||
|------|------|
|
||||
| `autoload/admin/ViewModels/Forms/FormFieldType.php` | Stale typow pol |
|
||||
| `autoload/admin/ViewModels/Forms/FormField.php` | Factory methods per typ |
|
||||
| `autoload/admin/ViewModels/Forms/FormTab.php` | Zakladki |
|
||||
| `autoload/admin/ViewModels/Forms/FormAction.php` | Akcje (zapisz, anuluj) |
|
||||
| `autoload/admin/ViewModels/Forms/FormEditViewModel.php` | ViewModel formularza |
|
||||
| `autoload/admin/Support/Forms/FormValidator.php` | Walidacja pol |
|
||||
| `autoload/admin/Support/Forms/FormRequestHandler.php` | Obsluga POST + persist |
|
||||
| `autoload/admin/Support/Forms/FormFieldRenderer.php` | Renderowanie HTML |
|
||||
| `admin/templates/components/form-edit.php` | Uniwersalny szablon |
|
||||
|
||||
## 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)` | - |
|
||||
| `color` | `FormField::color(name, ['label' => '...'])` | HTML5 color picker + text input |
|
||||
| `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
|
||||
|
||||
---
|
||||
*Dokument aktualizowany: 2026-02-14*
|
||||
254
docs/PROJECT_STRUCTURE.md
Normal file
254
docs/PROJECT_STRUCTURE.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# Struktura Projektu shopPRO
|
||||
|
||||
Dokumentacja struktury projektu shopPRO do szybkiego odniesienia.
|
||||
|
||||
## System Cache (Redis)
|
||||
|
||||
### Klasy odpowiedzialne za cache
|
||||
|
||||
#### RedisConnection
|
||||
- **Plik:** `autoload/class.RedisConnection.php`
|
||||
- **Opis:** Singleton zarządzający połączeniem z Redis
|
||||
- **Metody:**
|
||||
- `getInstance()` - pobiera instancję połączenia
|
||||
- `getConnection()` - zwraca obiekt Redis
|
||||
|
||||
#### CacheHandler
|
||||
- **Plik:** `autoload/class.CacheHandler.php`
|
||||
- **Opis:** Handler do obsługi cache Redis
|
||||
- **Metody:**
|
||||
- `get($key)` - pobiera wartość z cache
|
||||
- `set($key, $value, $ttl = 86400)` - zapisuje wartość do cache
|
||||
- `exists($key)` - sprawdza czy klucz istnieje
|
||||
- `delete($key)` - usuwa pojedynczy klucz
|
||||
- `deletePattern($pattern)` - usuwa klucze według wzorca
|
||||
|
||||
#### Klasa S (pomocnicza)
|
||||
- **Plik:** `autoload/class.S.php`
|
||||
- **Metody cache:**
|
||||
- `clear_redis_cache()` - czyści cały cache Redis (flushAll)
|
||||
- `clear_product_cache(int $product_id)` - czyści cache konkretnego produktu
|
||||
|
||||
### Wzorce kluczy Redis
|
||||
|
||||
#### Produkty
|
||||
```
|
||||
shop\product:{product_id}:{lang_id}:{permutation_hash}
|
||||
```
|
||||
- Przechowuje zserializowany obiekt produktu
|
||||
- TTL: 24 godziny (86400 sekund)
|
||||
- Klasa: `shop\Product::getFromCache()` - `autoload/shop/class.Product.php:121`
|
||||
|
||||
#### Opcje ilościowe produktu
|
||||
```
|
||||
\shop\Product::get_product_permutation_quantity_options:{product_id}:{permutation}
|
||||
```
|
||||
- Przechowuje informacje o ilości i komunikatach magazynowych
|
||||
- Klasa: `shop\Product::get_product_permutation_quantity_options()` - `autoload/shop/class.Product.php:549`
|
||||
|
||||
#### Zestawy produktów
|
||||
```
|
||||
\shop\Product::product_sets_when_add_to_basket:{product_id}
|
||||
```
|
||||
- Przechowuje produkty często kupowane razem
|
||||
- Klasa: `shop\Product::product_sets_when_add_to_basket()` - `autoload/shop/class.Product.php:316`
|
||||
|
||||
## Integracje z systemami zewnętrznymi (CRON)
|
||||
|
||||
### Plik: `cron.php`
|
||||
|
||||
#### Apilo
|
||||
- **Aktualizacja pojedynczego produktu:** synchronizacja cen i stanow
|
||||
- Czestotliwosc: Co 10 minut
|
||||
- **Synchronizacja cennika:** masowa aktualizacja cen z Apilo
|
||||
- Czestotliwosc: Co 1 godzine
|
||||
|
||||
**Uwaga:** Integracje Sellasist i Baselinker zostaly usuniete w ver. 0.263.
|
||||
|
||||
## Panel Administratora
|
||||
|
||||
### Routing
|
||||
- Główny katalog: `admin/`
|
||||
- Template główny: `admin/templates/site/main-layout.php`
|
||||
- Kontrolery (nowe): `autoload/admin/Controllers/`
|
||||
- Kontrolery legacy (fallback): `autoload/admin/controls/`
|
||||
|
||||
### Przycisk "Wyczyść cache"
|
||||
- **Lokalizacja UI:** `admin/templates/site/main-layout.php:172`
|
||||
- **JavaScript:** `admin/templates/site/main-layout.php:235-274`
|
||||
- **Endpoint AJAX:** `/admin/settings/clear_cache_ajax/`
|
||||
- **Kontroler:** `autoload/admin/Controllers/SettingsController.php:43-60`
|
||||
- **Działanie:**
|
||||
1. Pokazuje spinner "Czyszczę cache..."
|
||||
2. Czyści katalogi: `temp/`, `thumbs/`
|
||||
3. Wykonuje `flushAll()` na Redis
|
||||
4. Pokazuje "Cache wyczyszczony!" przez 2 sekundy
|
||||
5. Przywraca stan początkowy
|
||||
|
||||
## Struktura katalogów
|
||||
|
||||
```
|
||||
shopPRO/
|
||||
├── admin/ # Panel administratora
|
||||
│ ├── templates/ # Szablony widoków
|
||||
│ └── layout/ # Zasoby CSS/JS/ikony
|
||||
├── autoload/ # Klasy autoloadowane
|
||||
│ ├── admin/ # Klasy panelu admin
|
||||
│ │ ├── Controllers/ # Nowe kontrolery DI
|
||||
│ │ ├── controls/ # Kontrolery legacy (fallback)
|
||||
│ │ └── factory/ # Fabryki/helpery
|
||||
│ ├── Domain/ # Repozytoria/logika domenowa
|
||||
│ ├── front/ # Klasy frontendu
|
||||
│ │ └── factory/ # Fabryki/helpery
|
||||
│ └── shop/ # Klasy sklepu
|
||||
├── docs/ # Dokumentacja techniczna
|
||||
├── libraries/ # Biblioteki zewnętrzne
|
||||
├── temp/ # Cache tymczasowy
|
||||
├── thumbs/ # Miniatury zdjęć
|
||||
└── cron.php # Zadania CRON
|
||||
```
|
||||
|
||||
## Baza danych
|
||||
|
||||
### Główne tabele produktów
|
||||
- `pp_shop_products` - produkty główne
|
||||
- `pp_shop_products_langs` - tłumaczenia produktów
|
||||
- `pp_shop_products_images` - zdjęcia produktów
|
||||
- `pp_shop_products_categories` - kategorie produktów
|
||||
- `pp_shop_products_custom_fields` - pola własne produktów
|
||||
|
||||
### Tabele integracji
|
||||
- Kolumny w `pp_shop_products`:
|
||||
- `apilo_product_id`, `apilo_product_name`, `apilo_get_data_date`
|
||||
- Tabele ustawien:
|
||||
- `pp_shop_apilo_settings` (key-value)
|
||||
- `pp_shop_shoppro_settings` (key-value)
|
||||
|
||||
Pelna dokumentacja tabel: `DATABASE_STRUCTURE.md`
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
### Redis
|
||||
- Konfiguracja: `config.php` (zmienna `$config['redis']`)
|
||||
- Parametry: host, port, password
|
||||
|
||||
### Autoload
|
||||
- Funkcja: `__autoload_my_classes()` w `cron.php:6`
|
||||
- Wzorzec: `autoload/{namespace}/class.{ClassName}.php`
|
||||
|
||||
## Klasy pomocnicze
|
||||
|
||||
### \S (autoload/class.S.php)
|
||||
Główna klasa helper z metodami:
|
||||
- `seo($val)` - generowanie URL SEO
|
||||
- `normalize_decimal($val, $precision)` - normalizacja liczb
|
||||
- `send_email()` - wysyłanie emaili
|
||||
- `delete_dir($dir)` - usuwanie katalogów
|
||||
- `htacces()` - generowanie .htaccess i sitemap.xml
|
||||
|
||||
### Medoo
|
||||
- Plik: `libraries/medoo/medoo.php`
|
||||
- Zmienna: `$mdb`
|
||||
- ORM do operacji na bazie danych
|
||||
|
||||
## Najważniejsze wzorce
|
||||
|
||||
### Namespace'y
|
||||
- `\admin\Controllers\` - nowe kontrolery panelu admin (DI)
|
||||
- `\admin\controls\` - kontrolery legacy (fallback)
|
||||
- `\Domain\` - repozytoria/logika domenowa
|
||||
- `\admin\factory\` - helpery/fabryki admin
|
||||
- `\front\factory\` - helpery/fabryki frontend
|
||||
- `\shop\` - klasy sklepu (Product, Order, itp.)
|
||||
|
||||
### Cachowanie produktów
|
||||
```php
|
||||
// Pobranie produktu z cache
|
||||
$product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash);
|
||||
|
||||
// Czyszczenie cache produktu
|
||||
\S::clear_product_cache($product_id);
|
||||
|
||||
// Czyszczenie całego cache
|
||||
\S::clear_redis_cache();
|
||||
```
|
||||
|
||||
## Refaktoryzacja do Domain-Driven Architecture
|
||||
|
||||
### Nowa struktura (w trakcie migracji)
|
||||
```
|
||||
autoload/
|
||||
├── Domain/ # Nowa warstwa biznesowa (namespace \Domain\)
|
||||
│ ├── Product/
|
||||
│ │ └── ProductRepository.php
|
||||
│ ├── Banner/
|
||||
│ │ └── BannerRepository.php
|
||||
│ ├── Settings/
|
||||
│ │ └── SettingsRepository.php
|
||||
│ ├── Cache/
|
||||
│ │ └── CacheRepository.php
|
||||
│ ├── Article/
|
||||
│ │ └── ArticleRepository.php
|
||||
│ ├── User/
|
||||
│ │ └── UserRepository.php
|
||||
│ ├── Languages/
|
||||
│ │ └── LanguagesRepository.php
|
||||
│ ├── Layouts/
|
||||
│ │ └── LayoutsRepository.php
|
||||
│ ├── Newsletter/
|
||||
│ │ └── NewsletterRepository.php
|
||||
│ ├── Scontainers/
|
||||
│ │ └── ScontainersRepository.php
|
||||
│ ├── Dictionaries/
|
||||
│ │ └── DictionariesRepository.php
|
||||
│ ├── Pages/
|
||||
│ │ └── PagesRepository.php
|
||||
│ ├── Integrations/
|
||||
│ │ └── IntegrationsRepository.php
|
||||
│ ├── Promotion/
|
||||
│ │ └── PromotionRepository.php
|
||||
│ ├── Coupon/
|
||||
│ │ └── CouponRepository.php
|
||||
│ ├── ShopStatus/
|
||||
│ │ └── ShopStatusRepository.php
|
||||
│ └── ...
|
||||
├── admin/
|
||||
│ ├── Controllers/ # Nowe kontrolery (namespace \admin\Controllers\)
|
||||
│ ├── class.Site.php # Router: nowy kontroler → fallback stary
|
||||
│ ├── controls/ # Stare kontrolery (niezależny fallback)
|
||||
│ ├── factory/ # Stare helpery (niezależny fallback)
|
||||
│ └── view/ # Widoki (statyczne - bez zmian)
|
||||
├── shop/ # Legacy - fasady do Domain
|
||||
└── front/factory/ # Legacy - stopniowo migrowane
|
||||
```
|
||||
|
||||
### Routing admin (admin\Site::route())
|
||||
1. Sprawdź mapę `$newControllers` → utwórz instancję z DI → wywołaj
|
||||
2. Jeśli nowy kontroler nie istnieje (`class_exists()` = false) → fallback na `admin\controls\`
|
||||
3. Stary kontroler jest NIEZALEŻNY od nowych klas (bezpieczny fallback)
|
||||
|
||||
### Dependency Injection
|
||||
Nowe klasy używają **Dependency Injection** zamiast `global` variables:
|
||||
```php
|
||||
// STARE
|
||||
global $mdb;
|
||||
$quantity = $mdb->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
|
||||
// NOWE
|
||||
$repository = new \Domain\Product\ProductRepository($mdb);
|
||||
$quantity = $repository->getQuantity($id);
|
||||
```
|
||||
|
||||
## Testowanie (tylko dla deweloperów)
|
||||
|
||||
**UWAGA:** Pliki testów NIE są częścią aktualizacji dla klientów!
|
||||
|
||||
### Narzędzia
|
||||
- **PHPUnit 9.6.34** - framework testowy
|
||||
- **test.bat** - uruchamianie testów
|
||||
- **composer.json** - autoloading PSR-4
|
||||
|
||||
Pelna dokumentacja testow: `TESTING.md`
|
||||
|
||||
---
|
||||
*Dokument aktualizowany: 2026-02-14*
|
||||
276
docs/REFACTORING_PLAN.md
Normal file
276
docs/REFACTORING_PLAN.md
Normal file
@@ -0,0 +1,276 @@
|
||||
# Plan Refaktoryzacji shopPRO - Domain-Driven Architecture
|
||||
|
||||
## Cel
|
||||
Stopniowe przeniesienie logiki biznesowej do architektury warstwowej:
|
||||
- **Domain/** - logika biznesowa (core)
|
||||
- **Admin/** - warstwa administratora
|
||||
- **Frontend/** - warstwa użytkownika
|
||||
- **Shared/** - współdzielone narzędzia
|
||||
|
||||
## Docelowa struktura
|
||||
|
||||
```
|
||||
autoload/
|
||||
├── Domain/ # Logika biznesowa (CORE) - namespace \Domain\
|
||||
│ ├── Product/
|
||||
│ │ ├── ProductRepository.php
|
||||
│ │ ├── ProductService.php # (przyszłość)
|
||||
│ │ └── ProductCacheService.php # (przyszłość)
|
||||
│ ├── Banner/
|
||||
│ │ └── BannerRepository.php
|
||||
│ ├── Settings/
|
||||
│ │ └── SettingsRepository.php
|
||||
│ ├── Cache/
|
||||
│ │ └── CacheRepository.php
|
||||
│ ├── Order/
|
||||
│ ├── Category/
|
||||
│ └── ...
|
||||
│
|
||||
├── admin/ # Warstwa administratora (istniejący katalog!)
|
||||
│ ├── Controllers/ # Nowe kontrolery - namespace \admin\Controllers\
|
||||
│ ├── controls/ # Stare kontrolery (legacy fallback)
|
||||
│ ├── factory/ # Stare helpery (legacy)
|
||||
│ └── view/ # Widoki (statyczne - OK bez zmian)
|
||||
│
|
||||
├── Frontend/ # Warstwa użytkownika (przyszłość)
|
||||
│ ├── Controllers/
|
||||
│ └── Services/
|
||||
│
|
||||
├── Shared/ # Współdzielone narzędzia
|
||||
│ ├── Cache/
|
||||
│ │ ├── CacheHandler.php
|
||||
│ │ └── RedisConnection.php
|
||||
│ └── Helpers/
|
||||
│ └── S.php
|
||||
│
|
||||
└── [LEGACY] # Stare klasy (stopniowo deprecated)
|
||||
├── shop/
|
||||
├── admin/factory/
|
||||
└── front/factory/
|
||||
```
|
||||
|
||||
### WAŻNE: Konwencja namespace → katalog (Linux case-sensitive!)
|
||||
- `\Domain\` → `autoload/Domain/` (duże D - nowy katalog)
|
||||
- `\admin\Controllers\` → `autoload/admin/Controllers/` (małe a - istniejący katalog)
|
||||
- NIE używać `\Admin\` (duże A) bo na serwerze Linux katalog to `admin/` (małe a)
|
||||
|
||||
## Zasady migracji
|
||||
|
||||
### 1. Stopniowość
|
||||
- Przenosimy **jedną funkcję na raz**
|
||||
- Zachowujemy kompatybilność wsteczną
|
||||
- Stare klasy działają jako fasady do nowych
|
||||
|
||||
### 2. Dependency Injection zamiast statycznych metod
|
||||
```php
|
||||
// ❌ STARE - statyczne
|
||||
class Product {
|
||||
public static function getQuantity($id) {
|
||||
global $mdb;
|
||||
return $mdb->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NOWE - instancje z DI
|
||||
class ProductRepository {
|
||||
private $db;
|
||||
|
||||
public function __construct($db) {
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function getQuantity($id) {
|
||||
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Fasady dla kompatybilności
|
||||
```php
|
||||
// Stara klasa wywołuje nową
|
||||
namespace shop;
|
||||
|
||||
class Product {
|
||||
public static function getQuantity($id) {
|
||||
global $mdb;
|
||||
$repo = new \Domain\Product\ProductRepository($mdb);
|
||||
return $repo->getQuantity($id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Proces migracji funkcji
|
||||
|
||||
### Krok 1: Wybór funkcji
|
||||
- Wybierz prostą funkcję statyczną
|
||||
- Sprawdź jej zależności
|
||||
- Przeanalizuj gdzie jest używana
|
||||
|
||||
### Krok 2: Stworzenie nowej struktury
|
||||
- Utwórz folder `Domain/{Module}/`
|
||||
- Stwórz odpowiednią klasę (Repository/Service/Entity)
|
||||
- Przenieś logikę
|
||||
|
||||
### Krok 3: Znalezienie użyć
|
||||
```bash
|
||||
grep -r "Product::getQuantity" .
|
||||
```
|
||||
|
||||
### Krok 4: Aktualizacja wywołań
|
||||
- Opcja A: Bezpośrednie wywołanie nowej klasy
|
||||
- Opcja B: Fasada w starej klasie (zalecane na początek)
|
||||
|
||||
### Krok 5: Testy
|
||||
- Napisz test jednostkowy dla nowej funkcji
|
||||
- Sprawdź czy stare wywołania działają
|
||||
|
||||
## Status migracji
|
||||
|
||||
### ✅ Zmigrowane moduły
|
||||
| # | Modul | Wersja | Zakres |
|
||||
|---|-------|--------|--------|
|
||||
| 1 | Cache | 0.237 | CacheHandler, RedisConnection, clear_product_cache |
|
||||
| 2 | Product | 0.238-0.252 | getQuantity, getPrice, getName, archive/unarchive |
|
||||
| 3 | Banner | 0.239 | find, delete, save, kontroler DI |
|
||||
| 4 | Settings | 0.240/0.250 | saveSettings, getSettings, kontroler DI |
|
||||
| 5 | Dictionaries | 0.251 | listForAdmin, find, save, delete, kontroler DI |
|
||||
| 6 | ProductArchive | 0.252 | kontroler DI, table-list |
|
||||
| 7 | Filemanager | 0.252 | kontroler DI, fix Invalid Key |
|
||||
| 8 | Users | 0.253 | CRUD, logon, 2FA, kontroler DI |
|
||||
| 9 | Languages | 0.254 | languages + translations, kontroler DI |
|
||||
| 10 | Layouts | 0.256 | find, save, delete, menusWithPages, categoriesTree |
|
||||
| 11 | Newsletter | 0.257-0.258 | subskrybenci, szablony, ustawienia |
|
||||
| 12 | Scontainers | 0.259 | listForAdmin, find, save, delete |
|
||||
| 13 | ArticlesArchive | 0.260 | restore, deletePermanently |
|
||||
| 14 | Articles | 0.261 | pelna migracja (CRUD, AJAX, galeria, pliki) |
|
||||
| 15 | Pages | 0.262 | menu/page CRUD, drzewo stron, AJAX |
|
||||
| 16 | Integrations | 0.263 | Apilo/ShopPRO, cleanup Sellasist/Baselinker |
|
||||
| 17 | ShopPromotion | 0.264-0.265 | listForAdmin, find, save, delete, categoriesTree |
|
||||
| 18 | ShopCoupon | 0.266 | listForAdmin, find, save, delete, categoriesTree |
|
||||
| 19 | ShopStatuses | 0.267 | listForAdmin, find, save, color picker |
|
||||
|
||||
### Product - szczegolowy status
|
||||
- ✅ getQuantity (ver. 0.238)
|
||||
- ✅ getPrice (ver. 0.239)
|
||||
- ✅ getName (ver. 0.239)
|
||||
- ✅ archive / unarchive (ver. 0.241/0.252)
|
||||
- [ ] is_product_on_promotion
|
||||
- [ ] getFromCache
|
||||
- [ ] getProductImg
|
||||
|
||||
### 📋 Do zrobienia
|
||||
- Order
|
||||
- Category
|
||||
- ShopAttribute
|
||||
- ShopProduct (factory)
|
||||
|
||||
## Kolejność refaktoryzacji (priorytet)
|
||||
|
||||
1-13: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses
|
||||
|
||||
Nastepne:
|
||||
14. **Order**
|
||||
15. **Category**
|
||||
16. **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
|
||||
- Wspierane typy pól: text, number, email, password, date, datetime, switch, select, textarea, editor, image, file, hidden, lang_section, color
|
||||
- Obsługa zakładek (vertical) i sekcji językowych (horizontal)
|
||||
- **Do zrobienia**: Przerobić pozostałe kontrolery/formularze (Product, Category, Pages, itd.)
|
||||
|
||||
Pelna dokumentacja: `docs/FORM_EDIT_SYSTEM.md`
|
||||
|
||||
## Zasady kodu
|
||||
|
||||
### 1. SOLID Principles
|
||||
- **S**ingle Responsibility - jedna klasa = jedna odpowiedzialność
|
||||
- **O**pen/Closed - otwarty na rozszerzenia, zamknięty na modyfikacje
|
||||
- **L**iskov Substitution - podklasy mogą zastąpić nadklasy
|
||||
- **I**nterface Segregation - wiele małych interfejsów
|
||||
- **D**ependency Inversion - zależności od abstrakcji
|
||||
|
||||
### 2. Nazewnictwo
|
||||
- **Entity** - `Product.php` (reprezentuje obiekt domenowy)
|
||||
- **Repository** - `ProductRepository.php` (dostęp do danych)
|
||||
- **Service** - `ProductService.php` (logika biznesowa)
|
||||
- **Controller** - `ProductController.php` (obsługa requestów)
|
||||
|
||||
### 3. Type Hinting
|
||||
```php
|
||||
// ✅ DOBRE
|
||||
public function getQuantity(int $id): ?int {
|
||||
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
|
||||
// ❌ ZŁE
|
||||
public function getQuantity($id) {
|
||||
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
```
|
||||
|
||||
## Narzędzia pomocnicze
|
||||
|
||||
### Autoloader (produkcja)
|
||||
Autoloader w 9 entry pointach obsługuje dwie konwencje:
|
||||
1. `autoload/{namespace}/class.{ClassName}.php` (legacy)
|
||||
2. `autoload/{namespace}/{ClassName}.php` (PSR-4, fallback)
|
||||
|
||||
Entry pointy: `index.php`, `ajax.php`, `api.php`, `cron.php`, `cron-turstmate.php`, `download.php`, `admin/index.php`, `admin/ajax.php`, `cron/cron-xml.php`
|
||||
|
||||
### Static Analysis
|
||||
```bash
|
||||
composer require --dev phpstan/phpstan
|
||||
vendor/bin/phpstan analyse autoload/Domain
|
||||
```
|
||||
|
||||
## Testowanie
|
||||
|
||||
### Framework: PHPUnit
|
||||
```bash
|
||||
composer test
|
||||
```
|
||||
|
||||
### Struktura testów
|
||||
```
|
||||
tests/
|
||||
├── Unit/
|
||||
│ ├── Domain/
|
||||
│ │ ├── Article/ArticleRepositoryTest.php
|
||||
│ │ ├── Banner/BannerRepositoryTest.php
|
||||
│ │ ├── Cache/CacheRepositoryTest.php
|
||||
│ │ ├── Coupon/CouponRepositoryTest.php
|
||||
│ │ ├── Dictionaries/DictionariesRepositoryTest.php
|
||||
│ │ ├── Integrations/IntegrationsRepositoryTest.php
|
||||
│ │ ├── Product/ProductRepositoryTest.php
|
||||
│ │ ├── Promotion/PromotionRepositoryTest.php
|
||||
│ │ ├── Settings/SettingsRepositoryTest.php
|
||||
│ │ ├── ShopStatus/ShopStatusRepositoryTest.php
|
||||
│ │ └── User/UserRepositoryTest.php
|
||||
│ └── admin/
|
||||
│ └── Controllers/
|
||||
│ ├── ArticlesControllerTest.php
|
||||
│ ├── DictionariesControllerTest.php
|
||||
│ ├── IntegrationsControllerTest.php
|
||||
│ ├── ProductArchiveControllerTest.php
|
||||
│ ├── SettingsControllerTest.php
|
||||
│ ├── ShopCouponControllerTest.php
|
||||
│ ├── ShopPromotionControllerTest.php
|
||||
│ ├── ShopStatusesControllerTest.php
|
||||
│ └── UsersControllerTest.php
|
||||
└── Integration/
|
||||
```
|
||||
**Łącznie: 254 testów, 736 asercji**
|
||||
|
||||
Pelna dokumentacja testow: `TESTING.md`
|
||||
|
||||
---
|
||||
*Rozpoczęto: 2025-02-05*
|
||||
*Ostatnia aktualizacja: 2026-02-14*
|
||||
*Changelog zmian: `docs/CHANGELOG.md`*
|
||||
@@ -36,7 +36,7 @@ Alternatywnie (Git Bash):
|
||||
Ostatnio zweryfikowano: 2026-02-13
|
||||
|
||||
```text
|
||||
OK (235 tests, 682 assertions)
|
||||
OK (254 tests, 736 assertions)
|
||||
```
|
||||
|
||||
## Struktura testow
|
||||
@@ -51,10 +51,12 @@ tests/
|
||||
| | |-- Cache/CacheRepositoryTest.php
|
||||
| | |-- Coupon/CouponRepositoryTest.php
|
||||
| | |-- Dictionaries/DictionariesRepositoryTest.php
|
||||
| | |-- Integrations/IntegrationsRepositoryTest.php
|
||||
| | |-- Product/ProductRepositoryTest.php
|
||||
| | |-- Promotion/PromotionRepositoryTest.php
|
||||
| | |-- Settings/SettingsRepositoryTest.php
|
||||
| | |-- User/UserRepositoryTest.php
|
||||
| | `-- Integrations/IntegrationsRepositoryTest.php
|
||||
| | |-- ShopStatus/ShopStatusRepositoryTest.php
|
||||
| | `-- User/UserRepositoryTest.php
|
||||
| `-- admin/
|
||||
| `-- Controllers/
|
||||
| |-- ArticlesControllerTest.php
|
||||
@@ -63,6 +65,8 @@ tests/
|
||||
| |-- ProductArchiveControllerTest.php
|
||||
| |-- SettingsControllerTest.php
|
||||
| |-- ShopCouponControllerTest.php
|
||||
| |-- ShopPromotionControllerTest.php
|
||||
| |-- ShopStatusesControllerTest.php
|
||||
| `-- UsersControllerTest.php
|
||||
`-- Integration/
|
||||
```
|
||||
@@ -334,3 +338,14 @@ Nowe testy dodane 2026-02-13:
|
||||
|
||||
Ponowna weryfikacja po poprawkach UI (drzewko + checkboxy): 2026-02-13
|
||||
- `OK (235 tests, 682 assertions)`
|
||||
|
||||
## Aktualizacja suite (ShopStatuses refactor, ver. 0.267)
|
||||
Ostatnio zweryfikowano: 2026-02-14
|
||||
|
||||
```text
|
||||
OK (254 tests, 736 assertions)
|
||||
```
|
||||
|
||||
Nowe testy dodane 2026-02-14:
|
||||
- `tests/Unit/Domain/ShopStatus/ShopStatusRepositoryTest.php` (9 testow: find z ID=0, find null apilo, save update, save z ID=0, empty apilo sets null, reject negative ID, getApiloStatusId, getByIntegrationStatusId, allStatuses, whitelist sortowania)
|
||||
- `tests/Unit/admin/Controllers/ShopStatusesControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora)
|
||||
253
tests/Unit/Domain/ShopStatus/ShopStatusRepositoryTest.php
Normal file
253
tests/Unit/Domain/ShopStatus/ShopStatusRepositoryTest.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
namespace Tests\Unit\Domain\ShopStatus;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Domain\ShopStatus\ShopStatusRepository;
|
||||
|
||||
class ShopStatusRepositoryTest extends TestCase
|
||||
{
|
||||
public function testFindReturnsNullForNegativeId(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->never())->method('get');
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$this->assertNull($repository->find(-1));
|
||||
}
|
||||
|
||||
public function testFindReturnsNullWhenNotFound(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->once())
|
||||
->method('get')
|
||||
->with('pp_shop_statuses', '*', ['id' => 99])
|
||||
->willReturn(null);
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$this->assertNull($repository->find(99));
|
||||
}
|
||||
|
||||
public function testFindReturnsStatusWithIdZero(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->once())
|
||||
->method('get')
|
||||
->with('pp_shop_statuses', '*', ['id' => 0])
|
||||
->willReturn([
|
||||
'id' => '0',
|
||||
'status' => 'zamówienie złożone',
|
||||
'color' => '#ff0000',
|
||||
'o' => '0',
|
||||
'apilo_status_id' => '5',
|
||||
]);
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$result = $repository->find(0);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertSame(0, $result['id']);
|
||||
$this->assertSame('zamówienie złożone', $result['status']);
|
||||
$this->assertSame('#ff0000', $result['color']);
|
||||
$this->assertSame(0, $result['o']);
|
||||
$this->assertSame(5, $result['apilo_status_id']);
|
||||
}
|
||||
|
||||
public function testFindNormalizesNullApiloStatusId(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->once())
|
||||
->method('get')
|
||||
->with('pp_shop_statuses', '*', ['id' => 1])
|
||||
->willReturn([
|
||||
'id' => '1',
|
||||
'status' => 'zamówienie opłacone',
|
||||
'color' => '',
|
||||
'o' => '1',
|
||||
'apilo_status_id' => null,
|
||||
]);
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$result = $repository->find(1);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertSame(1, $result['id']);
|
||||
$this->assertNull($result['apilo_status_id']);
|
||||
}
|
||||
|
||||
public function testSaveUpdatesColorAndApiloStatusId(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$updateRow = null;
|
||||
$updateWhere = null;
|
||||
|
||||
$mockDb->expects($this->once())
|
||||
->method('update')
|
||||
->willReturnCallback(function ($table, $row, $where) use (&$updateRow, &$updateWhere) {
|
||||
$this->assertSame('pp_shop_statuses', $table);
|
||||
$updateRow = $row;
|
||||
$updateWhere = $where;
|
||||
return true;
|
||||
});
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$id = $repository->save(3, [
|
||||
'color' => ' #00ff00 ',
|
||||
'apilo_status_id' => '12',
|
||||
]);
|
||||
|
||||
$this->assertSame(3, $id);
|
||||
$this->assertSame('#00ff00', $updateRow['color']);
|
||||
$this->assertSame(12, $updateRow['apilo_status_id']);
|
||||
$this->assertSame(['id' => 3], $updateWhere);
|
||||
}
|
||||
|
||||
public function testSaveWithIdZeroWorks(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->once())
|
||||
->method('update')
|
||||
->with('pp_shop_statuses', $this->anything(), ['id' => 0]);
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$id = $repository->save(0, ['color' => '#aabbcc']);
|
||||
|
||||
$this->assertSame(0, $id);
|
||||
}
|
||||
|
||||
public function testSaveWithEmptyApiloStatusIdSetsNull(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$updateRow = null;
|
||||
|
||||
$mockDb->expects($this->once())
|
||||
->method('update')
|
||||
->willReturnCallback(function ($table, $row) use (&$updateRow) {
|
||||
$updateRow = $row;
|
||||
});
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$repository->save(1, ['color' => '#000', 'apilo_status_id' => '']);
|
||||
|
||||
$this->assertNull($updateRow['apilo_status_id']);
|
||||
}
|
||||
|
||||
public function testSaveRejectsNegativeId(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->never())->method('update');
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$this->assertSame(0, $repository->save(-1, ['color' => '#fff']));
|
||||
}
|
||||
|
||||
public function testGetApiloStatusIdReturnsValue(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->once())
|
||||
->method('get')
|
||||
->with('pp_shop_statuses', 'apilo_status_id', ['id' => 4])
|
||||
->willReturn(15);
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$this->assertSame(15, $repository->getApiloStatusId(4));
|
||||
}
|
||||
|
||||
public function testGetApiloStatusIdReturnsNullWhenNotSet(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn(null);
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$this->assertNull($repository->getApiloStatusId(4));
|
||||
}
|
||||
|
||||
public function testGetByIntegrationStatusIdForApilo(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->once())
|
||||
->method('get')
|
||||
->with('pp_shop_statuses', 'id', ['apilo_status_id' => 15])
|
||||
->willReturn(4);
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$this->assertSame(4, $repository->getByIntegrationStatusId('apilo', 15));
|
||||
}
|
||||
|
||||
public function testGetByIntegrationStatusIdReturnsNullForUnknownIntegration(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->never())->method('get');
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$this->assertNull($repository->getByIntegrationStatusId('unknown', 1));
|
||||
}
|
||||
|
||||
public function testAllStatusesReturnsOrderedList(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->once())
|
||||
->method('select')
|
||||
->with('pp_shop_statuses', ['id', 'status'], ['ORDER' => ['o' => 'ASC']])
|
||||
->willReturn([
|
||||
['id' => 0, 'status' => 'złożone'],
|
||||
['id' => 1, 'status' => 'opłacone'],
|
||||
]);
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$result = $repository->allStatuses();
|
||||
|
||||
$this->assertSame([0 => 'złożone', 1 => 'opłacone'], $result);
|
||||
}
|
||||
|
||||
public function testListForAdminWhitelistsSortAndDirection(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$queries = [];
|
||||
|
||||
$mockDb->method('query')
|
||||
->willReturnCallback(function ($sql, $params = []) use (&$queries) {
|
||||
$queries[] = ['sql' => $sql, 'params' => $params];
|
||||
|
||||
if (strpos($sql, 'COUNT(0)') !== false) {
|
||||
return new class {
|
||||
public function fetchAll()
|
||||
{
|
||||
return [[2]];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new class {
|
||||
public function fetchAll()
|
||||
{
|
||||
return [[
|
||||
'id' => 0,
|
||||
'status' => 'złożone',
|
||||
'color' => '#fff',
|
||||
'o' => 0,
|
||||
'apilo_status_id' => null,
|
||||
]];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$repository = new ShopStatusRepository($mockDb);
|
||||
$result = $repository->listForAdmin(
|
||||
[],
|
||||
'status DESC; DROP TABLE pp_shop_statuses; --',
|
||||
'DESC; DELETE FROM pp_users; --',
|
||||
1,
|
||||
999
|
||||
);
|
||||
|
||||
$this->assertCount(2, $queries);
|
||||
$dataSql = $queries[1]['sql'];
|
||||
|
||||
$this->assertMatchesRegularExpression('/ORDER BY\s+ss\.o\s+ASC,\s+ss\.id\s+ASC/i', $dataSql);
|
||||
$this->assertStringNotContainsString('DROP TABLE', $dataSql);
|
||||
$this->assertStringNotContainsString('DELETE FROM pp_users', $dataSql);
|
||||
$this->assertMatchesRegularExpression('/LIMIT\s+100\s+OFFSET\s+0/i', $dataSql);
|
||||
}
|
||||
}
|
||||
57
tests/Unit/admin/Controllers/ShopStatusesControllerTest.php
Normal file
57
tests/Unit/admin/Controllers/ShopStatusesControllerTest.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace Tests\Unit\admin\Controllers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use admin\Controllers\ShopStatusesController;
|
||||
use Domain\ShopStatus\ShopStatusRepository;
|
||||
|
||||
class ShopStatusesControllerTest extends TestCase
|
||||
{
|
||||
private $repository;
|
||||
private $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = $this->createMock(ShopStatusRepository::class);
|
||||
$this->controller = new ShopStatusesController($this->repository);
|
||||
}
|
||||
|
||||
public function testConstructorAcceptsRepository(): void
|
||||
{
|
||||
$controller = new ShopStatusesController($this->repository);
|
||||
$this->assertInstanceOf(ShopStatusesController::class, $controller);
|
||||
}
|
||||
|
||||
public function testHasMainActionMethods(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'list'));
|
||||
$this->assertTrue(method_exists($this->controller, 'edit'));
|
||||
$this->assertTrue(method_exists($this->controller, 'save'));
|
||||
}
|
||||
|
||||
public function testHasNoLegacyAliasMethods(): void
|
||||
{
|
||||
$this->assertFalse(method_exists($this->controller, 'view_list'));
|
||||
$this->assertFalse(method_exists($this->controller, 'status_edit'));
|
||||
$this->assertFalse(method_exists($this->controller, 'status_save'));
|
||||
}
|
||||
|
||||
public function testActionMethodReturnTypes(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType());
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('edit')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType());
|
||||
}
|
||||
|
||||
public function testConstructorRequiresShopStatusRepository(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(ShopStatusesController::class);
|
||||
$constructor = $reflection->getConstructor();
|
||||
$params = $constructor->getParameters();
|
||||
|
||||
$this->assertCount(1, $params);
|
||||
$this->assertEquals('Domain\ShopStatus\ShopStatusRepository', $params[0]->getType()->getName());
|
||||
}
|
||||
}
|
||||
BIN
updates/0.20/ver_0.268.zip
Normal file
BIN
updates/0.20/ver_0.268.zip
Normal file
Binary file not shown.
7
updates/0.20/ver_0.268_files.txt
Normal file
7
updates/0.20/ver_0.268_files.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
F: ../autoload/admin/controls/class.ShopStatuses.php
|
||||
F: ../autoload/admin/factory/class.ShopStatuses.php
|
||||
F: ../PROJECT_STRUCTURE.md
|
||||
F: ../REFACTORING_PLAN.md
|
||||
F: ../DATABASE_STRUCTURE.md
|
||||
F: ../TESTING.md
|
||||
F: ../UPDATE_INSTRUCTIONS.md
|
||||
@@ -1,3 +1,14 @@
|
||||
<b>ver. 0.268 - 14.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopStatuses` do architektury Domain + DI (`Domain\ShopStatus\ShopStatusRepository`, `admin\Controllers\ShopStatusesController`)
|
||||
- UPDATE - modul `/admin/shop_statuses/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`
|
||||
- NEW - nowy typ pola formularza `color` (HTML5 color picker + pole tekstowe zsynchronizowane)
|
||||
- UPDATE - `front\factory\ShopStatuses` dziala jako fasada do `Domain\ShopStatus\ShopStatusRepository`
|
||||
- UPDATE - menu admin przepiete na kanoniczny URL `/admin/shop_statuses/list/`
|
||||
- CLEANUP - usuniete legacy klasy: `autoload/admin/controls/class.ShopStatuses.php`, `autoload/admin/factory/class.ShopStatuses.php`
|
||||
- UPDATE - reorganizacja dokumentacji technicznej: pliki przeniesione do folderu `docs/` i rozbite na mniejsze pliki tematyczne
|
||||
- UPDATE - testy: `OK (254 tests, 736 assertions)` + nowe pliki testowe `ShopStatusRepositoryTest`, `ShopStatusesControllerTest`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.268.zip`, `ver_0.268_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.267 - 13.02.2026</b><br />
|
||||
- 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`)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?
|
||||
$current_ver = 267;
|
||||
$current_ver = 268;
|
||||
|
||||
for ($i = 1; $i <= $current_ver; $i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user