# PLAN REFAKTORINGU cmsPRO ## Cel Przenieść architekturę z shopPRO do cmsPRO: - **Domain-Driven Design** z warstwą `\Domain\` (repozytoria z DI) - **Kontrolery z Dependency Injection** (zamiast statycznych `controls` + `factory`) - **Nowy system aktualizacji** z manifestami JSON, checksumami SHA256, backupami - **Shared utilities** w namespace `\Shared\` - **Testy PHPUnit** - **Automatyczny build paczek** (`build-update.ps1`) --- ## STAN OBECNY cmsPRO vs DOCELOWY ### Obecna architektura (legacy) ``` autoload/ ├── admin/ │ ├── class.Site.php ← Router (statyczne metody, global $mdb) │ ├── controls/ ← 16 kontrolerów (statyczne, global $mdb) │ ├── factory/ ← 15 klas logiki biznesowej (statyczne, global $mdb) │ └── view/ ← 17 klas widoków (statyczne) ├── front/ │ ├── controls/ ← 4 kontrolery │ ├── factory/ ← 13 klas logiki │ └── view/ ← 8 klas widoków ├── class.S.php ← Mega-utility (sesja, cache, email, SEO, token, DOM...) ├── class.Tpl.php ← Template engine ├── class.Cache.php ← Cache plikowy (gzdeflate + MD5) ├── class.Html.php ← Komponenty formularzy HTML ├── class.Image.php ← ImageManipulator ├── class.Article.php ← Model artykułu z ArrayAccess ├── class.Scontainer.php ← Kontenery statyczne z ArrayAccess ├── class.Page.php ← Model strony ├── class.Mobile_Detect.php ← Detekcja mobilnych └── class.geoplugin.php ← GeoIP ``` ### Architektura docelowa (wzór: shopPRO) ``` autoload/ ├── Domain/ ← NOWE: warstwa domenowa │ ├── Article/ │ │ └── ArticleRepository.php │ ├── Banner/ │ │ └── BannerRepository.php │ ├── Languages/ │ │ └── LanguagesRepository.php │ ├── Layouts/ │ │ └── LayoutsRepository.php │ ├── Newsletter/ │ │ ├── NewsletterRepository.php │ │ └── NewsletterPreviewRenderer.php │ ├── Pages/ │ │ └── PagesRepository.php │ ├── Scontainers/ │ │ └── ScontainersRepository.php │ ├── Settings/ │ │ └── SettingsRepository.php │ ├── Update/ │ │ └── UpdateRepository.php │ ├── User/ │ │ └── UserRepository.php │ ├── Author/ │ │ └── AuthorRepository.php │ ├── SeoAdditional/ │ │ └── SeoAdditionalRepository.php │ ├── Cache/ │ │ └── CacheRepository.php │ ├── Email/ │ │ └── EmailRepository.php │ └── Backup/ │ └── BackupRepository.php ├── Shared/ ← NOWE: wspólne utility │ ├── Helpers/ │ │ └── Helpers.php ← Refaktor z class.S.php │ ├── Tpl/ │ │ └── Tpl.php ← Refaktor z class.Tpl.php │ ├── Cache/ │ │ └── CacheHandler.php ← Refaktor z class.Cache.php │ ├── Html/ │ │ └── Html.php ← Refaktor z class.Html.php │ ├── Image/ │ │ └── ImageManipulator.php ← Refaktor z class.Image.php │ └── Email/ │ └── Email.php ← PHPMailer wrapper ├── admin/ │ ├── App.php ← NOWY: Router + DI container (zamienia Site.php) │ ├── Controllers/ ← NOWE: kontrolery z DI │ │ ├── ArticlesController.php │ │ ├── ArticlesArchiveController.php │ │ ├── AuthorsController.php │ │ ├── BackupsController.php │ │ ├── BannerController.php │ │ ├── EmailsController.php │ │ ├── FilemanagerController.php │ │ ├── LanguagesController.php │ │ ├── LayoutsController.php │ │ ├── NewsletterController.php │ │ ├── PagesController.php │ │ ├── ScontainersController.php │ │ ├── SeoAdditionalController.php │ │ ├── SettingsController.php │ │ ├── UpdateController.php │ │ └── UsersController.php │ ├── controls/ ← USUNĄĆ (po migracji) │ ├── factory/ ← USUNĄĆ (po migracji) │ └── view/ ← USUNĄĆ (po migracji — logic do Controllers) ├── front/ │ ├── App.php ← NOWY: Router + DI container │ ├── LayoutEngine.php ← NOWY: refaktor z front\view\Site::show() │ ├── Controllers/ ← NOWE: kontrolery z DI │ │ ├── NewsletterController.php │ │ └── SearchController.php (+ ewentualne inne) │ ├── Views/ ← NOWE: statyczne klasy renderujące (z front\view\) │ │ ├── Articles.php │ │ ├── Banners.php │ │ ├── Languages.php │ │ ├── Menu.php │ │ ├── Newsletter.php │ │ ├── Scontainers.php │ │ └── Search.php │ ├── controls/ ← USUNĄĆ (po migracji) │ ├── factory/ ← USUNĄĆ (po migracji) │ └── view/ ← USUNĄĆ (po migracji — do Views/ + LayoutEngine) └── (stare class.*.php) ← USUNĄĆ (po migracji do Shared/) ``` --- ## FAZY REFAKTORINGU Każda faza kończy się działającym systemem. Stary i nowy kod koegzystują aż do pełnej migracji. --- ## FAZA 0: Przygotowanie infrastruktury ### 0.1 Aktualizacja autoloadera **Obecny autoloader** (index.php, admin/index.php) szuka tylko `class.{Name}.php`: ```php $f = 'autoload/' . implode('/', $q) . '/class.' . $c . '.php'; ``` **Docelowy** — musi obsługiwać OBE konwencje (fallback PSR-4): ```php function __autoload_my_classes($classname) { $q = explode('\\', $classname); $c = array_pop($q); // 1. Legacy: class.ClassName.php $f = 'autoload/' . implode('/', $q) . '/class.' . $c . '.php'; if (file_exists($f)) { require_once($f); return; } // 2. PSR-4: ClassName.php $f = 'autoload/' . implode('/', $q) . '/' . $c . '.php'; if (file_exists($f)) require_once($f); } ``` **Pliki do zmiany:** - `index.php` (linia ~3-11) - `admin/index.php` (linia ~15-22, ścieżka `../autoload/`) - `ajax.php` (jeśli ma autoloader) - `admin/ajax.php` - `api.php` - `cron.php` ### 0.2 Utworzenie katalogów ``` mkdir autoload/Domain mkdir autoload/Shared mkdir autoload/Shared/Helpers mkdir autoload/Shared/Tpl mkdir autoload/Shared/Cache mkdir autoload/Shared/Html mkdir autoload/Shared/Image mkdir autoload/Shared/Email mkdir autoload/admin/Controllers mkdir autoload/front/Controllers mkdir autoload/front/Views mkdir tests mkdir tests/Unit mkdir tests/Unit/Domain mkdir tests/stubs mkdir migrations mkdir docs mkdir backups ``` ### 0.3 Konfiguracja PHPUnit Skopiować z shopPRO: - `phpunit.xml` - `phpunit.phar` - `test.ps1` - `tests/bootstrap.php` - `composer.json` (sekcja `require-dev`) ### 0.4 Git tagging — ustalenie punktu startowego ```bash git tag v1.689 # bieżąca wersja cmsPRO git push origin v1.689 ``` Od następnej wersji (1.690) zaczyna się nowy system aktualizacji. ### 0.5 Utworzenie CLAUDE.md i AGENTS.md Skopiować strukturę z shopPRO, dostosować do cmsPRO (brak modułu sklepowego, inne moduły). --- ## FAZA 1: Shared Utilities (przeniesienie klas globalnych) Kolejność: Helpers → Tpl → Cache → Html → Image → Email. Po każdym kroku starą klasę zostawiamy jako wrapper (deleguje do nowej), żeby nic się nie zepsuło. ### 1.1 `\Shared\Helpers\Helpers` (z `class.S.php`) **Plik:** `autoload/Shared/Helpers/Helpers.php` Skopiować WSZYSTKIE metody z `\S` do `\Shared\Helpers\Helpers`: - `get()`, `get_session()`, `set_session()`, `delete_session()` - `alert()`, `set_message()`, `lang()` - `send_email()`, `email_check()` - `seo()`, `noPL()` - `cache_write()`, `cache_read()`, `cache_file_url()` - `htacces()` - `delete_cache()`, `delete_dir()`, `is_empty_dir()` - `date_diff()`, `months()`, `months_short()` - `get_token()`, `is_token_valid()` - DOM manipulation: `suSetHtmlById()`, `suAddHtmlById()`, itd. - `get_version()`, `get_new_version()` - `Pre()`, `json_to_array()`, `is_bot()`, `is_mobile()` - `generate_webp_image()` **Stary `class.S.php`** — zamienić na wrapper: ```php class S { public static function __callStatic($name, $args) { return call_user_func_array(['\Shared\Helpers\Helpers', $name], $args); } // LUB: jawne delegacje dla każdej metody } ``` **UWAGA:** `\S::` jest używane WSZĘDZIE w cmsPRO (controls, factory, view, templates, index.php). Delegacja przez wrapper pozwala na stopniową migrację referencji. ### 1.2 `\Shared\Tpl\Tpl` (z `class.Tpl.php`) **Plik:** `autoload/Shared/Tpl/Tpl.php` **Namespace:** `\Shared\Tpl\` Stary `class.Tpl.php` → wrapper delegujący do `\Shared\Tpl\Tpl`. ### 1.3 `\Shared\Cache\CacheHandler` (z `class.Cache.php`) **Plik:** `autoload/Shared/Cache/CacheHandler.php` **Namespace:** `\Shared\Cache\` cmsPRO używa cache plikowego (gzdeflate + MD5), NIE Redis. Zachować tę samą implementację, tylko przenieść do nowego namespace. Stary `class.Cache.php` → wrapper. ### 1.4 `\Shared\Html\Html` (z `class.Html.php`) **Plik:** `autoload/Shared/Html/Html.php` Stary `class.Html.php` → wrapper. ### 1.5 `\Shared\Image\ImageManipulator` (z `class.Image.php`) **Plik:** `autoload/Shared/Image/ImageManipulator.php` Stary `class.Image.php` (klasa `ImageManipulator`) → wrapper lub alias. ### 1.6 `\Shared\Email\Email` **Plik:** `autoload/Shared/Email/Email.php` Nowa klasa opakowująca PHPMailer (wzór: shopPRO `Shared\Email\Email`). ### 1.7 Usunięcie starych klas globalnych **PO PEŁNEJ MIGRACJI** wszystkich referencji (to będzie trwać przez kolejne fazy): - `class.Article.php` → logika do `Domain\Article\ArticleRepository` - `class.Scontainer.php` → logika do `Domain\Scontainers\ScontainersRepository` - `class.Page.php` → logika do `Domain\Pages\PagesRepository` `class.Mobile_Detect.php` i `class.geoplugin.php` zostawić (biblioteki zewnętrzne). --- ## FAZA 2: Warstwa Domain (repozytoria) Dla KAŻDEGO modułu: wyciągnąć logikę z `admin\factory\*` i `front\factory\*` do wspólnego repozytorium w `Domain\`. ### Wzorzec repozytorium (kopiowany z shopPRO) ```php db = $db; } public function find(int $id): ?array { return $this->db->get('pp_articles', '*', ['id' => $id]); } // ... reszta metod } ``` ### Lista modułów do migracji (16 modułów) | # | Moduł | Źródło (factory) | Cel (Domain) | Priorytet | |---|-------|-------------------|--------------|-----------| | 1 | **Settings** | `admin\factory\Settings` + `front\factory\Settings` | `Domain\Settings\SettingsRepository` | WYSOKI — używany wszędzie | | 2 | **Languages** | `admin\factory\Languages` + `front\factory\Languages` | `Domain\Languages\LanguagesRepository` | WYSOKI — używany w bootstrap | | 3 | **Users** | `admin\factory\Users` | `Domain\User\UserRepository` | WYSOKI — login, 2FA, uprawnienia | | 4 | **Pages** | `admin\factory\Pages` + `front\factory\Pages` | `Domain\Pages\PagesRepository` | WYSOKI — routing frontu | | 5 | **Articles** | `admin\factory\Articles` + `front\factory\Articles` + `class.Article.php` | `Domain\Article\ArticleRepository` | WYSOKI — główna funkcjonalność | | 6 | **Layouts** | `admin\factory\Layouts` + `front\factory\Layouts` | `Domain\Layouts\LayoutsRepository` | WYSOKI — layout engine | | 7 | **Scontainers** | `admin\factory\Scontainers` + `front\factory\Scontainers` + `class.Scontainer.php` | `Domain\Scontainers\ScontainersRepository` | ŚREDNI | | 8 | **Banners** | `admin\factory\Banners` + `front\factory\Banners` | `Domain\Banner\BannerRepository` | ŚREDNI | | 9 | **Newsletter** | `admin\factory\Newsletter` + `front\factory\Newsletter` | `Domain\Newsletter\NewsletterRepository` | ŚREDNI | | 10 | **Authors** | `admin\factory\Authors` + `front\factory\Authors` | `Domain\Author\AuthorRepository` | NISKI | | 11 | **Emails** | `admin\factory\Emails` | `Domain\Email\EmailRepository` | NISKI | | 12 | **Backups** | `admin\factory\Backups` | `Domain\Backup\BackupRepository` | NISKI | | 13 | **SeoAdditional** | `admin\factory\SeoAdditional` + `front\factory\SeoAdditional` | `Domain\SeoAdditional\SeoAdditionalRepository` | NISKI | | 14 | **ArticlesArchive** | `admin\factory\ArticlesArchive` | (część ArticleRepository) | NISKI | | 15 | **Menu** | `front\factory\Menu` | `Domain\Menu\MenuRepository` | ŚREDNI | | 16 | **Update** | `admin\factory\Update` | `Domain\Update\UpdateRepository` | WYSOKI — nowy system | ### Proces migracji jednego modułu (przykład: Settings) 1. **Utworzyć** `autoload/Domain/Settings/SettingsRepository.php` 2. **Przenieść metody** z `admin\factory\Settings` i `front\factory\Settings`: - `settings_details()` → `allSettings()` - Zamienić `global $mdb` → `$this->db` 3. **Napisać testy** w `tests/Unit/Domain/Settings/SettingsRepositoryTest.php` 4. **Zaktualizować** wywołujących (controls, view, index.php): - `\front\factory\Settings::settings_details()` → `$settingsRepo->allSettings()` 5. **Stary factory** — zostawić tymczasowo jako wrapper: ```php namespace front\factory; class Settings { public static function settings_details() { global $mdb; return (new \Domain\Settings\SettingsRepository($mdb))->allSettings(); } } ``` 6. **Po migracji wszystkich wywołujących** — usunąć stary factory. ### Specjalne przypadki **`class.Article.php`** — to model z ArrayAccess, nie zwykły factory: - Konstruktor ładuje artykuł z bazy + images + files + pages + tags + params - Szablony frontu używają `$article->field` (obiekt) - **Strategia:** Przenieść logikę do `ArticleRepository`, zwracać tablice. Szablony frontu zostawić na później (użyć wrappera ArrayAccess tymczasowo). **`class.Scontainer.php`** — analogicznie, implementuje ArrayAccess. --- ## FAZA 3: Admin Controllers z DI ### 3.1 Nowy router: `admin\App` **Plik:** `autoload/admin/App.php` Wzorowany 1:1 na shopPRO `admin\App`. Kluczowe elementy: ```php twofa(); } if (!$user || !$user['admin']) { $controller = self::createController('Users'); return $controller->login_form(); } $tpl = new \Shared\Tpl\Tpl; $tpl->content = self::route(); return $tpl->render('site/main-layout'); } public static function route() { $_SESSION['admin'] = true; $moduleName = ''; $parts = explode('_', (string)\Shared\Helpers\Helpers::get('module')); foreach ($parts as $part) $moduleName .= ucfirst($part); $action = \Shared\Helpers\Helpers::get('action'); // Najpierw nowe kontrolery z DI $controller = self::createController($moduleName); if ($controller && method_exists($controller, $action)) return $controller->$action(); // Fallback do starych controls (okres przejściowy) $class = '\admin\controls\\' . $moduleName; if (class_exists($class) && method_exists(new $class, $action)) return call_user_func_array([$class, $action], []); \Shared\Helpers\Helpers::alert('Nieprawidłowy adres url.'); return false; } private static function createController(string $moduleName) { $factories = self::getControllerFactories(); if (!isset($factories[$moduleName])) return null; $factory = $factories[$moduleName]; return is_callable($factory) ? $factory() : null; } private static function getControllerFactories(): array { if (!empty(self::$newControllers)) return self::$newControllers; self::$newControllers = [ // Dodawane stopniowo w miarę migracji modułów ]; return self::$newControllers; } public static function update() { global $mdb; $repository = new \Domain\Update\UpdateRepository($mdb); $repository->runPendingMigrations(); } } ``` **Przełączenie:** - W `admin/index.php`: zamienić `\admin\Site::special_actions()` → `\admin\App::special_actions()` - Zamienić `\admin\view\Page::show()` → `\admin\App::render()` - Dodać `\admin\App::update()` (migracje SQL) Fallback do starych `\admin\controls\*` zapewnia, że system działa ZANIM wszystkie kontrolery zostaną zmigrowane. ### 3.2 Nowe kontrolery admin (po jednym na moduł) **Wzorzec kontrolera** (kopiowany z shopPRO): ```php articleRepo = $articleRepo; $this->langRepo = $langRepo; } public function list() { // logika z admin\controls\Articles::view_list() // + renderowanie szablonu } public function edit() { // logika z admin\controls\Articles::article_edit() } public function save() { // logika z admin\controls\Articles::article_save() } } ``` ### 3.3 Kolejność migracji kontrolerów admin | # | Kontroler | Factory → Repository | Wiring w App | |---|-----------|---------------------|--------------| | 1 | `UsersController` | `Users` → `UserRepository` | `'Users' => fn() => new UsersController(new UserRepository($mdb))` | | 2 | `SettingsController` | `Settings` → `SettingsRepository` | `'Settings' => fn() => ...` | | 3 | `UpdateController` | `Update` → `UpdateRepository` | `'Update' => fn() => ...` | | 4 | `LanguagesController` | `Languages` → `LanguagesRepository` | | | 5 | `PagesController` | `Pages` → `PagesRepository` | | | 6 | `ArticlesController` | `Articles` → `ArticleRepository` | | | 7 | `ArticlesArchiveController` | `ArticlesArchive` | | | 8 | `LayoutsController` | `Layouts` → `LayoutsRepository` | | | 9 | `ScontainersController` | `Scontainers` → `ScontainersRepository` | | | 10 | `BannerController` | `Banners` → `BannerRepository` | | | 11 | `NewsletterController` | `Newsletter` → `NewsletterRepository` | | | 12 | `AuthorsController` | `Authors` → `AuthorRepository` | | | 13 | `EmailsController` | `Emails` → `EmailRepository` | | | 14 | `BackupsController` | `Backups` → `BackupRepository` | | | 15 | `SeoAdditionalController` | `SeoAdditional` → `SeoAdditionalRepository` | | | 16 | `FilemanagerController` | (brak factory — sam w sobie) | | ### 3.4 Mapowanie akcji (stare → nowe nazwy metod) Konwencja shopPRO: camelCase zamiast snake_case. | Stara akcja (URL) | Nowa metoda | |--------------------|-------------| | `view_list` | `list()` | | `article_edit` | `edit()` | | `article_save` | `save()` | | `article_delete` | `delete()` | | `main_view` | `main_view()` (bez zmian, bo URL tego wymaga) | **UWAGA:** URL w admin to `?module=articles&action=view_list`. Router konwertuje `_` na ucfirst, ale akcja jest przekazywana dosłownie. Trzeba zachować dokładne nazwy metod odpowiadające `action` w URL, LUB dodać mapowanie camelCase. Rekomendacja: dodać konwersję w routerze (jak shopPRO front\App robi `lcfirst(implode('', array_map('ucfirst', explode('_', $action))))`) LUB zostawić snake_case w nazwach metod kontrolerów. --- ## FAZA 4: Frontend (App + Controllers + Views + LayoutEngine) ### 4.1 `front\App` — nowy router **Plik:** `autoload/front/App.php` Wzorowany na shopPRO. Kluczowe: - `checkUrlParams()` — obsługa `?a=page`, `?a=change_language` - `route()` — routing do artykułów + kontrolerów DI + fallback do starych controls - `getControllerFactories()` — fabryki kontrolerów - `pageTitle()`, `title()` — tytuły stron ### 4.2 `front\LayoutEngine` — silnik szablonów **Plik:** `autoload/front/LayoutEngine.php` Refaktor z `front\view\Site::show()`. Odpowiedzialny za: - Ładowanie layoutu (aktywny dla strony) - Zastępowanie tagów: `[MENU:id]`, `[KONTENER:id]`, `[AKTUALNOSCI:...]`, `[ZAWARTOSC]`, `[TITLE]`, `[LANG:key]`, itp. - Kontakt (formularz) - Dodatkowe kody (statystyki, cookies) ### 4.3 Frontend Views (statyczne) Przenieść `front\view\*` → `front\Views\*` (nowy namespace): | Stara klasa | Nowa klasa | |-------------|------------| | `\front\view\Articles` | `\front\Views\Articles` | | `\front\view\Banners` | `\front\Views\Banners` | | `\front\view\Languages` | `\front\Views\Languages` | | `\front\view\Menu` | `\front\Views\Menu` | | `\front\view\Newsletter` | `\front\Views\Newsletter` | | `\front\view\Scontainers` | `\front\Views\Scontainers` | | `\front\view\Search` | `\front\Views\Search` | | `\front\view\Site` | → `\front\LayoutEngine` + `\front\Views\*` | **Klasy Views NIE mają DI** — to czyste renderery statyczne (wzór: shopPRO). ### 4.4 Frontend Controllers | Kontroler | Źródło | DI | |-----------|--------|-----| | `NewsletterController` | `front\controls\Newsletter` + `front\factory\Newsletter` | `NewsletterRepository` | | `SearchController` | `front\controls\*` (jeśli istnieje logika) | brak | | `ArticlesController` (opcjonalnie) | `front\controls\Articles` | `ArticleRepository` | ### 4.5 Przełączenie entry pointów **`index.php`:** ```php // Stare: \front\controls\Site::check_url_params(); $out = \front\view\Site::show(); // Nowe: \front\App::checkUrlParams(); $out = \front\LayoutEngine::show(); ``` **`admin/index.php`:** ```php // Stare: \admin\Site::special_actions(); echo \admin\view\Page::show(); // Nowe: \admin\App::special_actions(); \admin\App::update(); echo \admin\App::render(); ``` --- ## FAZA 5: Nowy system aktualizacji ### KLUCZOWA ZASADA: Pierwsza paczka musi być legacy! **Pierwsza aktualizacja (v1.690) MUSI być opublikowana starą metodą** (ZIP + `_sql.txt` + `_files.txt`, BEZ manifestu). To dlatego, że v1.690 właśnie wgrywa nowy `UpdateRepository` z obsługą manifestów JSON na serwery klientów. Klienci mają jeszcze stary kod aktualizacji (`admin\factory\Update::update()`), który rozumie TYLKO legacy format. **Sekwencja:** 1. **v1.690** — budujemy **ręcznie starą metodą** (lub `build-update.ps1` z flagą `--legacy-only`). Ta paczka zawiera nowy `UpdateRepository`, nowy `admin\App`, nowy autoloader — całą infrastrukturę. 2. **v1.691+** — od tego momentu budujemy normalnie z manifestem JSON. Klienci już mają nowy `UpdateRepository` (wgrany przez v1.690), który obsługuje oba formaty. Innymi słowy: v1.690 to "paczka-most" — dostarczana starym kanałem, ale wgrywająca nowy silnik aktualizacji. ### 5.1 `Domain\Update\UpdateRepository` — pełna logika aktualizacji **Plik:** `autoload/Domain/Update/UpdateRepository.php` Skopiować z shopPRO `Domain\Update\UpdateRepository` i dostosować: - URL serwera: `https://cmspro.project-dc.pl/updates/` (zamiast shoppro) - Plik wersji: `libraries/version.ini` (jak jest teraz) Kluczowe metody: ```php public function update(): array private function downloadAndApply(string $ver, string $dir, array $log): array private function downloadAndApplyWithManifest(string $ver, string $dir, array $manifest, array $log): array private function downloadAndApplyLegacy(string $ver, string $dir, array $log): array private function verifyChecksum(string $filePath, string $expectedChecksum, array $log): array private function createBackup(array $manifest, array $log): array private function extractZip(string $fileName, array $log): array public function runPendingMigrations(): void ``` **Kompatybilność wsteczna:** Metoda `downloadAndApply()` próbuje najpierw manifest. Jeśli brak → fallback do legacy (ZIP + _sql.txt + _files.txt). Dzięki temu stare paczki (v1.001-v1.689) nadal działają. ### 5.2 `admin\Controllers\UpdateController` ```php namespace admin\Controllers; use Domain\Update\UpdateRepository; class UpdateController { private $repository; public function __construct(UpdateRepository $repository) { $this->repository = $repository; } public function main_view(): string { /* widok panelu aktualizacji */ } public function update(): void { /* POST: wykonaj update */ } public function updateAll(): void { /* AJAX: pętla aktualizacji */ } public function checkUpdate(): void { /* AJAX: sprawdź dostępność */ } } ``` ### 5.3 Nowy `updates/versions.php` Dostosować do nowego formatu (wzór shopPRO): ```php db = $this->createMock(\medoo::class); $this->repo = new \Domain\Article\ArticleRepository($this->db); } public function testFindReturnsArticleArray(): void { $this->db->method('get') ->willReturn(['id' => 1, 'status' => 1]); $result = $this->repo->find(1); $this->assertIsArray($result); $this->assertEquals(1, $result['id']); } public function testFindReturnsNullWhenNotFound(): void { $this->db->method('get')->willReturn(null); $this->assertNull($this->repo->find(999)); } } ``` --- ## FAZA 7: Dokumentacja Utworzyć w `docs/`: - `PROJECT_STRUCTURE.md` — architektura, katalogi, namespace'y, routing - `DATABASE_STRUCTURE.md` — schemat bazy danych (tabele `pp_*`) - `TESTING.md` — jak uruchamiać testy - `CHANGELOG.md` — historia zmian - `UPDATE_INSTRUCTIONS.md` — jak budować paczki aktualizacji --- ## FAZA 8: Porządki (po pełnej migracji) ### 8.1 Usunięcie starych katalogów ``` rm -rf autoload/admin/controls/ rm -rf autoload/admin/factory/ rm -rf autoload/admin/view/ rm -rf autoload/front/controls/ rm -rf autoload/front/factory/ rm -rf autoload/front/view/ rm autoload/class.S.php rm autoload/class.Tpl.php rm autoload/class.Cache.php rm autoload/class.Html.php rm autoload/class.Image.php rm autoload/class.Article.php rm autoload/class.Page.php rm autoload/class.Scontainer.php rm autoload/admin/class.Site.php ``` ### 8.2 Usunięcie wrappera `\S` Przeszukać CAŁY kod i zamienić: - `\S::get()` → `\Shared\Helpers\Helpers::get()` - `\S::get_session()` → `\Shared\Helpers\Helpers::get_session()` - `\S::alert()` → `\Shared\Helpers\Helpers::alert()` - itd. ### 8.3 Usunięcie wrappera `\Tpl` - `\Tpl::view(...)` → `\Shared\Tpl\Tpl::view(...)` - `new \Tpl` → `new \Shared\Tpl\Tpl` --- ## PODSUMOWANIE — KOLEJNOŚĆ PRAC | Faza | Co robimy | Szacunkowa złożoność | |------|-----------|---------------------| | **0** | Autoloader, katalogi, PHPUnit, git tag, CLAUDE.md | Mała | | **1** | Shared utilities (S→Helpers, Tpl, Cache, Html, Image, Email) | Średnia | | **2** | Domain repositories (16 modułów: Settings, Languages, Users, Pages, Articles...) | **Duża** | | **3** | Admin Controllers + App router (16 kontrolerów) | **Duża** | | **4** | Frontend App + LayoutEngine + Views + Controllers | Średnia | | **5** | Nowy system aktualizacji (UpdateRepository, build-update.ps1, manifesty) | Średnia | | **6** | Testy PHPUnit | Średnia | | **7** | Dokumentacja | Mała | | **8** | Porządki — usunięcie starych klas | Mała | ### Zasada naczelna **Każda faza musi zostawić działający system.** Stary i nowy kod koegzystują dzięki: - Fallback w routerze (nowy kontroler → stary controls) - Wrappery w starych klasach (delegujące do nowych) - Stopniowe przenoszenie referencji ### Kolejność prac wewnątrz fazy 2+3 (moduł po module) Rekomendowana kolejność modułów (od najprostszych do najtrudniejszych): 1. **Settings** (1 metoda, zero zależności) 2. **Languages** (kilka metod, używany w bootstrap) 3. **Users** (login, 2FA, uprawnienia — kluczowe dla admin) 4. **Update** (nowy system aktualizacji) 5. **Pages** (routing frontu) 6. **Layouts** (layout engine) 7. **Scontainers** (kontenery statyczne) 8. **Banners** (banery) 9. **Authors** (autorzy) 10. **Menu** (menu — tylko front) 11. **Articles** (NAJTRUDNIEJSZY — class.Article.php z ArrayAccess, images, files, params, tags) 12. **Newsletter** (newsletter + preview renderer) 13. **SeoAdditional** (SEO per URL) 14. **Emails** (log kontaktowy) 15. **Backups** (backup bazy) 16. **ArticlesArchive** (archiwum — część ArticleRepository) --- ## CHECKLIST "KONIEC PRACY" (dla cmsPRO) Po zakończeniu każdej sesji pracy: 1. Uruchom testy: `./test.ps1` 2. Zaktualizuj docs jeśli potrzeba 3. SQL migracje → `migrations/{version}.sql` 4. `updates/versions.php` — ustawić `$current_ver` 5. Commit + push 6. Git tag: `git tag v1.XXX` + `git push origin v1.XXX` --- ## RÓŻNICE cmsPRO vs shopPRO | Aspekt | cmsPRO | shopPRO | |--------|--------|---------| | Cache | Plikowy (gzdeflate + MD5) | Redis (CacheHandler + RedisConnection) | | Moduły sklepowe | BRAK | Product, Category, Order, Basket, Client, Coupon, Promotion, Transport, PaymentMethod, Producer, Attribute, ProductSet, ShopStatus, Integrations, Dashboard | | Wersjonowanie | v1.689 (4 cyfry) | v0.308 (3 cyfry) | | URL aktualizacji | cmspro.project-dc.pl | shoppro.project-dc.pl | | API REST | Minimalny (api.php) | Rozbudowany (ApiRouter + 3 kontrolery) | | Pixieset | TAK | NIE | | AuditSEO | TAK | NIE | | FormEdit System | NIE | TAK (ViewModels + FormValidator + FormFieldRenderer) | | Remember-me | Legacy JSON cookie (hash hasła!) | HMAC-SHA256 signed payload | **UWAGA na bezpieczeństwo:** W `admin/index.php` cmsPRO cookie remember-me przechowuje hash hasła w JSON — to luka! W ramach migracji Users należy to zmienić na HMAC-SHA256 (jak w shopPRO `admin\App::finalize_admin_login()`).