971 lines
33 KiB
Markdown
971 lines
33 KiB
Markdown
# 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
|
|
<?php
|
|
namespace Domain\Article;
|
|
|
|
class ArticleRepository
|
|
{
|
|
private $db;
|
|
|
|
public function __construct($db)
|
|
{
|
|
$this->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
|
|
<?php
|
|
namespace admin;
|
|
|
|
class App
|
|
{
|
|
const APP_SECRET_KEY = 'c3cb2537d25c0efc9e573d059d79c3b8';
|
|
|
|
private static $newControllers = [];
|
|
|
|
public static function special_actions() { /* z admin\Site */ }
|
|
public static function finalize_admin_login(...) { /* z admin\Site */ }
|
|
|
|
public static function render(): string
|
|
{
|
|
global $user;
|
|
|
|
if (\Shared\Helpers\Helpers::get('module') === 'user'
|
|
&& \Shared\Helpers\Helpers::get('action') === 'twofa') {
|
|
$controller = self::createController('Users');
|
|
return $controller->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
|
|
<?php
|
|
namespace admin\Controllers;
|
|
|
|
use Domain\Article\ArticleRepository;
|
|
use Domain\Languages\LanguagesRepository;
|
|
|
|
class ArticlesController
|
|
{
|
|
private $articleRepo;
|
|
private $langRepo;
|
|
|
|
public function __construct(
|
|
ArticleRepository $articleRepo,
|
|
LanguagesRepository $langRepo
|
|
) {
|
|
$this->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
|
|
<?
|
|
$current_ver = 1690; // Pierwsza wersja z nowym systemem
|
|
|
|
for ($i = 1; $i <= $current_ver; $i++)
|
|
{
|
|
$dir = substr(number_format($i / 1000, 3), 0, strlen(number_format($i / 1000, 3)) - 2) . '0';
|
|
$version_new = number_format($i / 1000, 3);
|
|
|
|
if (file_exists('../updates/' . $dir . '/ver_' . $version_new . '.zip'))
|
|
$versions[] = $version_new;
|
|
}
|
|
|
|
// ... licencje (bez zmian) ...
|
|
```
|
|
|
|
### 5.4 Auto-generowany `updates/changelog.php`
|
|
|
|
Skopiować z shopPRO — skanuje `_manifest.json` + `changelog-legacy.json`.
|
|
|
|
### 5.5 `changelog-legacy.json`
|
|
|
|
Przenieść dotychczasowe wpisy z `updates/changelog.php` do JSON:
|
|
```json
|
|
[
|
|
{"version": "1.689", "date": "01.01.2026", "text": "- opis zmian v1.689"},
|
|
{"version": "1.688", "date": "...", "text": "- ..."},
|
|
...
|
|
]
|
|
```
|
|
|
|
### 5.6 `build-update.ps1`
|
|
|
|
Skopiować z shopPRO i dostosować:
|
|
- Zmienić header: `=== cmsPRO Build Update ===`
|
|
- Wersja: `v1.690` (format `X.XXX`, nie `0.XXX`)
|
|
- Katalog: `updates/1.60/` (dla 1.690), `updates/1.70/` (dla 1.700) itd.
|
|
- URL: `https://cmspro.project-dc.pl/updates/`
|
|
|
|
### 5.7 `.updateignore`
|
|
|
|
Skopiować z shopPRO, dostosować ścieżki:
|
|
```
|
|
*.md
|
|
docs/
|
|
CLAUDE.md
|
|
AGENTS.md
|
|
.claude/
|
|
.gitignore
|
|
.git/
|
|
tests/
|
|
phpunit.xml
|
|
phpunit.phar
|
|
composer.json
|
|
composer.lock
|
|
vendor/
|
|
test.ps1
|
|
memory/
|
|
updates/
|
|
.updateignore
|
|
build-update.ps1
|
|
migrations/
|
|
config.php
|
|
.htaccess
|
|
admin/.htaccess
|
|
libraries/version.ini
|
|
layout/style-css/style.css
|
|
layout/style-css/style.css.map
|
|
layout/style-scss/style.scss
|
|
layout/style-scss/_mixins.scss
|
|
layout/style-scss/_mixins.css
|
|
temp/
|
|
backups/
|
|
cache/
|
|
cron/temp/
|
|
.vscode/
|
|
.serena/
|
|
.phpunit.result.cache
|
|
```
|
|
|
|
### 5.8 Katalog `migrations/`
|
|
|
|
Migracje SQL w formacie `{version}.sql`:
|
|
```
|
|
migrations/
|
|
├── 1.690.sql
|
|
├── 1.691.sql
|
|
└── ...
|
|
```
|
|
|
|
`UpdateRepository::runPendingMigrations()` skanuje katalog i wykonuje migracje nowsze niż `version.ini`.
|
|
|
|
---
|
|
|
|
## FAZA 6: Testy PHPUnit
|
|
|
|
### 6.1 Struktura testów
|
|
|
|
```
|
|
tests/
|
|
├── bootstrap.php
|
|
├── stubs/
|
|
│ ├── CacheHandler.php ← stub dla \Shared\Cache\CacheHandler
|
|
│ └── Helpers.php ← stub dla \Shared\Helpers\Helpers
|
|
└── Unit/
|
|
└── Domain/
|
|
├── Article/
|
|
│ └── ArticleRepositoryTest.php
|
|
├── Settings/
|
|
│ └── SettingsRepositoryTest.php
|
|
├── User/
|
|
│ └── UserRepositoryTest.php
|
|
├── Languages/
|
|
│ └── LanguagesRepositoryTest.php
|
|
└── ...
|
|
```
|
|
|
|
### 6.2 Wzorzec testu
|
|
|
|
```php
|
|
class ArticleRepositoryTest extends TestCase
|
|
{
|
|
private $db;
|
|
private $repo;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->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()`).
|