# 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` #### Sellasist - **Aktualizacja produktów:** Linia 111-149 - **Funkcje:** Aktualizacja cen i stanów magazynowych - **Częstotliwość:** Co 10 minut - **Czyszczenie cache:** Linia 146 #### Apilo - **Aktualizacja pojedynczego produktu:** Linia 152-176 - Częstotliwość: Co 10 minut - Czyszczenie cache: Linia 173 - **Synchronizacja cennika:** Linia 179-218 - Częstotliwość: Co 1 godzinę - Czyszczenie cache: Linia 212 #### Baselinker - **Aktualizacja produktów:** Linia 220-289 - **Funkcje:** Ceny, stany magazynowe, wagi - **Częstotliwość:** Co 24 godziny (1440 minut) - **Czyszczenie cache:** Linia 278 ## 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`: - `sellasist_product_id`, `sellasist_get_data_date` - `apilo_product_id`, `apilo_get_data_date` - `baselinker_product_id`, `baselinker_get_data_date` ## 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 users dziaa na `Domain\\User\\UserRepository` + `admin\\Controllers\\UsersController`. - Usunito legacy klasy: `autoload/admin/controls/class.Users.php`, `autoload/admin/factory/class.Users.php`, `autoload/admin/view/class.Users.php`. - Walidacja: przy wczonym 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 `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`.