# Plan refaktoryzacji frontendu shopPRO ## Kontekst Panel administratora (33 moduły) został w pełni zmigrowany na architekturę Domain + DI + Controllers (ver. 0.237–0.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 | CZĘŚCIOWO zmigrowana | ŚREDNI | | Settings | Fasada (BUG: get_single_settings_value ignoruje $param) | NISKI | | Languages | Fasada | NISKI | | Layouts | Fasada | NISKI | | Banners | Fasada | NISKI | | 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, Banners, Languages, Menu, Newsletter, Scontainers | Czyste VIEW | | 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 | OK — Redis wrapper | | RedisConnection | ~40 | OK — singleton | | Email | ~100 | OK — PHPMailer wrapper (drobne poprawki) | | Log | ~20 | OK — audit logging | | DbModel | ~60 | OK — base ORM | | Cache | ~50 | LEGACY — file-based cache, rozważyć usunięcie | | 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 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: 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: Banners, Menu, Pages, Articles, Layouts Frontend Services **Cel:** Migracja pozostałych fabryk "liściowych". **NOWE:** - `Domain/Banner/BannerFrontendService.php` — `mainBanner()`, `banners()` (filtrowanie po datach) - `Domain/Menu/MenuFrontendService.php` — `menuDetails()`, `menuPages()` (rekurencja) - `Domain/Pages/PagesFrontendService.php` — `pageDetails()`, `mainPageId()`, `langUrl()`, `pageSort()` - `Domain/Article/ArticleFrontendService.php` — `articleDetails()`, `news()`, `pageArticles()`, `pageArticlesCount()`, `generateTableOfContents()`, `generateHeadersIds()` - `Domain/Layouts/LayoutsFrontendService.php` — `activeLayout()`, `articleLayout()`, `productLayout()`, `categoryLayout()`, `defaultLayout()`, `categoryDefaultLayout()` - Testy: 5 plików testowych **ZMIANA:** - `front/factory/Banners`, `Menu`, `Pages`, `Articles`, `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) jeśli wszystkie użycia przeniesione na `CacheHandler` - 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 --- ## 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. ### 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)