Files
shopPRO/docs/FRONTEND_REFACTORING_PLAN.md
Jacek Pyziak 8e97413361 ver. 0.282: Banners frontend migration, Cache cleanup, Shared\Cache namespace
- Banners frontend: front\Views\Banners (new), BannerRepository +2 frontend methods,
  front\view\Site przepięty, usunięte front\factory\Banners i front\view\Banners
- Cache cleanup: eliminacja legacy class.Cache.php (file-based cache),
  13 metod front\factory przepiętych z \Cache::fetch/store na CacheHandler
- Shared\Cache namespace: CacheHandler i RedisConnection przeniesione do Shared\Cache\,
  60 odwołań CacheHandler i 12 odwołań RedisConnection przepiętych,
  usunięte backward-compat wrappery class.CacheHandler.php i class.RedisConnection.php
- Naprawione rozbieżności kluczy cache (random_products, category_name)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 21:25:50 +01:00

566 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan refaktoryzacji frontendu shopPRO
## Kontekst
Panel administratora (33 moduły) został w pełni zmigrowany na architekturę Domain + DI + Controllers (ver. 0.2370.277). Teraz kolej na frontend: klasy `front/`, `shop/`, `cms/`, klasy utility z `autoload/` oraz szablony `templates/`.
**Cel:** przenieść logikę biznesową z warstwy frontendowej do warstwy domenowej (`Domain/`), zachowując kompatybilność wsteczną przez fasady. Zastosować te same wzorce co w adminie (DI, repozytoria/serwisy, testy PHPUnit).
---
## Inwentaryzacja — co istnieje
### front/controls/ (8 klas — handlery requestów)
| Klasa | Status | Logika biznesowa |
|-------|--------|-----------------|
| Site | Router główny | route(), check_url_params(), title() |
| ShopBasket | MIXED | Operacje koszyka, add/remove/quantity, checkout |
| ShopClient | Fasada | Deleguje do factory |
| ShopOrder | KRYTYCZNY | Webhooki płatności (tPay, Przelewy24, Hotpay) — bezpośrednie operacje DB |
| ShopProduct | Fasada | lazy_loading, warehouse_message, draw_product_attributes |
| ShopProducer | Fasada | list(), products() |
| ShopCoupon | Fasada | use_coupon(), delete_coupon() |
| Newsletter | Fasada | signin(), confirm(), unsubscribe() |
### front/factory/ (20 klas — pobieranie danych + logika)
| Klasa | Status | Priorytet migracji |
|-------|--------|--------------------|
| ShopProduct | ORYGINALNA LOGIKA (~370 linii) | KRYTYCZNY — product_details(), promoted/top/new products |
| ShopOrder | ORYGINALNA LOGIKA (~180 linii) | KRYTYCZNY — basket_save() tworzy zamówienie |
| ShopClient | ORYGINALNA LOGIKA | KRYTYCZNY — login (BUG: hardcoded bypass 'Legia1916'), signup, recover |
| ShopCategory | ORYGINALNA LOGIKA | WYSOKI — złożone SQL z language fallback |
| Articles | ORYGINALNA LOGIKA | WYSOKI — złożone SQL z language fallback |
| ShopPromotion | ORYGINALNA LOGIKA | WYSOKI — silnik promocji (5 typów) |
| ShopBasket | Fasada | ŚREDNI — summary_price, count |
| ShopTransport | CZĘŚCIOWO zmigrowana | ŚREDNI — transport_methods z filtrowaniem |
| ShopPaymentMethod | ZMIGROWANA (Domain) | — |
| ShopStatuses | ZMIGROWANA (Domain) | — |
| Scontainers | ZMIGROWANA (Domain) | — |
| Newsletter | ZMIGROWANA (Domain) — usunięta | — |
| Settings | Fasada (BUG: get_single_settings_value ignoruje $param) | NISKI |
| Languages | USUNIĘTA — przepięta na Domain | — |
| Layouts | Fasada | NISKI |
| Banners | USUNIETA — przepieta na Domain | — |
| Menu | Fasada | NISKI |
| Pages | Fasada | NISKI |
| ShopAttribute | Fasada | NISKI |
| ShopCoupon | Model danych | NISKI |
### front/view/ (12 klas — renderowanie)
| Klasa | Status |
|-------|--------|
| Site | KRYTYCZNY — show() ~600 linii, pattern substitution engine |
| ShopCategory | VIEW z logiką routingu (infinite scroll vs pagination) |
| Articles, Menu, Scontainers | Czyste VIEW |
| Banners | PRZENIESIONA do `front\Views\Banners` |
| Languages, Newsletter | PRZENIESIONE do `front\Views\` (nowy namespace) |
| ShopClient, ShopOrder, ShopPaymentMethod | Czyste VIEW |
| ShopTransport | PUSTA klasa (placeholder) |
### shop/ (14 klas — encje biznesowe)
| Klasa | Linii | Status | Priorytet |
|-------|-------|--------|-----------|
| Product | ~950 | KRYTYCZNY — pricing, atrybuty, search, cache, permutacje | KRYTYCZNY |
| Order | ~500+ | KRYTYCZNY — statusy, Apilo sync, emaile, webhooki | KRYTYCZNY |
| Promotion | ~200 | WYSOKI — matching aktywnych promocji + delegacja do factory | WYSOKI |
| Basket | ~80 | ŚREDNI — walidacja stanów magazynowych | ŚREDNI |
| Category | ~60 | NISKI — proste zapytania, pusty konstruktor | NISKI |
| Search | ~80 | NISKI — wrapper na szablony | NISKI |
| Coupon | ~60 | NISKI — niekompletne metody | NISKI |
| Transport | ~30 | NISKI — transport_list() | NISKI |
| PaymentMethod | — | ZMIGROWANA (fasada do Domain) | — |
| Producer | — | ZMIGROWANA (fasada do Domain) | — |
| ProductSet | — | ZMIGROWANA (fasada do Domain) | — |
| ProductAttribute | ~100 | OK — dobry caching | — |
| ProductCustomField | ~50 | OK — Redis caching | — |
| Shop | ~30 | OK — utility do cen | — |
### autoload/ root (klasy utility)
| Klasa | Linii | Status |
|-------|-------|--------|
| S | ~1130 | htacces() ~500 linii — generowanie .htaccess; reszta to utility |
| Tpl | ~90 | OK — silnik szablonów, bez zmian |
| CacheHandler | ~50 | ZMIGROWANY do `Shared\Cache\CacheHandler` — wrappery usuniete |
| RedisConnection | ~40 | ZMIGROWANY do `Shared\Cache\RedisConnection` — wrappery usuniete |
| Email | ~100 | OK — PHPMailer wrapper (drobne poprawki) |
| Log | ~20 | OK — audit logging |
| DbModel | ~60 | OK — base ORM |
| Cache | ~50 | USUNIETA — zastapiona CacheHandler (Redis) w ver. 0.282 |
| Html | ~80 | OK — form helpers |
| Image | ~100 | OK — GD wrapper |
| Mobile_Detect | — | Third-party, bez zmian |
### cms/ (1 klasa)
| Klasa | Status |
|-------|--------|
| Layout | BUG w __get() — referuje $this->data które nie istnieje |
### templates/ (75 plików w 15 modułach)
articles(8), banner(2), controls(1), menu(4), newsletter(2), scontainers(1), shop-basket(9), shop-category(6), shop-client(8), shop-coupon(1), shop-order(3), shop-producer(3), shop-product(12), shop-search(3), site(11)
### Entry points
- `index.php` — główny frontend (autoload, sesja, DB, routing, DOM post-processing)
- `ajax.php` — AJAX handler (koszyk, transport, kontakt)
- `api.php` — REST API (Ekomi CSV export)
- `download.php` — pobieranie plików
- `cron-turstmate.php` — TrustMate integracja
---
## Znane bugi do naprawy podczas refaktoringu
1. **KRYTYCZNY** `front\factory\ShopClient::login()` — hardcoded password bypass `'Legia1916'`
2. `front\factory\Settings::get_single_settings_value()` — ignoruje `$param`, zawsze zwraca `firm_name`
3. ~~`front\factory\Newsletter::newsletter_unsubscribe()` — błędna składnia SQL w delete~~ **NAPRAWIONE**`NewsletterRepository::unsubscribe()` z poprawną składnią medoo `delete()`
4. `cms\Layout::__get()` — referuje nieistniejące `$this->data`
5. `shop\Search` — typo w use: `shop\Produt` (brak 'c')
---
## Kolejność migracji (graf zależności)
```
Settings, Languages (liście — brak zależności)
Banners, Menu, Pages, Articles, Layouts (zależą od Languages)
Category (zależy od Languages)
Promotion (zależy od Category — rozbicie circular dep)
Product Frontend (zależy od Category, Promotion, Languages)
Client/Auth (standalone + security fix)
Transport, Payment, Coupon (frontend serwisy)
Basket (zależy od Product, Promotion, Transport, Coupon)
Order Creation (zależy od Basket, Product, Transport, Payment)
Payment Webhooks (zależy od Order)
shop\Order instance + Apilo (zależy od Order Operations)
shop\Product instance + cache (zależy od ProductFrontendService)
Frontend App + Controllers (nowa warstwa DI)
Site Layout Engine (zależy od wszystkiego)
Entry Point Unification (index.php, ajax.php)
Legacy Cleanup
```
---
## Etapy migracji
### Etap: Settings + Languages — ZREALIZOWANY
**Cel:** Dodac metody frontendowe (z cache Redis) do istniejacych repozytoriow Domain.
**DODANE METODY (do istniejacych klas):**
- `Domain/Settings/SettingsRepository``allSettings($skipCache)`, `getSingleValue($param)` (FIX: uzywa poprawnie $param)
- `Domain/Languages/LanguagesRepository``defaultLanguage()`, `activeLanguages()`, `translations($lang)`
- Testy: dopisane do `SettingsRepositoryTest` (6 testow), `LanguagesRepositoryTest` (7 testow)
**ZMIANA:**
- `front/factory/Settings` → fasada delegujaca do `SettingsRepository`
- `front/factory/Languages` → fasada delegujaca do `LanguagesRepository`
- `tests/bootstrap.php` — uzupelniony stub CacheHandler o `get()`/`set()`/`exists()`
**BUG FIX:** `get_single_settings_value()` — zmiana `['param' => 'firm_name']` na `['param' => $param]`
---
### Etap: Newsletter Frontend — ZREALIZOWANY
**Cel:** Przeniesienie logiki frontendowej z `front\factory\Newsletter` do `Domain\Newsletter\NewsletterRepository`. Migracja view do nowego namespace `front\Views`.
**DODANE METODY (do istniejącej klasy `NewsletterRepository`):**
- `unsubscribe(string $hash): bool` — FIX: poprawna składnia medoo `delete()` (2 args zamiast 3)
- `confirmSubscription(string $hash): bool`
- `getHashByEmail(string $email): ?string`
- `removeByEmail(string $email): bool`
- `signup(string $email, string $serverName, bool $ssl, array $settings): bool`
- `sendQueued(int $limit, string $serverName, bool $ssl, string $unsubscribeLabel): bool`
- Konstruktor rozszerzony o opcjonalne: `ArticleRepository`, `NewsletterPreviewRenderer` (lazy-init)
- Testy: 10 nowych testów w `NewsletterRepositoryTest`
**ZMIANA:**
- `front/factory/Newsletter` → USUNIĘTA (logika przeniesiona do `NewsletterRepository`)
- `front/view/Newsletter` → USUNIĘTA, zastąpiona przez `front/Views/Newsletter` (nowy namespace, bez `class.` prefix)
- `front/controls/Newsletter` → thin wrapper na `NewsletterRepository`
- `front/view/Site::show()``\front\Views\Newsletter::render()`
- `index.php``$newsletterRepo->sendQueued()`
- `front/factory/ShopClient` (4 miejsca) → `NewsletterRepository::templateByName()`
- `tests/bootstrap.php` — dodane stuby: `S::email_check()`, `S::get_session()`, `S::set_session()`
**BUG FIX:** `newsletter_unsubscribe()` — medoo `delete()` wywoływane z 3 argumentami zamiast 2
---
### Etap: Category Frontend Service
**Cel:** Migracja `front\factory\ShopCategory` do Domain.
**NOWE:**
- `Domain/Category/CategoryFrontendService.php``getCategorySort()`, `categoryName()`, `categoryUrl()`, `blogCategoryProducts()`, `categoryProducts()`, `productsId()` (złożone SQL z sortowaniem/language fallback/paginacją), `categoryDetails()`, `categoriesDetails()` (rekurencyjne), `categoryProductsCount()`
- Testy: `CategoryFrontendServiceTest`
**ZMIANA:**
- `front/factory/ShopCategory` → fasada
- `front/factory/ShopProduct::product_categories()` → deleguje do `CategoryFrontendService`
---
### Etap: Articles Frontend — ZREALIZOWANY
**Cel:** Migracja `front\factory\Articles`, `front\view\Articles` i statycznych metod `class.Article` do Domain + Views.
**DODANE METODY (do istniejącej klasy `ArticleRepository`):**
- `articleDetailsFrontend(int $articleId, string $langId): ?array` — z copy_from fallback + Redis cache
- `articlesIds(int $pageId, string $langId, int $limit, int $sortType, int $from): ?array` — złożone SQL z language fallback + sortowanie + LIMIT + Redis cache
- `pageArticlesCount(int $pageId, string $langId): int` — COUNT z Redis cache
- `pageArticles(array $page, string $langId, int $bs): array` — paginacja
- `news(int $pageId, int $limit, string $langId): ?array` — inline sort_type query (eliminacja zależności od `front\factory\Pages`)
- `articleNoindex(int $articleId, string $langId): bool` — jawny $langId zamiast `global $lang`
- `topArticles(int $pageId, int $limit, string $langId): ?array` — ORDER BY views DESC + Redis cache
- `newsListArticles(int $pageId, int $limit, string $langId): ?array` — ORDER BY date_add DESC + CacheHandler (Redis) zamiast legacy `\Cache`
**NOWE:**
- `front\Views\Articles` — czysty VIEW + utility:
- Renderowanie: `fullArticle()`, `miniatureArticlesList()`, `entryArticlesList()`, `fullArticlesList()`, `news()`, `newsList()`
- Utility: `generateTableOfContents()`, `processHeaders()`, `generateHeadersIds()`, `getImage()`
**ZMIANA:**
- `front\factory\Articles` → fasada (10 metod delegujących do repo + Views)
- `front\view\Articles` → fasada (5 metod delegujących do repo + Views)
- `class.Article` → USUNIĘTA (encja + metody statyczne przeniesione do `ArticleRepository` + `front\Views\Articles`)
- `front\view\Site::show()` → 5 sekcji przepiętych na repo + Views
- `front\controls\Site::route()` → single article + page_type switch przepięte na repo + Views
- 5 szablonów `templates/articles/*``\front\Views\Articles::`
- `tests/bootstrap.php` — dodany stub `S::is_array_fix()`
- Testy: 13 nowych w `ArticleRepositoryTest` (450 OK, 1431 asercji)
---
### Etap: Banners Frontend — ZREALIZOWANY
**Cel:** Migracja `front\factory\Banners` i `front\view\Banners` do Domain + Views.
**DODANE METODY (do istniejącej klasy `BannerRepository`):**
- `banners(string $langId): ?array` — aktywne banery (home_page=0), filtrowanie dat, Redis cache, plaski format languages
- `mainBanner(string $langId): ?array` — baner glowny (home_page=1), filtrowanie dat, Redis cache, plaski format languages
**NOWE:**
- `front\Views\Banners` — czysty VIEW (`banners()`, `mainBanner()`)
**ZMIANA:**
- `front\factory\Banners` → USUNIETA (logika przeniesiona do `BannerRepository`)
- `front\view\Banners` → USUNIETA (zastapiona przez `front\Views\Banners`)
- `front\view\Site::show()` — przepiecie na `$bannerRepo->banners()` / `$bannerRepo->mainBanner()` + `\front\Views\Banners::`
- Testy: 4 nowe w `BannerRepositoryTest` (454 OK, 1449 asercji)
---
### Etap: Menu, Pages, Layouts Frontend Services
**Cel:** Migracja pozostałych fabryk "liściowych".
**NOWE:**
- `Domain/Menu/MenuFrontendService.php``menuDetails()`, `menuPages()` (rekurencja)
- `Domain/Pages/PagesFrontendService.php``pageDetails()`, `mainPageId()`, `langUrl()`, `pageSort()`
- `Domain/Layouts/LayoutsFrontendService.php``activeLayout()`, `articleLayout()`, `productLayout()`, `categoryLayout()`, `defaultLayout()`, `categoryDefaultLayout()`
- Testy: 3 pliki testowe
**ZMIANA:**
- `front/factory/Menu`, `Pages`, `Layouts` → fasady
**BUG FIX:** `cms\Layout::__get()` — poprawka referencji do `$this->data`
---
### Etap: Promotion Engine (rozbicie circular dependency)
**Cel:** Przeniesienie silnika promocji do Domain. Rozbicie cyklicznej zależności `shop\Promotion ↔ front\factory\ShopPromotion`.
**Obecna zależność cykliczna:**
```
shop\Promotion::find_promotion() → front\factory\ShopPromotion::promotion_type_XX()
front\factory\ShopPromotion::promotion_type_XX() → shop\Product::is_product_on_promotion()
```
**Rozwiązanie:** Wszystko w jednym serwisie `PromotionFrontendService`.
**NOWE:**
- `Domain/Promotion/PromotionFrontendService.php`:
- `getActivePromotions()` (z `shop\Promotion`)
- `applyPromotions(array $basket)` (z `shop\Promotion::find_promotion()`)
- `private applyType01..05()` (z `front\factory\ShopPromotion`)
- `private isProductOnPromotion()` (z `shop\Product` — proste zapytanie)
- Testy: `PromotionFrontendServiceTest` (7 typów promocji + brak aktywnych)
**ZMIANA:**
- `shop/Promotion` → fasada do `PromotionFrontendService::applyPromotions()`
- `front/factory/ShopPromotion` → fasada
---
### Etap: Product Frontend Service
**Cel:** Migracja `front\factory\ShopProduct` i statycznych metod `shop\Product` do Domain.
**NOWE:**
- `Domain/Product/ProductFrontendService.php`:
- Z `front\factory\ShopProduct`: `productDetails()` (~330 linii z Redis), `promotedProducts()`, `topProducts()`, `newProducts()`, `productUrl()`, `getMinimalPrice()`, `isProductActive()`, `getProductSku()`, `getProductEan()`, `productImage()`, `productWp()`, `randomProducts()`, `warehouseMessageZero()`, `warehouseMessageNonzero()`
- Z `shop\Product` (statyczne): `isProductOnPromotion()`, `getProductImg()`, `getProductUrl()`, `addVisit()`, `productSetsForBasket()`, `searchProductsByName()`, `searchProductByNameAjax()`
- Testy: `ProductFrontendServiceTest`
**ZMIANA:**
- `front/factory/ShopProduct` → fasada
- `shop/Product` statyczne metody → fasady do `ProductFrontendService`
**UWAGA:** Konstruktor `shop\Product`, `getFromCache()`, `calculate_basket_product_price()` — zostają na razie (instancyjne, używane w szablonach). Migracja w etapie "Product Instance + Cache".
---
### Etap: Client Authentication (Security Fix)
**Cel:** Migracja `front\factory\ShopClient` + NAPRAWIENIE hardcoded password bypass.
**NOWE:**
- `Domain/Client/ClientFrontendService.php`:
- `login()`**BEZ** bypassa 'Legia1916', z opcjonalną migracją md5 → password_hash
- `signup()`, `registerConfirm()`, `sendPasswordRecovery()`, `resetPassword()`
- `clientDetails()`, `clientOrders()`
- CRUD adresów: `saveAddress()`, `deleteAddress()`, `getAddresses()`, `markAddressAsCurrent()`
- Testy: `ClientFrontendServiceTest`**KRYTYCZNY test: login z 'Legia1916' NIE przechodzi**
**ZMIANA:**
- `front/factory/ShopClient` → fasada
- `front/controls/ShopClient` → deleguje do serwisu
---
### Etap: Transport, Payment, Coupon Frontend Services
**Cel:** Frontend serwisy dla transportu, płatności i kuponów.
**NOWE:**
- `Domain/Transport/TransportFrontendService.php``transportMethods()` (filtrowanie WP, free delivery), `transportCost()`, `transportDetails()`
- `Domain/PaymentMethod/PaymentMethodFrontendService.php``activePaymentMethods()`, `paymentMethodsByTransport()`, `paymentMethodDetails()`
- `Domain/Coupon/CouponFrontendService.php``validateCoupon()`, `applyCoupon()`, `markCouponUsed()`
- Testy: 3 pliki testowe
**ZMIANA:**
- `front/factory/ShopTransport` → fasada
- `front/factory/ShopCoupon` → fasada
- `shop/Coupon` → fasada
- `shop/Transport` → fasada
---
### Etap: Basket Service
**Cel:** Migracja logiki koszyka do `Domain\Basket\BasketService`.
**NOWE:**
- `Domain/Basket/BasketService.php`:
- `addProduct()`, `removeProduct()`, `changeQuantity()`, `increaseQuantity()`, `decreaseQuantity()`
- `summaryPrice()`, `countProducts()`, `countProductsText()`
- `validateStock()` (z `shop\Basket::check_product_quantity_in_stock()`)
- `calculateProductPrice()` (z `shop\Product::calculate_basket_product_price()` ~112 linii)
- Testy: `BasketServiceTest` (pricing z promocjami/kuponami, walidacja stanów)
**ZMIANA:**
- `front/factory/ShopBasket` → fasada
- `front/controls/ShopBasket` — wewnętrznie używa `BasketService`
- `shop/Basket` → fasada
- `shop/Product::calculate_basket_product_price()` → fasada
---
### Etap: shop\Product Instance + Cache
**Cel:** Refaktoring konstruktora `shop\Product` i `getFromCache()`.
**NOWE:**
- `Domain/Product/ProductDataLoader.php`:
- `loadFull(int $productId, ?string $langId, ?string $permutationHash)` — ładowanie pełnych danych produktu
- `loadCached()` — Redis cache wrapper
- Testy: `ProductDataLoaderTest`
**ZMIANA:**
- `shop/Product` — konstruktor i `getFromCache()` delegują do `ProductDataLoader`
- ArrayAccess interface zachowany (szablony dalej używają `$product->property`)
---
### Etap: Order Creation Frontend Service
**Cel:** Migracja `front\factory\ShopOrder::basket_save()` (~180 linii).
**NOWE:**
- `Domain/Order/OrderFrontendService.php`:
- `createOrder()` — tworzenie zamówienia z koszyka (walidacja, kalkulacja cen, insert, redukcja stanów, obsługa kuponu, wysyłka emaili, auto-status dla pobrania)
- `generateOrderNumber()` — format YYYY/MM/NNN
- `orderDetails()`, `orderIdByHash()`, `orderHashById()`
- Testy: `OrderFrontendServiceTest`
**ZMIANA:**
- `front/factory/ShopOrder` → fasada
---
### Etap: Payment Webhook Service
**Cel:** Wyodrębnienie webhooków płatności z `front\controls\ShopOrder`.
**NOWE:**
- `Domain/Payment/PaymentWebhookService.php`:
- `processTpay(array $params)` — weryfikacja tPay
- `processPrzelewy24(array $params)` — weryfikacja przez API + walidacja kwoty
- `processHotpay(array $params)` — walidacja SHA256 hash
- `private markOrderPaid()` — wspólna logika (update status + email + Apilo sync)
- Testy: `PaymentWebhookServiceTest`
**ZMIANA:**
- `front/controls/ShopOrder` — webhooki stają się thin wrappers
**POPRAWA:** Zamiana `file_put_contents('tpay.txt')` na `\Log::save_log()`
---
### Etap: shop\Order Instance + Apilo Service
**Cel:** Refaktoring `shop\Order` instancyjnych metod + wyodrębnienie integracji Apilo.
**NOWE:**
- `Domain/Integrations/ApiloService.php`:
- `syncPayment()`, `syncStatus()`, `processQueue()`
- `private queueSync()`, `private loadQueue()`, `private saveQueue()`
- `Domain/Order/OrderOperationsService.php`:
- `setAsPaid()`, `setAsUnpaid()`, `updateStatus()`, `sendStatusChangeEmail()`, `resendConfirmationEmail()`
- Testy: `ApiloServiceTest`, `OrderOperationsServiceTest`
**ZMIANA:**
- `shop/Order` instancyjne metody → fasady do `OrderOperationsService`
- Konstruktor i ArrayAccess bez zmian
---
### Etap: Frontend App + Controllers (DI layer)
**Cel:** Stworzenie `front\App` (wzorowanego na `admin\App`) z mapą kontrolerów i DI.
**NOWE:**
- `autoload/front/App.php``render()`, `handleAjax()`, `getControllerFactories()` (DI wiring)
- `autoload/front/Controllers/ShopBasketController.php` — DI z `BasketService`, `ProductFrontendService`
- `autoload/front/Controllers/ShopOrderController.php` — DI z `OrderFrontendService`, `PaymentWebhookService`
- `autoload/front/Controllers/ShopClientController.php` — DI z `ClientFrontendService`
- Testy: 3 pliki testowe kontrolerów
**ZMIANA:**
- `front/controls/ShopBasket`, `ShopOrder`, `ShopClient` → delegują do nowych kontrolerów
---
### Etap: Site Layout Engine
**Cel:** Refaktoring `front\view\Site::show()` (~600 linii) na testowalny `LayoutEngine`.
**NOWE:**
- `autoload/front/View/LayoutEngine.php`:
- `resolveLayout(array $page, array $params)` — wybór layoutu (product > category > article > page > default)
- `processPatterns(string $html, array $context)` — zamiana tagów szablonowych
- Osobne prywatne metody: `replaceCategories()`, `replaceProducts()`, `replaceMenus()`, `replaceLanguageTags()`, `replaceBanners()`, `replaceArticles()`, `replaceContainers()`, `replaceMetaTags()`, etc.
- Testy: `LayoutEngineTest`
**ZMIANA:**
- `front/view/Site::show()` → thin wrapper na `LayoutEngine`
---
### Etap: Entry Point Unification
**Cel:** Ujednolicenie bootstrapu `index.php` i `ajax.php`.
**NOWE:**
- `autoload/front/Bootstrap.php``init()` (autoloader, config, sesja, DB, lang/settings)
- `autoload/front/PostProcessor.php` — ekstrakcja DOM manipulacji z `index.php` (GTM, Facebook Pixel, WebP, lazy loading)
**ZMIANA:**
- `index.php` → uproszczony: `Bootstrap::init()``App::render()``PostProcessor::process()`
- `ajax.php` → uproszczony: `Bootstrap::init()``App::handleAjax()`
---
### Etap: Legacy Cleanup
**Cel:** Finalne porządki.
**USUNIĘCIE:**
- Martwy kod `eval()` dla `[PHP]` bloków (jeśli nieużywany)
- ~~`class.Cache.php` (legacy file-based)~~ **ZREALIZOWANE** w ver. 0.282
- Pusta klasa `front\view\ShopTransport`
**ZMIANA:**
- Dodanie `@deprecated` do wszystkich metod-fasad w `front/factory/`, `front/controls/`, `shop/`
- PHPDoc do wszystkich nowych klas Domain z `@since`
- Aktualizacja `tests/bootstrap.php`
- BUG FIX: `shop\Search` — typo `shop\Produt``shop\Product`
- ~~BUG FIX: `front\factory\Newsletter::newsletter_unsubscribe()` — poprawka SQL~~ **ZREALIZOWANE** w etapie Newsletter Frontend
---
## Podsumowanie
| Etap | Zakres | Priorytet | Nowe klasy Domain | Testy |
|------|--------|-----------|-------------------|-------|
| Settings + Languages | Fundamenty | FUNDAMENT | 2 serwisy | 2 |
| Category Frontend | Kategorie | WYSOKI | 1 serwis | 1 |
| Banners/Menu/Pages/Articles/Layouts | Treści | ŚREDNI | 5 serwisów | 5 |
| Promotion Engine | Promocje | KRYTYCZNY | 1 serwis | 1 |
| Product Frontend | Produkty | KRYTYCZNY | 1 serwis | 1 |
| Client/Auth (security fix) | Klienci | KRYTYCZNY | 1 serwis | 1 |
| Transport/Payment/Coupon | Dostawa/Płatności | WYSOKI | 3 serwisy | 3 |
| Basket Service | Koszyk | WYSOKI | 1 serwis | 1 |
| Product Instance + Cache | Produkt cache | ŚREDNI | 1 loader | 1 |
| Order Creation | Zamówienia | WYSOKI | 1 serwis | 1 |
| Payment Webhooks | Webhooki | WYSOKI | 1 serwis | 1 |
| Order Instance + Apilo | Zamówienie + Apilo | ŚREDNI | 2 serwisy | 2 |
| Frontend App + Controllers | DI layer | WYSOKI | App + 3 kontrolery | 3 |
| Layout Engine | Silnik layoutu | ŚREDNI | 1 engine | 1 |
| Entry Point Unification | Entry points | ŚREDNI | Bootstrap + PostProcessor | 1 |
| Legacy Cleanup | Porządki | NISKI | — | — |
**Łącznie:** 16 etapów, ~24 nowe klasy Domain, ~25 plików testowych
### Wzorce do przestrzegania (z migracji admin)
- Konstruktor DI z `$db` (medoo)
- CacheHandler (Redis) dla cachingu — unifikacja (usunięcie starego `Cache`)
- Fasady w `shop\*` i `front\factory\*` dla kompatybilności wstecznej
- Testy PHPUnit z mockami medoo
- Namespace `\Domain\*``autoload/Domain/*/`
- Namespace `\front\Controllers\``autoload/front/Controllers/`
- **Klasy Domain sa wspolne dla admin i frontendu** — NIE tworzymy osobnych FrontendService/AdminService. Metody frontendowe (z cache Redis) dodajemy do istniejacych repozytoriow/serwisow Domain. Klasy sa ladowane lazy (instancja tworzona dopiero przy wywolaniu), wiec nie wplywaja na wydajnosc.
### Nazewnictwo plikow
- Nowe klasy: `NazwaKlasy.php` (bez przedrostka `class.`)
- Legacy: `class.NazwaKlasy.php` — zostawiamy do momentu migracji danej klasy
- Autoloader obsluguje oba formaty (probuje `class.X.php`, potem `X.php`)
- Nowe katalogi z duzej litery: `Views/`, `Controllers/` (legacy: `view/`, `controls/`, `factory/`)
### Statyczne vs instancyjne metody
- **Statyczne** — gdy klasa jest bezstanowa (brak konstruktora, brak properties, brak DI). Czyste funkcje: dane wchodzą, wynik wychodzi. Przykład: klasy VIEW (`front\Views\Languages::render($data)`, `front\view\Banners::banners($data)`)
- **Instancyjne** — gdy klasa ma zależności do wstrzyknięcia (repozytoria, serwisy) lub trzyma stan. Przykład: kontrolery z DI (`ShopProductController` z `ProductRepository`, `LanguagesRepository`)
### Weryfikacja po każdym etapie
1. `composer test` (pełny suite PHPUnit)
2. Manualne sprawdzenie frontendu: strona główna, kategoria, produkt, koszyk, zamówienie
3. Sprawdzenie AJAX: dodawanie do koszyka, zmiana transportu, kupon
4. Sprawdzenie webhooków płatności (tPay, Przelewy24, Hotpay)