first commit
This commit is contained in:
970
PLAN_REFAKTORINGU.md
Normal file
970
PLAN_REFAKTORINGU.md
Normal file
@@ -0,0 +1,970 @@
|
||||
# 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()`).
|
||||
Reference in New Issue
Block a user