# 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 | ZMIGROWANY do `front\App` — usunięty | route(), checkUrlParams(), title(), pageTitle(), getControllerFactories() | | ShopBasket | ZMIGROWANY do `front\Controllers\ShopBasketController` | Operacje koszyka, add/remove/quantity, checkout | | ShopClient | ZMIGROWANY do `front\Controllers\ShopClientController` | Logowanie, rejestracja, odzyskiwanie hasla, adresy, zamowienia | | ShopOrder | ZMIGROWANY do `front\Controllers\ShopOrderController` | Webhooki płatności + order details | | ShopProduct | ZMIGROWANY — usunięty | logika w ProductRepository + szablony | | ShopProducer | ZMIGROWANY do `front\Controllers\ShopProducerController` | list(), products() | | ShopCoupon | ZMIGROWANY do `front\Controllers\ShopCouponController` | use_coupon(), delete_coupon() | | Newsletter | ZMIGROWANY do `front\Controllers\NewsletterController` | signin(), confirm(), unsubscribe() | ### ~~front/factory/~~ (20 klas — USUNIĘTY w ver. 0.292, wszystkie zmigrowane do Domain) | Klasa | Status | Priorytet migracji | |-------|--------|--------------------| | ShopProduct | ZMIGROWANA do `ProductRepository` — usunięta | — | | ShopOrder | ZMIGROWANA do `OrderRepository` — usunięta | — | | ShopClient | ZMIGROWANA do `ClientRepository` + `ShopClientController` — usunięta | — | | ShopCategory | ZMIGROWANA do `CategoryRepository` — usunięta | — | | Articles | ZMIGROWANA do `ArticleRepository` — usunięta | — | | ShopPromotion | ZMIGROWANA do `PromotionRepository` — usunięta | — | | ShopBasket | ZMIGROWANA do `Domain\Basket\BasketCalculator` — usunięta | — | | ShopTransport | ZMIGROWANA do `TransportRepository` — usunięta | — | | ShopPaymentMethod | ZMIGROWANA do `PaymentMethodRepository` — usunięta | — | | ShopStatuses | ZMIGROWANA do `ShopStatusRepository` — usunięta | — | | Scontainers | ZMIGROWANA (Domain) — usunięta | — | | Newsletter | ZMIGROWANA (Domain) — usunięta | — | | Settings | ZMIGROWANA do `SettingsRepository` — usunięta | — | | Languages | USUNIĘTA — przepięta na Domain | — | | Layouts | USUNIETA — przepieta na Domain | — | | Banners | USUNIETA — przepieta na Domain | — | | Menu | USUNIETA — przepieta na Domain | — | | Pages | USUNIETA — przepieta na Domain | — | | ShopAttribute | ZMIGROWANA (Domain) — usunięta | — | | ShopCoupon | ZMIGROWANA do `CouponRepository` — usunięta | — | ### front/view/ (12 klas — renderowanie) | Klasa | Status | |-------|--------| | Site | ZMIGROWANY do `front\LayoutEngine` — usunięty | | ShopCategory | PRZENIESIONA do `front\Views\ShopCategory` | | Articles | Czyste VIEW | | Scontainers | PRZENIESIONA do `front\Views\Scontainers` | | Menu | PRZENIESIONA do `front\Views\Menu` | | Banners | PRZENIESIONA do `front\Views\Banners` | | Languages, Newsletter | PRZENIESIONE do `front\Views\` (nowy namespace) | | ShopClient | PRZENIESIONA do `front\Views\ShopClient` | | ShopOrder | ZMIGROWANA do `ShopOrderController` — usunięta | | ShopPaymentMethod | USUNIĘTA (pusta klasa) | | ShopTransport | USUNIĘTA (pusta klasa) | ### 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 | — | USUNIETA — callery na PaymentMethodRepository | — | | Producer | — | USUNIETA (callery na ProducerRepository) | — | | 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 | ~960 | PRZENIESIONA do Shared\Helpers\Helpers — 12 metod usunieto, bug fix | | Tpl | ~90 | PRZENIESIONA do `Shared\Tpl\Tpl` — DRY render(), bug fix branch 3, ~135 plikow przepietych | | 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 | — | USUNIETA — logika wbudowana w `shop\Promotion` | | Cache | ~50 | USUNIETA — zastapiona CacheHandler (Redis) w ver. 0.282 | | Html | ~80 | OK — form helpers | | Image | ~100 | OK — GD wrapper | | Mobile_Detect | — | USUNIETA — przestarzala detekcja UA, zastapiona responsive design | ### cms/ (1 klasa) | Klasa | Status | |-------|--------| | Layout | USUNIETA — zastapiona przez `$layoutsRepo->find()` | ### 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'`~~ **NAPRAWIONE** — `ClientRepository::authenticate()` bez bypass 2. ~~`front\factory\Settings::get_single_settings_value()` — ignoruje `$param`, zawsze zwraca `firm_name`~~ **NAPRAWIONE** — klasa usunięta, `SettingsRepository::getSingleValue()` z poprawnym `$param` 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`~~ **NAPRAWIONE** — klasa usunięta, zastąpiona przez `$layoutsRepo->find()` 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 — ZREALIZOWANY **Cel:** Migracja `front\factory\ShopCategory` i `front\view\ShopCategory` do Domain + Views. **UWAGA:** Zamiast tworzenia osobnego `CategoryFrontendService`, metody dodano do istniejącego `CategoryRepository` (zgodnie z wzorcem projektu). **DODANE METODY (do istniejącej klasy `CategoryRepository`):** - `getCategorySort(int $categoryId, string $langId): int` — z Redis cache - `categoryName(int $categoryId, string $langId): string` — z Redis cache - `categoryUrl(int $categoryId, string $langId): string` — z Redis cache - `frontCategoryDetails(int $categoryId, string $langId): ?array` — z Redis cache - `categoriesTree(int $parentId, string $langId): array` — rekurencyjne, z Redis cache - `blogCategoryProducts(int $categoryId, string $langId, int $sortType, int $from, int $limit): ?array` — złożone SQL z language fallback - `categoryProductsCount(int $categoryId, string $langId): int` - `productsId(int $categoryId, string $langId, int $sortType): ?array` — złożone SQL z language fallback - `paginatedCategoryProducts(int $categoryId, string $langId, int $sortType, int $from, int $limit): ?array` - Stałe: `SORT_ORDER_SQL`, `PRODUCTS_PER_PAGE`, `LANGUAGE_FALLBACK_NAME_SQL` - Testy: +17 w `CategoryRepositoryTest` **NOWE:** - `front\Views\ShopCategory` — czysty VIEW (`categoryDescription()`, `categoryView()`, `categories()`) - BUG FIX: `categoryView()` — `category_products_count()` wywoływane z tablicą zamiast ID **ZMIANA:** - `front/factory/ShopCategory` → USUNIETA (logika przeniesiona do `CategoryRepository`) - `front/view/ShopCategory` → USUNIETA (zastąpiona przez `front\Views\ShopCategory`) - `index.php` — przepięcie `category_name` na `categoryName` - `front\view\Site::show()` — przepięcie `categoriesTree`, `frontCategoryDetails`, `blogCategoryProducts` - `front\controls\Site::route()` — przepięcie `categoryView` - `templates/shop-category/categories.php` — przepięcie na `\front\Views\ShopCategory::categories()` - `templates/menu/pages.php` — przepięcie `category_url` na `categoryUrl` - `front\controls\ShopProduct` — przepięcie `products_id` + `get_category_sort` **Testy:** 501 OK, 1562 asercji --- ### 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 — ZREALIZOWANY **Cel:** Migracja pozostałych fabryk "liściowych". **UWAGA:** Zamiast tworzenia osobnych FrontendService, metody dodano do istniejących repozytoriów Domain (zgodnie z wzorcem projektu). **DODANE METODY (do istniejących klas):** - `Domain/Layouts/LayoutsRepository` — `categoryDefaultLayoutId()`, `getDefaultLayout()`, `getProductLayout()`, `getArticleLayout()`, `getCategoryLayout()`, `getActiveLayout()` (Redis cache, 3-level fallback) - `Domain/Pages/PagesRepository` — `frontPageDetails()`, `frontPageSort()`, `frontMainPageId()`, `frontLangUrl()`, `frontMenuDetails()`, `frontMenuPages()` (Redis cache, rekurencja) - Testy: +8 w `LayoutsRepositoryTest`, +8 w `PagesRepositoryTest` **NOWE:** - `front\Views\Menu` — czysty VIEW (`pages()`, `menu()`) **ZMIANA:** - `front/factory/Layouts` → USUNIETA (logika w `LayoutsRepository`) - `front/factory/Menu` → USUNIETA (logika w `PagesRepository`) - `front/factory/Pages` → USUNIETA (logika w `PagesRepository`) - `front/view/Menu` → USUNIETA (zastapiona przez `front\Views\Menu`) - `templates/menu/submenu.php` → USUNIETA (martwy kod) - `front\view\Site::show()` — przepiecie na `$layoutsRepo` + `$pagesRepo` - `front\controls\Site::check_url_params()` — przepiecie na `$pagesRepo->frontPageDetails()` - `index.php` — przepiecie na `$pagesRepo->frontPageDetails()` - `Shared\Helpers\Helpers::htacces()` — optymalizacja 3→1 wywolan - Szablony `templates/menu/*` — przepiecie na `\front\Views\Menu::` - `templates/site/languages.php` — przepiecie na `$pagesRepo->frontLangUrl()` **BUG FIX:** `frontPageDetails()` — null `$lang_id` przy wczesnym `check_url_params()` (usuniety string type hint + cast + `?? ''` na call site) **Testy:** 470 OK, 1484 asercji --- ### 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) — ZREALIZOWANY **Cel:** Migracja `front\factory\ShopClient`, `front\view\ShopClient`, `front\controls\ShopClient` + NAPRAWIENIE hardcoded password bypass. **UWAGA:** Zamiast tworzenia osobnego `ClientFrontendService`, metody dodano do istniejącego `ClientRepository` (zgodnie z wzorcem projektu). Kontroler utworzony jako `front\Controllers\ShopClientController` z DI. **DODANE METODY (do istniejącej klasy `ClientRepository`):** - Simple CRUD: `clientDetails()`, `clientEmail()`, `clientAddresses()`, `addressDetails()`, `addressDelete()`, `addressSave(int $clientId, ?int $addressId, array $data)`, `markAddressAsCurrent()` - `clientOrders()` — zachowuje zależność od `\front\factory\ShopOrder::order_details()` - `authenticate(string $email, string $password)` — **BEZ** bypassa 'Legia1916', zwraca `['status' => 'ok'|'error'|'inactive', ...]` - `createClient()` — zwraca `['id' => ..., 'hash' => ...]` lub null - `confirmRegistration()` — zwraca email lub null - `generateNewPassword()` — zwraca `['email' => ..., 'password' => ...]` lub null - `initiatePasswordRecovery()` — zwraca hash lub null - Testy: +36 w `ClientRepositoryTest` (guard clauses, authenticate 5 scenariuszy, createClient, confirmRegistration, generateNewPassword, initiatePasswordRecovery, address CRUD, markAddressAsCurrent) **NOWE:** - `front\Views\ShopClient` — czysty VIEW (8 metod camelCase: addressEdit, clientAddresses, clientMenu, clientOrders, recoverPassword, miniLogin, loginForm, registerForm) - `front\Controllers\ShopClientController` — instancyjny kontroler z DI (`ClientRepository` przez konstruktor) - 15 metod publicznych (camelCase) - Prywatny helper `buildEmailBody(string $templateName, array $replacements = [])` — deduplikuje 4× powtórzony wzorzec budowania emaili z newslettera - `authenticate()` zwraca dane → kontroler obsługuje sesję/flash/redirect (separation of concerns) - `addressSave()` przyjmuje `array $data` zamiast 6 parametrów **ZMIANA:** - `front/factory/ShopClient` → USUNIETA (logika przeniesiona do `ClientRepository`) - `front/view/ShopClient` → USUNIETA (zastąpiona przez `front\Views\ShopClient`) - `front/controls/ShopClient` → USUNIETA (zastąpiona przez `front\Controllers\ShopClientController`) - `front\controls\Site::getControllerFactories()` — zarejestrowany `'ShopClient'` - `front\factory\ShopOrder` — przepięcie `client_email` na `ClientRepository::clientEmail()` - `front\Controllers\ShopBasketController` — przepięcie `client_addresses` na `ClientRepository::clientAddresses()` - `front\view\Site` — przepięcie `mini_login` na `\front\Views\ShopClient::miniLogin()` - 3 szablony `shop-client/*` — przepięcie `client_menu` na `\front\Views\ShopClient::clientMenu()` - `templates/shop-basket/summary-view.php` — przepięcie `login_form` na `\front\Views\ShopClient::loginForm()` **SECURITY FIX:** Hardcoded password bypass `'Legia1916'` usunięty — `authenticate()` sprawdza wyłącznie md5(register_date + password) **Testy:** 537 OK, 1648 asercji --- ### 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 — ZREALIZOWANY (ver. 0.290) **Cel:** Migracja `front\factory\ShopOrder::basket_save()` (~180 linii). **ZREALIZOWANE:** (wg wytycznej "NIE tworzymy osobnych FrontendService/AdminService" — metody dodane do istniejącego `OrderRepository`) - `Domain/Order/OrderRepository.php` — dodane metody frontendowe: - `createFromBasket()` — tworzenie zamówienia z koszyka (21 parametrów, pełna logika basket_save) - `generateOrderNumber()` — format YYYY/MM/NNN - `orderDetailsFrontend()`, `findIdByHash()`, `findHashById()` - `front/Controllers/ShopOrderController.php` — kontroler z DI (OrderRepository) - Testy: `OrderRepositoryTest` (9 nowych), `ShopOrderControllerTest` (3 nowe) **USUNIĘTE:** - `front/factory/class.ShopOrder.php` - `front/controls/class.ShopOrder.php` - `front/view/class.ShopOrder.php` **CALLERY ZAKTUALIZOWANE:** - `ShopBasketController` — DI OrderRepository, zmiana basket_save/order_hash - `ClientRepository::clientOrders()` — OrderRepository::orderDetailsFrontend() - `shop\Order::order_resend_confirmation_email()` — OrderRepository::orderDetailsFrontend() - `cron-turstmate.php` — OrderRepository::orderDetailsFrontend() --- ### Etap: Payment Webhook Service — ZREALIZOWANY (ver. 0.290) **Cel:** Wyodrębnienie webhooków płatności z `front\controls\ShopOrder`. **ZREALIZOWANE:** (webhooki przeniesione do `front\Controllers\ShopOrderController` — nadal używają `\shop\Order` do operacji statusów/płatności) - `ShopOrderController::paymentStatusTpay()` — przeniesione 1:1 - `ShopOrderController::paymentStatusPrzelewy24pl()` — ujednolicone z tpay (set_as_paid + update_status zamiast ręcznego $mdb->update) - `ShopOrderController::paymentStatusHotpay()` — analogiczna zamiana na \shop\Order metody **UWAGA:** `\shop\Order` nie jest jeszcze zmigrowany — osobny etap (Order Instance + Apilo Service) --- ### 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~~ | ZREALIZOWANY | — | — | | ~~Banners/Menu/Pages/Articles/Layouts~~ | ~~Treści~~ | ZREALIZOWANY | — | — | | Promotion Engine | Promocje | KRYTYCZNY | 1 serwis | 1 | | Product Frontend | Produkty | KRYTYCZNY | 1 serwis | 1 | | ~~Client/Auth (security fix)~~ | ~~Klienci~~ | ZREALIZOWANY | — | — | | 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)