33 KiB
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:
$f = 'autoload/' . implode('/', $q) . '/class.' . $c . '.php';
Docelowy — musi obsługiwać OBE konwencje (fallback PSR-4):
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.phpapi.phpcron.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.xmlphpunit.phartest.ps1tests/bootstrap.phpcomposer.json(sekcjarequire-dev)
0.4 Git tagging — ustalenie punktu startowego
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:
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 doDomain\Article\ArticleRepositoryclass.Scontainer.php→ logika doDomain\Scontainers\ScontainersRepositoryclass.Page.php→ logika doDomain\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
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)
- Utworzyć
autoload/Domain/Settings/SettingsRepository.php - Przenieść metody z
admin\factory\Settingsifront\factory\Settings:settings_details()→allSettings()- Zamienić
global $mdb→$this->db
- Napisać testy w
tests/Unit/Domain/Settings/SettingsRepositoryTest.php - Zaktualizować wywołujących (controls, view, index.php):
\front\factory\Settings::settings_details()→$settingsRepo->allSettings()
- Stary factory — zostawić tymczasowo jako wrapper:
namespace front\factory; class Settings { public static function settings_details() { global $mdb; return (new \Domain\Settings\SettingsRepository($mdb))->allSettings(); } } - 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
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
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_languageroute()— routing do artykułów + kontrolerów DI + fallback do starych controlsgetControllerFactories()— fabryki kontrolerówpageTitle(),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:
// Stare:
\front\controls\Site::check_url_params();
$out = \front\view\Site::show();
// Nowe:
\front\App::checkUrlParams();
$out = \front\LayoutEngine::show();
admin/index.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:
- v1.690 — budujemy ręcznie starą metodą (lub
build-update.ps1z flagą--legacy-only). Ta paczka zawiera nowyUpdateRepository, nowyadmin\App, nowy autoloader — całą infrastrukturę. - 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:
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
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):
<?
$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:
[
{"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(formatX.XXX, nie0.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
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, routingDATABASE_STRUCTURE.md— schemat bazy danych (tabelepp_*)TESTING.md— jak uruchamiać testyCHANGELOG.md— historia zmianUPDATE_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):
- Settings (1 metoda, zero zależności)
- Languages (kilka metod, używany w bootstrap)
- Users (login, 2FA, uprawnienia — kluczowe dla admin)
- Update (nowy system aktualizacji)
- Pages (routing frontu)
- Layouts (layout engine)
- Scontainers (kontenery statyczne)
- Banners (banery)
- Authors (autorzy)
- Menu (menu — tylko front)
- Articles (NAJTRUDNIEJSZY — class.Article.php z ArrayAccess, images, files, params, tags)
- Newsletter (newsletter + preview renderer)
- SeoAdditional (SEO per URL)
- Emails (log kontaktowy)
- Backups (backup bazy)
- ArticlesArchive (archiwum — część ArticleRepository)
CHECKLIST "KONIEC PRACY" (dla cmsPRO)
Po zakończeniu każdej sesji pracy:
- Uruchom testy:
./test.ps1 - Zaktualizuj docs jeśli potrzeba
- SQL migracje →
migrations/{version}.sql updates/versions.php— ustawić$current_ver- Commit + push
- 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()).