From 29821bccf2dac0e686b0645d1274e04a383747c3 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Wed, 18 Feb 2026 08:28:02 +0100 Subject: [PATCH] Refactor code structure for improved readability and maintainability --- AGENTS.md | 8 - CLAUDE.md | 62 +- docs/CLASS_CATALOG.md | 2049 +++++++++++++++++++++ docs/FRONTEND_REFACTORING_PLAN.md | 634 ------- docs/LEGACY_SHOP_REFACTORING_PLAN.md | 234 --- docs/MEMORY.md | 1 - docs/PROJECT_STRUCTURE.md | 613 ++---- docs/REFACTORING_PLAN.md | 308 ---- docs/SHOP_ATTRIBUTE_REFACTOR_PLAN.md | 176 -- docs/SHOP_PAYMENT_METHOD_REFACTOR_PLAN.md | 138 -- docs/TESTING.md | 567 +----- 11 files changed, 2266 insertions(+), 2524 deletions(-) create mode 100644 docs/CLASS_CATALOG.md delete mode 100644 docs/FRONTEND_REFACTORING_PLAN.md delete mode 100644 docs/LEGACY_SHOP_REFACTORING_PLAN.md delete mode 100644 docs/REFACTORING_PLAN.md delete mode 100644 docs/SHOP_ATTRIBUTE_REFACTOR_PLAN.md delete mode 100644 docs/SHOP_PAYMENT_METHOD_REFACTOR_PLAN.md diff --git a/AGENTS.md b/AGENTS.md index 8cd7c56..f079afe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,6 @@ Gdy użytkownik napisze `KONIEC PRACY`, wykonaj kolejno: 2. Aktualizacja dokumentacji technicznej, jeśli zmiany tego wymagają: - `docs/DATABASE_STRUCTURE.md` - `docs/PROJECT_STRUCTURE.md` - - `docs/FRONTEND_REFACTORING_PLAN.md` - `docs/FORM_EDIT_SYSTEM.md` - `docs/CHANGELOG.md` - `docs/TESTING.md` @@ -22,14 +21,7 @@ Przed rozpoczęciem implementacji sprawdź aktualną zawartość: - `docs/DATABASE_STRUCTURE.md` - `docs/PROJECT_STRUCTURE.md` -- `docs/REFACTORING_PLAN.md` -- `docs/FRONTEND_REFACTORING_PLAN.md` - `docs/CHANGELOG.md` - `docs/TESTING.md` To ma pomóc zachować spójność zmian i dokumentacji. - - -## INNE - -Przejdźmy teraz do refaktoringu wszystkiego co związane z https://shoppro.project-dc.pl/admin/shop_product/, nowe widoki, klasy (usuwanie starych), poprawa routingu, przeszukanie innych klas pod względem zależności. Zapisz plan i przedstaw mi go a po akceptacji realizuj krok po kroku w trybie Human In The Loop \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 586070c..15cd384 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -shopPRO is a PHP e-commerce platform with an admin panel and customer-facing storefront. It uses Medoo ORM (`$mdb`), Redis caching, and is undergoing a migration from legacy architecture to Domain-Driven Design with Dependency Injection. +shopPRO is a PHP e-commerce platform with an admin panel and customer-facing storefront. It uses Medoo ORM (`$mdb`), Redis caching, and a Domain-Driven Design architecture with Dependency Injection (migration from legacy architecture complete). ## PHP Version Constraint @@ -50,21 +50,27 @@ shopPRO/ │ ├── Domain/ # Business logic repositories (\Domain\) │ ├── Shared/ # Shared utilities (\Shared\) │ │ ├── Cache/ # CacheHandler, RedisConnection +│ │ ├── Email/ # Email (PHPMailer wrapper) │ │ ├── Helpers/ # Helpers (formerly class.S.php) +│ │ ├── Html/ # Html utility +│ │ ├── Image/ # ImageManipulator │ │ └── Tpl/ # Template engine │ ├── admin/ # Admin panel layer -│ │ └── Controllers/ # DI controllers (\admin\Controllers\) -│ ├── front/ # Frontend layer -│ │ ├── App.php # Frontend router (\front\App) -│ │ ├── LayoutEngine.php # Layout engine (\front\LayoutEngine) -│ │ ├── Controllers/ # DI controllers (\front\Controllers\) -│ │ └── Views/ # Static views (\front\Views\) -│ └── shop/ # EMPTY — all legacy classes migrated to Domain\ +│ │ ├── App.php # Admin router (\admin\App) +│ │ ├── Controllers/ # DI controllers (\admin\Controllers\) — 28 controllers +│ │ ├── Support/ # TableListRequestFactory, Forms/FormRequestHandler, Forms/FormFieldRenderer +│ │ ├── Validation/ # FormValidator +│ │ └── ViewModels/ # Forms/ (FormEditViewModel, FormField, FormTab, FormAction, FormFieldType), Common/ (PaginatedTableViewModel) +│ └── front/ # Frontend layer +│ ├── App.php # Frontend router (\front\App) +│ ├── LayoutEngine.php # Layout engine (\front\LayoutEngine) +│ ├── Controllers/ # DI controllers (\front\Controllers\) — 8 controllers +│ └── Views/ # Static views (\front\Views\) — 11 view classes ├── admin/ # Admin panel │ ├── templates/ # Admin view templates │ └── layout/ # Admin CSS/JS/icons ├── templates/ # Frontend view templates -├── libraries/ # Third-party libraries +├── libraries/ # Third-party libraries (Medoo, RedBeanPHP, PHPMailer) ├── tests/ # PHPUnit tests │ ├── bootstrap.php │ ├── stubs/ # Test stubs (CacheHandler, Helpers, ShopProduct) @@ -89,25 +95,27 @@ Custom autoloader in each entry point (not Composer autoload at runtime). Tries 2. `autoload/{namespace}/{ClassName}.php` (PSR-4 style, fallback) ### Namespace Conventions (case-sensitive on Linux!) -- `\Domain\` → `autoload/Domain/` (uppercase D — new directory) -- `\admin\Controllers\` → `autoload/admin/Controllers/` (lowercase a — existing directory) +- `\Domain\` → `autoload/Domain/` (uppercase D) +- `\admin\Controllers\` → `autoload/admin/Controllers/` (lowercase a) - `\Shared\` → `autoload/Shared/` - `\front\` → `autoload/front/` -- ~~`\shop\`~~ → `autoload/shop/` (EMPTY — all classes migrated to `\Domain\`) - Do NOT use `\Admin\` (uppercase A) — the server directory is `admin/` (lowercase) +- `\shop\` namespace is **deleted** — all 12 legacy classes migrated to `\Domain\`, `autoload/shop/` directory removed -### Domain-Driven Architecture (migration complete for admin + frontend) +### Domain-Driven Architecture (migration complete) -All modules use this pattern: +All legacy directories (`admin/controls/`, `admin/factory/`, `admin/view/`, `front/controls/`, `front/view/`, `front/factory/`, `shop/`) have been deleted. All modules now use this pattern: **Domain Layer** (`autoload/Domain/{Module}/`): - `{Module}Repository.php` — data access, business logic, Redis caching - Constructor DI with `$db` (Medoo instance) - Methods serve both admin and frontend (shared Domain, no separate services) +**Domain Modules**: Article, Attribute, Banner, Basket, Cache, Category, Client, Coupon, Dashboard, Dictionaries, Integrations, Languages, Layouts, Newsletter, Order, Pages, PaymentMethod, Producer, Product, ProductSet, Promotion, Scontainers, Settings, ShopStatus, Transport, Update, User + **Admin Controllers** (`autoload/admin/Controllers/`): - DI via constructor (repositories injected) -- Wired in `admin\App::route()` via `$newControllers` map +- Wired in `admin\App::getControllerFactories()` **Frontend Controllers** (`autoload/front/Controllers/`): - DI via constructor @@ -133,12 +141,20 @@ All modules use this pattern: - Full schema: `docs/DATABASE_STRUCTURE.md` ### Form Edit System -Universal form system for admin edit views. Uses ViewModels (`FormEditViewModel`, `FormField`, `FormTab`, `FormAction`), validation (`FormValidator`), and rendering (`FormFieldRenderer`). Template: `admin/templates/components/form-edit.php`. Docs: `docs/FORM_EDIT_SYSTEM.md`. +Universal form system for admin edit views. Docs: `docs/FORM_EDIT_SYSTEM.md`. +- **ViewModels** (`admin\ViewModels\Forms\`): `FormEditViewModel`, `FormField`, `FormTab`, `FormAction`, `FormFieldType` +- **Validation**: `admin\Validation\FormValidator` +- **Rendering**: `admin\Support\Forms\FormFieldRenderer`, `admin\Support\Forms\FormRequestHandler` +- **Template**: `admin/templates/components/form-edit.php` +- **Table lists**: `admin\Support\TableListRequestFactory` + `admin\ViewModels\Common\PaginatedTableViewModel` ### Caching - Redis via `\Shared\Cache\CacheHandler` (singleton `RedisConnection`) - Key pattern for products: `shop\product:{id}:{lang}:{permutation_hash}` - Clear product cache: `\Shared\Helpers\Helpers::clear_product_cache($id)` +- Pattern delete: `CacheHandler::deletePattern("shop\\product:{$id}:*")` +- Default TTL: 86400 (24h) +- Data is serialized — requires `unserialize()` after `get()` - Config: `config.php` (`$config['redis']`) ## Code Patterns @@ -161,6 +177,11 @@ $repo = new \Domain\Example\ExampleRepository($mdb); $controller = new \admin\Controllers\ExampleController($repo); ``` +### Medoo ORM pitfalls +- `$mdb->delete($table, $where)` takes **2 arguments**, NOT 3 — has caused bugs +- `$mdb->get()` returns `null` when no record, NOT `false` +- After `$mdb->insert()`, check `$mdb->id()` to confirm success + ### File naming - New classes: `ClassName.php` (no `class.` prefix) - Legacy classes: `class.ClassName.php` (leave until migrated) @@ -175,7 +196,7 @@ $controller = new \admin\Controllers\ExampleController($repo); When user says **"KONIEC PRACY"**, execute in order: 1. Run tests -2. Update documentation if needed: `docs/DATABASE_STRUCTURE.md`, `docs/PROJECT_STRUCTURE.md`, `docs/FRONTEND_REFACTORING_PLAN.md`, `docs/FORM_EDIT_SYSTEM.md`, `docs/CHANGELOG.md`, `docs/TESTING.md` +2. Update documentation if needed: `docs/DATABASE_STRUCTURE.md`, `docs/PROJECT_STRUCTURE.md`, `docs/FORM_EDIT_SYSTEM.md`, `docs/CHANGELOG.md`, `docs/TESTING.md` 3. Prepare update package per `docs/UPDATE_INSTRUCTIONS.md` 4. Commit 5. Push @@ -184,12 +205,9 @@ Before starting implementation, review current state of docs (see AGENTS.md for ## Key Documentation - `docs/MEMORY.md` — project memory: known issues, confirmed patterns, ORM pitfalls, caching conventions -- `docs/PROJECT_STRUCTURE.md` — detailed project structure and module status -- `docs/REFACTORING_PLAN.md` — Domain migration plan and status -- `docs/FRONTEND_REFACTORING_PLAN.md` — frontend migration plan (mostly complete) -- `docs/LEGACY_SHOP_REFACTORING_PLAN.md` — plan usunięcia 12 legacy klas z `autoload/shop/` (UKOŃCZONY — wszystkie 12 klas usunięte) +- `docs/PROJECT_STRUCTURE.md` — current architecture, layers, cache, entry points, integrations - `docs/DATABASE_STRUCTURE.md` — full database schema -- `docs/TESTING.md` — test suite status and history +- `docs/TESTING.md` — test suite guide and structure - `docs/FORM_EDIT_SYSTEM.md` — form system architecture - `docs/CHANGELOG.md` — version history - `docs/UPDATE_INSTRUCTIONS.md` — how to build client update packages diff --git a/docs/CLASS_CATALOG.md b/docs/CLASS_CATALOG.md new file mode 100644 index 0000000..d394ac3 --- /dev/null +++ b/docs/CLASS_CATALOG.md @@ -0,0 +1,2049 @@ +# Katalog klas i metod — autoload/ + +Wygenerowano: 2026-02-18 +Łącznie: **96 klas**, ~**530+ metod** + +--- + +## Spis treści + +1. [Domain/ — Warstwa domenowa (29 klas)](#domain) +2. [admin/ — Warstwa administracyjna (39 klas)](#admin) +3. [front/ — Warstwa frontendowa (21 klas)](#front) +4. [Shared/ — Klasy współdzielone (7 klas)](#shared) + +--- + + +## 1. Domain/ — Warstwa domenowa + +### Domain\Article\ArticleRepository +File: `autoload/Domain/Article/ArticleRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $articleId): array` +- `public function save(array $data): ?int` +- `public function delete(int $articleId): bool` +- `public function toggleStatus(int $articleId): bool` +- `public function detailsForLanguage(int $articleId, string $langId): ?array` +- `public function frontArticleDetails(int $articleId, string $langId): array` +- `public function frontArticleDetailsBySeoLink(string $seoLink, string $langId): ?array` +- `public function frontArticleList(string $langId, int $page = 1, int $perPage = 12): array` +- `public function frontArticleListAll(string $langId): array` +- `public function getFirstImageCached(int $articleId)` +- `private function baseListSelect(): string` +- `private function translationsMap(int $articleId): array` +- `private function extractTranslations(array $data): array` +- `private function toSwitchValue($value): int` +- `private function defaultArticle(): array` +- `private function clearFrontCache(int $articleId): void` +- `private function saveSeoRedirects(int $articleId, string $langId, string $newSeoLink, string $currentSeoLink): void` + +--- + +### Domain\Attribute\AttributeRepository +File: `autoload/Domain/Attribute/AttributeRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $attributeId): array` +- `public function save(array $data): ?int` +- `public function delete(int $attributeId): bool` +- `public function detailsForLanguage(int $attributeId, string $langId): ?array` +- `public function getAttributeValues(int $attributeId): array` +- `public function findValue(int $valueId): array` +- `public function saveValue(array $data): ?int` +- `public function deleteValue(int $valueId): bool` +- `public function detailsForValueLanguage(int $valueId, string $langId): ?array` +- `public function allForAdmin(): array` +- `public function allValuesForAttribute(int $attributeId): array` +- `public function productAttributes(int $productId): array` +- `public function valueDetails(int $valueId): ?array` +- `public function isValueDefault(int $valueId): int` +- `public function getAttributeOrder(int $attributeId): int` +- `public function getAttributeNameById(int $attributeId, string $langId): string` +- `public function getAttributeValueById(int $valueId, string $langId): string` +- `private function baseListSelect(): string` +- `private function translationsMap(int $attributeId): array` +- `private function extractTranslations(array $data): array` +- `private function valueTranslationsMap(int $valueId): array` +- `private function extractValueTranslations(array $data): array` +- `private function toSwitchValue($value): int` +- `private function defaultAttribute(): array` +- `private function defaultValue(): array` + +--- + +### Domain\Banner\BannerRepository +File: `autoload/Domain/Banner/BannerRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters = [], string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $bannerId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $bannerId): bool` +- `public function allActiveCached(): array` +- `private function toSwitchValue($value): int` + +--- + +### Domain\Basket\BasketCalculator +File: `autoload/Domain/Basket/BasketCalculator.php` +Properties: (brak) + +Methods: +- `public static function summaryPrice($basket, $coupon): float` +- `public static function summaryPriceWithTransport($basket, $coupon, $transport_cost): float` +- `public static function summaryQty($basket): int` +- `public static function summaryWp($basket): float` +- `public static function summaryCoupon($basket, $coupon): float` +- `public static function summaryNetto($basket): float` +- `public static function summaryBrutto($basket): float` + +--- + +### Domain\Cache\CacheRepository +File: `autoload/Domain/Cache/CacheRepository.php` +Properties: +- `private $db` + +Methods: +- `public function __construct($db)` +- `public function clearAll(): array` + +--- + +### Domain\Category\CategoryRepository +File: `autoload/Domain/Category/CategoryRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $categoryId): array` +- `public function save(array $data): ?int` +- `public function delete(int $categoryId): bool` +- `public function allForAdmin(bool $onlyActive = false): array` +- `public function detailsForLanguage(int $categoryId, string $langId): ?array` +- `public function categoryTree(string $langId): array` +- `public function buildTreeRecursive(array $categories, $parentId, array $categoryLangs, int $depth = 0): array` +- `public function categoryTreeFront(string $langId): array` +- `public function categoryDetailsCached(int $categoryId, string $langId): array` +- `public function categoryProductsCached(int $categoryId, string $langId, int $page = 1, string $sort = '', int $limit = 12, array $filters = []): array` +- `public function getCategoryIdBySeoLink(string $seoLink, string $langId): int` +- `public function getCategorySeoLink(int $categoryId, string $langId): string` +- `private function baseListSelect(): string` +- `private function translationsMap(int $categoryId): array` +- `private function extractTranslations(array $data): array` +- `private function toSwitchValue($value): int` +- `private function defaultCategory(): array` +- `private function clearFrontCache(int $categoryId): void` +- `private function saveSeoRedirects(int $categoryId, string $langId, string $newSeoLink, string $currentSeoLink): void` + +--- + +### Domain\Client\ClientRepository +File: `autoload/Domain/Client/ClientRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters, string $sortColumn = 'name', string $sortDir = 'ASC', int $page = 1, int $perPage = 15): array` +- `public function find(int $clientId): ?array` +- `public function findByEmail(string $email): ?array` +- `public function allActiveForAdmin(): array` +- `public function ordersForClient(int $clientId, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function frontRegister(array $data): array` +- `public function frontLogin(string $email, string $password): ?array` +- `public function frontResetPasswordRequest(string $email): bool` +- `public function frontResetPasswordConfirm(string $token, string $password): bool` +- `public function frontUpdateProfile(int $clientId, array $data): bool` +- `public function frontChangePassword(int $clientId, string $currentPassword, string $newPassword): array` +- `public function delete(int $clientId): bool` +- `private function normalizeClient(array $client): array` + +--- + +### Domain\Coupon\CouponRepository +File: `autoload/Domain/Coupon/CouponRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $couponId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $couponId): bool` +- `public function findByCode(string $code): ?array` +- `public function incrementUsageCount(int $couponId): void` +- `public function validateCoupon(string $code): array` +- `private function toSwitchValue($value): int` + +--- + +### Domain\Dashboard\DashboardRepository +File: `autoload/Domain/Dashboard/DashboardRepository.php` +Properties: +- `private $db` + +Methods: +- `public function __construct($db)` +- `public function countOrders(): int` +- `public function totalSales(): float` +- `public function countClients(): int` +- `public function countProducts(): int` +- `public function recentOrdersForGrid(int $limit = 5): array` +- `public function topSellingProducts(int $limit = 5): array` + +--- + +### Domain\Dictionaries\DictionariesRepository +File: `autoload/Domain/Dictionaries/DictionariesRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $unitId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $unitId): bool` +- `public function detailsForLanguage(int $unitId, string $langId): ?array` +- `public function allForSelect(): array` +- `public function getUnitNameCached(int $unitId, string $langId): string` +- `private function baseListSelect(): string` +- `private function translationsMap(int $unitId): array` +- `private function extractTranslations(array $data): array` + +--- + +### Domain\Integrations\IntegrationsRepository +File: `autoload/Domain/Integrations/IntegrationsRepository.php` +Properties: +- `private $db` + +Methods: +- `public function __construct($db)` +- `public function apiloAuth(): array` +- `public function apiloRefreshToken(string $refreshToken): array` +- `public function apiloSyncOrders(string $accessToken, int $page = 1): array` +- `public function apiloSyncProducts(string $accessToken, int $page = 1): array` +- `public function apiloSendOrder(string $accessToken, array $orderData): array` +- `public function apiloGetStatuses(string $accessToken): array` +- `public function apiloSyncOrderStatuses(string $accessToken): array` +- `public function apiloPostWaybill(string $accessToken, int $orderId, array $waybillData): array` +- `public function getApiloSettings(): array` +- `public function saveApiloSettings(array $data): bool` +- `public function shoppro_sync_products(string $token): array` +- `public function shoppro_sync_orders(string $token): array` +- `public function shoppro_sync_order_statuses(string $token): array` +- `public function shoppro_mark_as_sent(string $token, int $orderId): array` +- `private function apiloRequest(string $method, string $url, string $accessToken, array $data = []): array` + +--- + +### Domain\Languages\LanguagesRepository +File: `autoload/Domain/Languages/LanguagesRepository.php` +Properties: +- `private $db` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters = [], string $sortColumn = 'id', string $sortDir = 'ASC', int $page = 1, int $perPage = 50): array` +- `public function find(int $langId): ?array` +- `public function save(array $data): ?int` +- `public function delete(string $langId): bool` +- `public function languagesList(bool $onlyActive = false): array` +- `public function activeLanguagesCount(): int` +- `public function defaultLanguage(): string` +- `public function getLanguageName(string $langId): string` +- `public function saveTranslation(string $langId, string $key, string $value): bool` +- `public function getTranslations(string $langId): array` +- `public function langsFront(): array` +- `public function translationsCached(string $langId): array` + +--- + +### Domain\Layouts\LayoutsRepository +File: `autoload/Domain/Layouts/LayoutsRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters = [], string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $layoutId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $layoutId): bool` +- `public function allForSelect(): array` +- `public function getDefault(): ?array` +- `public function resolveLayoutForProduct(int $productId): ?array` +- `public function resolveLayoutForCategory(int $categoryId): ?array` +- `public function resolveLayoutForPage(int $pageId): ?array` +- `public function resolveLayoutForArticle(int $articleId): ?array` +- `public function resolveLayout(?int $layoutId): ?array` +- `private function toSwitchValue($value): int` + +--- + +### Domain\Newsletter\NewsletterPreviewRenderer +File: `autoload/Domain/Newsletter/NewsletterPreviewRenderer.php` +Properties: +- `private $db` + +Methods: +- `public function __construct($db)` +- `public function render(int $newsletterId): string` + +--- + +### Domain\Newsletter\NewsletterRepository +File: `autoload/Domain/Newsletter/NewsletterRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $newsletterId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $newsletterId): bool` +- `public function listSubscribersForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function findSubscriber(int $subscriberId): ?array` +- `public function saveSubscriber(array $data): ?int` +- `public function deleteSubscriber(int $subscriberId): bool` +- `public function sendNewsletter(int $newsletterId): array` +- `public function saveSettings(array $data): bool` +- `public function getSettings(): array` +- `public function subscribe(string $email): array` +- `public function unsubscribe(string $email): array` + +--- + +### Domain\Order\OrderAdminService +File: `autoload/Domain/Order/OrderAdminService.php` +Properties: +- `private $db` + +Methods: +- `public function __construct($db)` +- `public function getOrderDetails(int $orderId): ?array` +- `public function saveOrder(array $data): ?int` +- `public function updateOrderStatus(int $orderId, int $statusId): bool` +- `public function deleteOrder(int $orderId): bool` +- `public function addProduct(int $orderId, int $productId, int $quantity, $permutationHash = null): bool` +- `public function removeProduct(int $orderId, int $orderProductId): bool` +- `public function updateProductQuantity(int $orderId, int $orderProductId, int $quantity): bool` +- `public function updateProductPrice(int $orderId, int $orderProductId, float $price): bool` +- `public function updateTransportCost(int $orderId, float $cost): bool` +- `public function generateInvoice(int $orderId): ?string` +- `public function sendToApilo(int $orderId): array` +- `public function apiloSyncStatus(int $orderId, string $accessToken): array` + +--- + +### Domain\Order\OrderRepository +File: `autoload/Domain/Order/OrderRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $orderId): ?array` +- `public function findByApiloId(string $apiloId): ?array` +- `public function getOrderProducts(int $orderId): array` +- `public function getOrderHistory(int $orderId): array` +- `public function addHistoryEntry(int $orderId, string $message, ?int $statusId = null): void` +- `public function getLastOrderNumber(): int` +- `public function generateOrderNumber(): string` +- `public function createOrderFront(array $data): ?int` +- `public function updateOrderFromPayment(int $orderId, array $data): bool` +- `public function frontOrderList(int $clientId, int $page = 1, int $perPage = 10): array` +- `public function frontOrderDetails(int $orderId, int $clientId): ?array` +- `public function getOrdersForIntersection(): array` +- `public function updateIntersection(): void` +- `public function countOrdersToday(): int` +- `public function revenueToday(): float` +- `public function countOrdersByStatus(int $statusId): int` +- `public function getOrderNotes(int $orderId): array` +- `public function addOrderNote(int $orderId, string $note, ?int $userId = null): void` + +--- + +### Domain\Pages\PagesRepository +File: `autoload/Domain/Pages/PagesRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $pageId): array` +- `public function save(array $data): ?int` +- `public function delete(int $pageId): bool` +- `public function toggleStatus(int $pageId): bool` +- `public function detailsForLanguage(int $pageId, string $langId): ?array` +- `public function menuItems(string $position, string $langId): array` +- `public function allPagesForSelect(): array` +- `public function allForMenu(): array` +- `public function frontPageDetails(int $pageId, string $langId): array` +- `public function frontPageDetailsBySeoLink(string $seoLink, string $langId): ?array` +- `public function frontMenuItemsCached(string $position, string $langId): array` +- `private function baseListSelect(): string` +- `private function translationsMap(int $pageId): array` +- `private function extractTranslations(array $data): array` +- `private function toSwitchValue($value): int` +- `private function defaultPage(): array` +- `private function clearFrontCache(int $pageId): void` +- `private function saveSeoRedirects(int $pageId, string $langId, string $newSeoLink, string $currentSeoLink): void` + +--- + +### Domain\PaymentMethod\PaymentMethodRepository +File: `autoload/Domain/PaymentMethod/PaymentMethodRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters = [], string $sortColumn = 'name', string $sortDir = 'ASC', int $page = 1, int $perPage = 15): array` +- `public function find(int $paymentMethodId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $paymentMethodId): bool` +- `public function allActive(): array` +- `public function allForAdmin(): array` +- `public function findActiveById(int $paymentMethodId): ?array` +- `public function findActiveByIdCached(int $paymentMethodId)` +- `public function allActiveCached(): array` +- `private function normalizePaymentMethod(array $pm): array` +- `private function toSwitchValue($value): int` + +--- + +### Domain\Producer\ProducerRepository +File: `autoload/Domain/Producer/ProducerRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters = [], string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $producerId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $producerId): bool` +- `public function detailsForLanguage(int $producerId, string $langId): ?array` +- `public function allForSelect(): array` +- `public function allActiveFront(): array` +- `public function producerDetailsCached(int $producerId, string $langId): ?array` +- `public function producerProductsCached(int $producerId, string $langId, int $page = 1, int $limit = 12): array` +- `private function baseListSelect(): string` +- `private function translationsMap(int $producerId): array` +- `private function extractTranslations(array $data): array` +- `private function toSwitchValue($value): int` + +--- + +### Domain\Product\ProductRepository +File: `autoload/Domain/Product/ProductRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function getQuantity(int $productId): ?int` +- `public function find(int $productId): ?array` +- `public function listArchivedForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function getPrice(int $productId): ?float` +- `public function getName(int $productId, string $langId): ?string` +- `public function updateQuantity(int $productId, int $quantity): bool` +- `public function unarchive(int $productId): bool` +- `public function archive(int $productId): bool` +- `public function allProductsForMassEdit(): array` +- `public function getProductsByCategory(int $categoryId): array` +- `public function applyDiscountPercent(int $productId, float $discountPercent): ?array` +- `public function countProducts(?array $where = null): int` +- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function findForAdmin(int $productId): ?array` +- `public function allProductsList(): array` +- `public function productCategoriesText(int $productId): string` +- `public function getParentId(int $productId): ?int` +- `public function productDefaultName(int $productId): ?string` +- `public function saveProduct(array $d, ?int $userId = null): ?int` +- `public function delete(int $productId): bool` +- `public function duplicate(int $productId, bool $withCombinations = false): bool` +- `public function toggleStatus(int $productId): bool` +- `public function updatePriceBrutto(int $productId, $price): bool` +- `public function updatePriceBruttoPromo(int $productId, $price): bool` +- `public function updateCustomLabel(int $productId, string $label, $value): bool` +- `public function getCombinationsForTable(int $productId): array` +- `public function getPermutations(int $productId): array` +- `public function generateCombinations(int $productId, array $attributes): bool` +- `public function deleteCombination(int $combinationId): bool` +- `public function countCombinations(int $productId): int` +- `public function saveCombinationStock0Buy(int $productId, $value): bool` +- `public function saveCombinationSku(int $productId, $sku): bool` +- `public function saveCombinationQuantity(int $productId, $quantity): bool` +- `public function saveCombinationPrice(int $productId, $priceNetto): bool` +- `public function deleteImage(int $imageId): bool` +- `public function updateImageAlt(int $imageId, string $alt): bool` +- `public function saveImagesOrder(int $productId, string $order): bool` +- `public function deleteNonassignedImages(): void` +- `public function deleteFile(int $fileId): bool` +- `public function updateFileName(int $fileId, string $name): bool` +- `public function deleteNonassignedFiles(): void` +- `public function getProductImages(int $productId): array` +- `public function saveXmlName(int $productId, string $xmlName, string $langId): bool` +- `public function customLabelSuggestions(string $customLabel, string $labelType): array` +- `public function saveCustomLabel(int $productId, string $customLabel, string $labelType): bool` +- `public function generateEAN(string $number): string` +- `public function generateGoogleFeedXml(): void` +- `public function updateCombinationPricesFromBase(int $productId, $priceBrutto, $vat, $priceBruttoPromo): void` +- `public function getSkuWithFallback(int $productId, bool $withParentFallback = false)` +- `public function getEanWithFallback(int $productId, bool $withParentFallback = false)` +- `public function isProductActiveCached(int $productId): int` +- `public function getMinimalPriceCached(int $productId, $priceBruttoPromo = null)` +- `public function productCategoriesFront(int $productId): array` +- `public function getProductNameCached(int $productId, string $langId)` +- `public function getFirstImageCached(int $productId)` +- `public function getWeightCached(int $productId)` +- `public function promotedProductIdsCached(int $limit = 6): array` +- `public function topProductIds(int $limit = 6): array` +- `public function newProductIds(int $limit = 10): array` +- `public function productDetailsFrontCached(int $productId, string $langId)` +- `public function getWarehouseMessageZero(int $productId, string $langId)` +- `public function getWarehouseMessageNonzero(int $productId, string $langId)` +- `public function findCustomFieldCached(int $customFieldId)` +- `public function findCached(int $productId, string $langId = null, string $permutationHash = null)` +- `public function isProductOnPromotion(int $productId): bool` +- `public function productSetsWhenAddToBasket(int $productId): string` +- `public function addVisit(int $productId): void` +- `public function getProductImg(int $productId)` +- `public function getProductUrl(int $productId): string` +- `public function searchProductsByNameCount(string $query, string $langId): int` +- `public function getProductsIdByName(string $query, string $langId, int $limit, int $from): array` +- `public function searchProductsByName(string $query, string $langId, int $page = 0): array` +- `public function searchProductByNameAjax(string $query, string $langId): array` +- `public function isStock0Buy(int $productId)` +- `public function getProductPermutationQuantityOptions(int $productId, $permutation)` +- `public function getProductIdByAttributes(int $parentId, array $attributes)` +- `public function getProductPermutationHash(int $productId)` +- `public function getProductAttributes($products)` +- `public function generateSkuCode(): string` +- `public function productMeta(int $productId): array` +- `public function generateSubtitleFromAttributes(string $permutationHash, string $langId = null): string` +- `public function getDefaultCombinationPrices(array $product): array` +- `public function getProductDataBySelectedAttributes(array $product, string $selectedAttribute): array` +- `public function productCategories(int $productId): array` +- `public static function arrayCartesian(array $input): array` +- `private function defaultLangId(): string` +- `private function saveLanguages(int $productId, array $d, bool $isNew): void` +- `private function handleSeoRedirects(int $productId, string $langId, string $newSeoLink, string $currentSeoLink): void` +- `private function saveCategories(int $productId, $categories): void` +- `private function saveRelatedProducts(int $productId, $products): void` +- `private function moveTemporaryFiles(int $productId): void` +- `private function moveTemporaryImages(int $productId): void` +- `private function saveCustomFields(int $productId, array $names, array $types, array $required): void` +- `private function cleanupDeletedFiles(int $productId): void` +- `private function cleanupDeletedImages(int $productId): void` +- `private function nullIfEmpty($value)` +- `private function appendCombinationToXml(\DOMDocument $doc, \DOMElement $channelNode, $product, $combination, string $domainPrefix, string $url): void` +- `private function appendProductToXml(\DOMDocument $doc, \DOMElement $channelNode, $product, string $domainPrefix, string $url): void` +- `private function appendImagesToXml(\DOMDocument $doc, \DOMElement $itemNode, $product, string $domainPrefix, string $url): void` +- `private function appendShippingToXml(\DOMDocument $doc, \DOMElement $itemNode, $product): void` +- `private function updateCombinationPrices(int $productId, float $priceNetto, float $vat, ?float $priceNettoPromo): void` + +--- + +### Domain\ProductSet\ProductSetRepository +File: `autoload/Domain/ProductSet/ProductSetRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters = [], string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $setId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $setId): bool` +- `public function allForSelect(): array` +- `public function getProductsInSet(int $setId): array` +- `public function addProductToSet(int $setId, int $productId): bool` +- `public function removeProductFromSet(int $setId, int $productId): bool` + +--- + +### Domain\Promotion\PromotionRepository +File: `autoload/Domain/Promotion/PromotionRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters = [], string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $promotionId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $promotionId): bool` +- `public function getProducts(int $promotionId): array` +- `public function addProducts(int $promotionId, array $productIds): void` +- `public function removeProduct(int $promotionId, int $productId): bool` +- `public function applyPromotions(): array` +- `public function revertPromotions(): array` +- `public function basketPromotionForTransport(array $basket): ?array` +- `public function basketPromotionForDiscount(array $basket): ?array` +- `public function activePromotionsForBasket(): array` +- `private function toSwitchValue($value): int` + +--- + +### Domain\Scontainers\ScontainersRepository +File: `autoload/Domain/Scontainers/ScontainersRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array` +- `public function find(int $containerId): array` +- `public function detailsForLanguage(int $containerId, string $langId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $containerId): bool` +- `public function frontScontainerDetails(int $scontainerId, string $langId): array` +- `private function baseListSelect(): string` +- `private function clearFrontCache(int $containerId): void` +- `private function translationsMap(int $containerId): array` +- `private function extractTranslations(array $data): array` +- `private function toSwitchValue($value): int` +- `private function defaultContainer(): array` + +--- + +### Domain\Settings\SettingsRepository +File: `autoload/Domain/Settings/SettingsRepository.php` +Properties: +- `private $db` + +Methods: +- `public function __construct($db = null)` +- `public function saveSettings(array $values): array` +- `public function updateSetting(string $param, $value): bool` +- `public function updateSettings(array $settings): bool` +- `public function getSettings(): array` +- `public function allSettings(bool $skipCache = false): array` +- `public function getSingleValue(string $param): string` +- `private function isEnabled($value): bool` + +--- + +### Domain\ShopStatus\ShopStatusRepository +File: `autoload/Domain/ShopStatus/ShopStatusRepository.php` +Properties: +- `private $db` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters = [], string $sortColumn = 'id', string $sortDir = 'ASC', int $page = 1, int $perPage = 50): array` +- `public function find(int $statusId): ?array` +- `public function save(array $data): ?int` +- `public function delete(int $statusId): bool` +- `public function allForSelect(): array` +- `public function getStatusName(int $statusId): string` +- `public function findByApiloId(string $apiloId): ?array` + +--- + +### Domain\Transport\TransportRepository +File: `autoload/Domain/Transport/TransportRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function listForAdmin(array $filters = [], string $sortColumn = 'name', string $sortDir = 'ASC', int $page = 1, int $perPage = 15): array` +- `public function find(int $transportId): ?array` +- `public function save(array $data): ?int` +- `public function allActive(): array` +- `public function findActiveById(int $transportId): ?array` +- `public function getApiloCarrierAccountId(int $transportId): ?int` +- `public function getTransportCost(int $transportId): ?float` +- `public function lowestTransportPrice(int $wp): ?float` +- `public function allForAdmin(): array` +- `public function transportMethodsFront($basket, $coupon): array` +- `public function transportCostCached($transportId)` +- `public function findActiveByIdCached($transportId)` +- `public function forPaymentMethod(int $paymentMethodId): array` +- `private function savePaymentMethodLinks(int $transportId, $paymentMethods): void` +- `private function normalizeTransport(array $transport): array` +- `private function toSwitchValue($value): int` + +--- + +### Domain\Update\UpdateRepository +File: `autoload/Domain/Update/UpdateRepository.php` +Properties: +- `private $db` + +Methods: +- `public function __construct($db)` +- `public function update(): array` +- `public function runPendingMigrations(): void` +- `public function update0197(): void` +- `private function downloadAndApply(string $ver, string $dir, array $log): array` +- `private function executeSql(string $sqlUrl, array $log): array` +- `private function deleteFiles(string $filesUrl, array $log): array` +- `private function extractZip(string $fileName, array $log): array` +- `private function saveLog(array $log): void` + +--- + +### Domain\User\UserRepository +File: `autoload/Domain/User/UserRepository.php` +Properties: +- `private $db` +- `private const MAX_PER_PAGE = 100` + +Methods: +- `public function __construct($db)` +- `public function getById(int $userId): ?array` +- `public function updateById(int $userId, array $data): bool` +- `public function verifyTwofaCode(int $userId, string $code): bool` +- `public function sendTwofaCode(int $userId, bool $resend = false): bool` +- `public function delete(int $userId): bool` +- `public function find(int $userId): ?array` +- `public function save(int $userId, string $login, $status, string $password, string $passwordRepeat, $admin, $twofaEnabled = 0, string $twofaEmail = ''): array` +- `public function checkLogin(string $login, int $userId): array` +- `public function logon(string $login, string $password): int` +- `public function details(string $login): ?array` +- `public function listForAdmin(array $filters, string $sortColumn = 'login', string $sortDir = 'ASC', int $page = 1, int $perPage = 15): array` +- `private function toSwitchValue($value): int` + +--- + + +## 2. admin/ — Warstwa administracyjna + +### admin\App +File: `autoload/admin/App.php` +Properties: +- `private static array $newControllers` + +Constants: +- `const APP_SECRET_KEY = 'c3cb2537d25c0efc9e573d059d79c3b8'` + +Methods: +- `public static function finalize_admin_login(array $user, string $domain, string $cookie_name, bool $remember = false)` +- `public static function special_actions()` +- `public static function render(): string` +- `public static function route()` +- `private static function createController(string $moduleName)` +- `private static function getControllerFactories(): array` +- `public static function update()` + +--- + +### admin\Controllers\ArticlesArchiveController +File: `autoload/admin/Controllers/ArticlesArchiveController.php` +Properties: +- `private ArticleRepository $repository` + +Methods: +- `public function __construct(ArticleRepository $repository)` +- `public function list(): string` +- `public function view_list(): string` +- `public function restore(): void` +- `public function article_restore(): void` +- `public function delete(): void` +- `public function article_delete(): void` + +--- + +### admin\Controllers\ArticlesController +File: `autoload/admin/Controllers/ArticlesController.php` +Properties: +- `private ArticleRepository $repository` +- `private LanguagesRepository $languagesRepository` +- `private LayoutsRepository $layoutsRepository` +- `private PagesRepository $pagesRepository` + +Methods: +- `public function __construct(ArticleRepository $repository, LanguagesRepository $languagesRepository, LayoutsRepository $layoutsRepository, PagesRepository $pagesRepository)` +- `public function list(): string` +- `public function galleryOrderSave(): void` +- `public function filesOrderSave(): void` +- `public function save(): void` +- `public function imageAltChange(): void` +- `public function fileNameChange(): void` +- `public function imageDelete(): void` +- `public function fileDelete(): void` +- `public function delete(): void` +- `public function edit(): string` +- `private function resolveSavePayload(): array` +- `private function buildFormViewModel(array $article, array $languages, array $menus, array $layouts): FormEditViewModel` +- `private function renderPagesTree(array $menus, array $article): string` +- `private function renderImagesBox(array $article): string` +- `private function renderFilesBox(array $article): string` +- `private function escapeHtml(string $value): string` + +--- + +### admin\Controllers\BannerController +File: `autoload/admin/Controllers/BannerController.php` +Properties: +- `private BannerRepository $repository` +- `private LanguagesRepository $languagesRepository` +- `private FormRequestHandler $formHandler` + +Methods: +- `public function __construct(BannerRepository $repository, LanguagesRepository $languagesRepository)` +- `public function list(): string` +- `public function edit(): string` +- `public function save(): void` +- `public function delete(): void` +- `private function buildFormViewModel(array $banner, array $languages, ?array $errors = null): FormEditViewModel` +- `private function getFormId(): string` + +--- + +### admin\Controllers\DashboardController +File: `autoload/admin/Controllers/DashboardController.php` +Properties: +- `private DashboardRepository $repository` +- `private ShopStatusRepository $statusesRepository` + +Methods: +- `public function __construct(DashboardRepository $repository, ShopStatusRepository $statusesRepository)` +- `public function main_view(): string` + +--- + +### admin\Controllers\DictionariesController +File: `autoload/admin/Controllers/DictionariesController.php` +Properties: +- `private DictionariesRepository $repository` +- `private LanguagesRepository $languagesRepository` +- `private FormRequestHandler $formHandler` + +Methods: +- `public function __construct(DictionariesRepository $repository, LanguagesRepository $languagesRepository)` +- `public function list(): string` +- `public function edit(): string` +- `public function save(): void` +- `public function delete(): void` +- `private function buildFormViewModel(array $unit, array $languages, ?array $errors = null): FormEditViewModel` +- `private function getFormId(): string` + +--- + +### admin\Controllers\FilemanagerController +File: `autoload/admin/Controllers/FilemanagerController.php` +Properties: (brak) + +Constants: +- `private const RFM_KEY_TTL = 1200` +- `private const FILEMANAGER_DIALOG_PATH = '/libraries/filemanager-9.14.2/dialog.php'` + +Methods: +- `public function draw(): string` +- `private function ensureFilemanagerAccessKey(): string` +- `private function buildFilemanagerUrl(string $akey): string` + +--- + +### admin\Controllers\IntegrationsController +File: `autoload/admin/Controllers/IntegrationsController.php` +Properties: +- `private IntegrationsRepository $repository` + +Methods: +- `public function __construct(IntegrationsRepository $repository)` +- `public function apilo_settings(): string` +- `public function apilo_settings_save(): void` +- `public function apilo_authorization(): void` +- `public function get_platform_list(): void` +- `public function get_status_types_list(): void` +- `public function get_carrier_account_list(): void` +- `public function get_payment_types_list(): void` +- `public function apilo_create_product(): void` +- `public function apilo_product_search(): void` +- `public function apilo_product_select_save(): void` +- `public function apilo_product_select_delete(): void` +- `public function shoppro_settings(): string` +- `public function shoppro_settings_save(): void` +- `public function shoppro_product_import(): void` +- `private function fetchApiloListWithFeedback(string $type, string $label): void` + +--- + +### admin\Controllers\LanguagesController +File: `autoload/admin/Controllers/LanguagesController.php` +Properties: +- `private LanguagesRepository $repository` +- `private FormRequestHandler $formHandler` + +Methods: +- `public function __construct(LanguagesRepository $repository)` +- `public function list(): string` +- `public function view_list(): string` +- `public function language_edit(): string` +- `public function language_save(): void` +- `public function language_delete(): void` +- `public function translation_list(): string` +- `public function translation_edit(): string` +- `public function translation_save(): void` +- `public function translation_delete(): void` +- `private function buildLanguageFormViewModel(array $language, int $maxOrder, ?array $errors = null): FormEditViewModel` +- `private function buildTranslationFormViewModel(array $translation, array $languages, ?array $errors = null): FormEditViewModel` +- `private function extractLegacyTranslations(array $values, array $languages): array` +- `private function clearLanguageSessions(array $languages): void` +- `private function getLanguageFormId(): string` +- `private function getTranslationFormId(): string` + +--- + +### admin\Controllers\LayoutsController +File: `autoload/admin/Controllers/LayoutsController.php` +Properties: +- `private LayoutsRepository $repository` +- `private LanguagesRepository $languagesRepository` + +Methods: +- `public function __construct(LayoutsRepository $repository, LanguagesRepository $languagesRepository)` +- `public function list(): string` +- `public function edit(): string` +- `public function save(): void` +- `public function delete(): void` + +--- + +### admin\Controllers\NewsletterController +File: `autoload/admin/Controllers/NewsletterController.php` +Properties: +- `private NewsletterRepository $repository` +- `private NewsletterPreviewRenderer $previewRenderer` +- `private FormRequestHandler $formHandler` + +Methods: +- `public function __construct(NewsletterRepository $repository, NewsletterPreviewRenderer $previewRenderer)` +- `public function list(): string` +- `public function view_list(): string` +- `public function emails_list(): string` +- `public function email_delete(): void` +- `public function delete(): void` +- `public function prepare(): string` +- `public function preview(): string` +- `public function send(): void` +- `public function settings(): string` +- `public function settings_save(): void` +- `public function email_templates_user(): string` +- `public function email_templates_admin(): string` +- `public function email_template_edit(): string` +- `public function template_save(): void` +- `public function email_template_delete(): void` +- `private function buildSettingsFormViewModel(array $settings, ?array $errors = null): FormEditViewModel` +- `private function buildTemplateFormViewModel(array $template, ?array $errors = null): FormEditViewModel` +- `private function templatesListViewModel(): PaginatedTableViewModel` +- `private function settingsFormId(): string` +- `private function templateFormId(int $templateId): string` + +--- + +### admin\Controllers\PagesController +File: `autoload/admin/Controllers/PagesController.php` +Properties: +- `private PagesRepository $repository` +- `private LanguagesRepository $languagesRepository` +- `private LayoutsRepository $layoutsRepository` + +Methods: +- `public function __construct(PagesRepository $repository, LanguagesRepository $languagesRepository, LayoutsRepository $layoutsRepository)` +- `public function list(): string` +- `public function browseList(): string` +- `public function pagesUrlBrowser(): string` +- `public function menuEdit(): string` +- `public function menuSave(): void` +- `private function buildMenuFormViewModel(array $menu): FormEditViewModel` +- `public function menuDelete(): void` +- `public function edit(): string` +- `public function save(): void` +- `private function buildPageFormViewModel(array $page, int $parentId, int $menuId, array $menus, array $layouts, array $languages): FormEditViewModel` +- `public function delete(): void` +- `public function pageArticles(): string` +- `public function savePagesOrder(): void` +- `public function saveArticlesOrder(): void` +- `public function generateSeoLink(): void` +- `public function cookieMenus(): void` +- `public function cookiePages(): void` +- `private function withPreviewUrls(array $pages, string $defaultLanguage): array` +- `private function cookieState(string $cookieName): array` + +--- + +### admin\Controllers\ProductArchiveController +File: `autoload/admin/Controllers/ProductArchiveController.php` +Properties: +- `private ProductRepository $productRepository` + +Methods: +- `public function __construct(ProductRepository $productRepository)` +- `public function list(): string` +- `public function unarchive(): void` + +--- + +### admin\Controllers\ScontainersController +File: `autoload/admin/Controllers/ScontainersController.php` +Properties: +- `private ScontainersRepository $repository` +- `private LanguagesRepository $languagesRepository` +- `private FormRequestHandler $formHandler` + +Methods: +- `public function __construct(ScontainersRepository $repository, LanguagesRepository $languagesRepository)` +- `public function list(): string` +- `public function view_list(): string` +- `public function edit(): string` +- `public function container_edit(): string` +- `public function save(): void` +- `public function container_save(): void` +- `public function delete(): void` +- `public function container_delete(): void` +- `private function buildFormViewModel(array $container, array $languages, ?array $errors = null): FormEditViewModel` +- `private function formId(): string` + +--- + +### admin\Controllers\SettingsController +File: `autoload/admin/Controllers/SettingsController.php` +Properties: +- `private SettingsRepository $settingsRepository` +- `private LanguagesRepository $languagesRepository` +- `private FormRequestHandler $formHandler` + +Methods: +- `public function __construct(SettingsRepository $settingsRepository, LanguagesRepository $languagesRepository)` +- `public function clearCache(): void` +- `public function clearCacheAjax(): void` +- `public function globalSearchAjax(): void` +- `public function save(): void` +- `public function view(): string` +- `private function buildFormViewModel(array $settings, array $languages, ?array $errors = null): FormEditViewModel` +- `private function getFormId(): string` +- `private function transformSettingsToFormData(array $settings, array $languages): array` +- `private function transformFormDataToSettings(array $data): array` + +--- + +### admin\Controllers\ShopAttributeController +File: `autoload/admin/Controllers/ShopAttributeController.php` +Properties: +- `private AttributeRepository $repository` +- `private LanguagesRepository $languagesRepository` + +Methods: +- `public function __construct(AttributeRepository $repository, LanguagesRepository $languagesRepository)` +- `public function list(): string` +- `public function edit(): string` +- `public function save(): void` +- `public function delete(): void` +- `public function values(): string` +- `public function values_save(): void` +- `public function value_row_tpl(): void` +- `private function validateValuesRows(array $rows, string $defaultLanguageId): array` +- `private function buildFormViewModel(array $attribute, array $languages): FormEditViewModel` + +--- + +### admin\Controllers\ShopCategoryController +File: `autoload/admin/Controllers/ShopCategoryController.php` +Properties: +- `private CategoryRepository $repository` +- `private LanguagesRepository $languagesRepository` + +Methods: +- `public function __construct(CategoryRepository $repository, LanguagesRepository $languagesRepository)` +- `public function view_list(): string` +- `public function list(): string` +- `public function category_edit(): string` +- `public function edit(): string` +- `public function save(): void` +- `public function category_delete(): void` +- `public function delete(): void` +- `public function category_products(): string` +- `public function products(): string` +- `public function category_url_browser(): void` +- `public function save_categories_order(): void` +- `public function save_products_order(): void` +- `public function cookie_categories(): void` + +--- + +### admin\Controllers\ShopClientsController +File: `autoload/admin/Controllers/ShopClientsController.php` +Properties: +- `private ClientRepository $repository` + +Methods: +- `public function __construct(ClientRepository $repository)` +- `public function list(): string` +- `public function view_list(): string` +- `public function details(): string` +- `public function clients_details(): string` + +--- + +### admin\Controllers\ShopCouponController +File: `autoload/admin/Controllers/ShopCouponController.php` +Properties: +- `private CouponRepository $repository` + +Methods: +- `public function __construct(CouponRepository $repository)` +- `public function list(): string` +- `public function view_list(): string` +- `public function edit(): string` +- `public function coupon_edit(): string` +- `public function save(): void` +- `public function coupon_save(): void` +- `public function delete(): void` +- `public function coupon_delete(): void` +- `private function buildFormViewModel(array $coupon, array $categories): FormEditViewModel` + +--- + +### admin\Controllers\ShopOrderController +File: `autoload/admin/Controllers/ShopOrderController.php` +Properties: +- `private OrderAdminService $service` + +Methods: +- `public function __construct(OrderAdminService $service)` +- `public function list(): string` +- `public function view_list(): string` +- `public function details(): string` +- `public function order_details(): string` +- `public function edit(): string` +- `public function order_edit(): string` +- `public function save(): void` +- `public function order_save(): void` +- `public function notes_save(): void` +- `public function order_status_change(): void` +- `public function order_resend_confirmation_email(): void` +- `public function set_order_as_unpaid(): void` +- `public function set_order_as_paid(): void` +- `public function send_order_to_apilo(): void` +- `public function toggle_trustmate_send(): void` +- `public function delete(): void` +- `public function order_delete(): void` +- `private function formatDateTime(string $value): string` + +--- + +### admin\Controllers\ShopPaymentMethodController +File: `autoload/admin/Controllers/ShopPaymentMethodController.php` +Properties: +- `private PaymentMethodRepository $repository` + +Methods: +- `public function __construct(PaymentMethodRepository $repository)` +- `public function list(): string` +- `public function edit(): string` +- `public function save(): void` +- `private function buildFormViewModel(array $paymentMethod, array $apiloPaymentTypes): FormEditViewModel` +- `private function getApiloPaymentTypes(): array` + +--- + +### admin\Controllers\ShopProducerController +File: `autoload/admin/Controllers/ShopProducerController.php` +Properties: +- `private ProducerRepository $repository` +- `private LanguagesRepository $languagesRepository` +- `private FormRequestHandler $formHandler` + +Methods: +- `public function __construct(ProducerRepository $repository, LanguagesRepository $languagesRepository)` +- `public function list(): string` +- `public function view_list(): string` +- `public function edit(): string` +- `public function producer_edit(): string` +- `public function save(): void` +- `public function producer_save(): void` +- `public function delete(): void` +- `public function producer_delete(): void` +- `private function buildFormViewModel(array $producer, array $languages, ?array $errors = null): FormEditViewModel` +- `private function formId(): string` +- `private function toSwitchValue($value): int` + +--- + +### admin\Controllers\ShopProductController +File: `autoload/admin/Controllers/ShopProductController.php` +Properties: +- `private ProductRepository $repository` +- `private IntegrationsRepository $integrationsRepository` +- `private LanguagesRepository $languagesRepository` + +Methods: +- `public function __construct(ProductRepository $repository, IntegrationsRepository $integrationsRepository, LanguagesRepository $languagesRepository)` +- `public function view_list(): string` +- `public function product_edit(): string` +- `private function layoutsForProductEdit($db): array` +- `private function buildProductFormViewModel(array $product, array $languages, array $categories, array $layouts, array $products, array $sets, array $producers, array $units, $dlang): FormEditViewModel` +- `private function renderSkuField(array $product): string` +- `private function renderCategoriesTree(array $categories, array $product, $dlang): string` +- `private function renderGalleryBox(array $product): string` +- `private function renderFilesBox(array $product): string` +- `private function renderRelatedProducts(array $product, array $products, array $sets): string` +- `private function renderCustomFieldsBox(array $product): string` +- `private function escapeHtml(string $value): string` +- `private function resolveSavePayload(): array` +- `public function save(): void` +- `public function duplicate_product(): void` +- `public function product_archive(): void` +- `public function product_unarchive(): void` +- `public function product_delete(): void` +- `public function change_product_status(): void` +- `public function product_change_price_brutto(): void` +- `public function product_change_price_brutto_promo(): void` +- `public function product_change_custom_label(): void` +- `public function product_custom_label_suggestions(): void` +- `public function product_custom_label_save(): void` +- `public function ajax_product_url(): void` +- `public function generate_sku_code(): void` +- `public function product_combination(): string` +- `public function generate_combination(): void` +- `public function delete_combination(): void` +- `public function product_combination_stock_0_buy_save(): void` +- `public function product_combination_sku_save(): void` +- `public function product_combination_quantity_save(): void` +- `public function product_combination_price_save(): void` +- `public function delete_combination_ajax(): void` +- `public function image_delete(): void` +- `public function images_order_save(): void` +- `public function image_alt_change(): void` +- `public function product_file_delete(): void` +- `public function product_file_name_change(): void` +- `public function product_image_delete(): void` +- `public function mass_edit(): string` +- `public function mass_edit_save(): void` +- `public function get_products_by_category(): void` + +--- + +### admin\Controllers\ShopProductSetsController +File: `autoload/admin/Controllers/ShopProductSetsController.php` +Properties: +- `private ProductSetRepository $repository` + +Methods: +- `public function __construct(ProductSetRepository $repository)` +- `public function list(): string` +- `public function view_list(): string` +- `public function edit(): string` +- `public function set_edit(): string` +- `public function save(): void` +- `public function delete(): void` +- `public function set_delete(): void` +- `private function buildFormViewModel(array $set, array $products = []): FormEditViewModel` +- `private function toSwitchValue($value): int` +- `private function renderProductsSelect(array $products, array $selectedProducts): string` + +--- + +### admin\Controllers\ShopPromotionController +File: `autoload/admin/Controllers/ShopPromotionController.php` +Properties: +- `private PromotionRepository $repository` + +Methods: +- `public function __construct(PromotionRepository $repository)` +- `public function list(): string` +- `public function edit(): string` +- `public function save(): void` +- `public function delete(): void` +- `private function buildFormViewModel(array $promotion, array $categories): FormEditViewModel` + +--- + +### admin\Controllers\ShopStatusesController +File: `autoload/admin/Controllers/ShopStatusesController.php` +Properties: +- `private ShopStatusRepository $repository` + +Methods: +- `public function __construct(ShopStatusRepository $repository)` +- `public function list(): string` +- `public function edit(): string` +- `public function save(): void` +- `private function buildFormViewModel(array $status, array $apiloStatusList): FormEditViewModel` +- `private function getApiloStatusList(): array` + +--- + +### admin\Controllers\ShopTransportController +File: `autoload/admin/Controllers/ShopTransportController.php` +Properties: +- `private TransportRepository $transportRepository` +- `private PaymentMethodRepository $paymentMethodRepository` + +Methods: +- `public function __construct(TransportRepository $transportRepository, PaymentMethodRepository $paymentMethodRepository)` +- `public function list(): string` +- `public function edit(): string` +- `public function save(): void` +- `private function buildFormViewModel(array $transport, array $paymentMethods, array $apiloCarrierAccounts): FormEditViewModel` +- `private function getApiloCarrierAccounts(): array` + +--- + +### admin\Controllers\UpdateController +File: `autoload/admin/Controllers/UpdateController.php` +Properties: +- `private UpdateRepository $repository` + +Methods: +- `public function __construct(UpdateRepository $repository)` +- `public function main_view(): string` +- `public function update(): void` +- `public function updateAll(): void` + +--- + +### admin\Controllers\UsersController +File: `autoload/admin/Controllers/UsersController.php` +Properties: +- `private UserRepository $repository` +- `private FormRequestHandler $formHandler` + +Methods: +- `public function __construct(UserRepository $repository)` +- `public function user_delete(): void` +- `public function user_save(): void` +- `public function user_edit(): string` +- `public function view_list(): string` +- `public function list(): string` +- `public function login_form(): string` +- `public function twofa(): string` +- `private function normalizeUser(?array $user): array` +- `private function buildFormViewModel(array $user, ?array $errors = null): FormEditViewModel` +- `private function getFormId(): string` +- `private function isTwofaEmailValidForEnabled($twofaEnabled, string $twofaEmail): bool` + +--- + +### admin\Support\TableListRequestFactory +File: `autoload/admin/Support/class.TableListRequestFactory.php` +Properties: (brak) + +Constants: +- `public const DEFAULT_PER_PAGE_OPTIONS = [5, 10, 15, 25, 50, 100]` +- `public const DEFAULT_PER_PAGE = 15` + +Methods: +- `public static function fromRequest(array $filterDefinitions, array $sortableColumns, string $defaultSortColumn = 'date_add', ?array $perPageOptions = null, ?int $defaultPerPage = null): array` + +--- + +### admin\Support\Forms\FormFieldRenderer +File: `autoload/admin/Support/Forms/FormFieldRenderer.php` +Properties: +- `private FormEditViewModel $form` + +Methods: +- `public function __construct(FormEditViewModel $form)` +- `public function renderField(FormField $field): string` +- `public function renderText(FormField $field): string` +- `public function renderNumber(FormField $field): string` +- `public function renderEmail(FormField $field): string` +- `public function renderPassword(FormField $field): string` +- `public function renderDate(FormField $field): string` +- `public function renderDatetime(FormField $field): string` +- `public function renderSwitch(FormField $field): string` +- `public function renderSelect(FormField $field): string` +- `public function renderTextarea(FormField $field): string` +- `public function renderEditor(FormField $field): string` +- `public function renderImage(FormField $field): string` +- `public function renderFile(FormField $field): string` +- `public function renderHidden(FormField $field): string` +- `public function renderColor(FormField $field): string` +- `public function renderCustom(FormField $field): string` +- `public function renderLangSection(FormField $section): string` +- `private function renderLangField(FormField $field, $languageId, string $sectionName): string` +- `private function generateFilemanagerUrl(string $fieldId): string` +- `private function wrapWithError(string $html, ?string $error): string` + +--- + +### admin\Support\Forms\FormRequestHandler +File: `autoload/admin/Support/Forms/FormRequestHandler.php` +Properties: +- `private FormValidator $validator` + +Methods: +- `public function __construct()` +- `public function handleSubmit(FormEditViewModel $formViewModel, array $postData): array` +- `private function processData(array $postData, array $fields): array` +- `private function processLangSection(array $postData, $section): array` +- `public function restoreFromPersist(FormEditViewModel $formViewModel): ?array` +- `public function isFormSubmit(string $formId): bool` + +--- + +### admin\Validation\FormValidator +File: `autoload/admin/Validation/FormValidator.php` +Properties: +- `private array $errors` + +Methods: +- `public function validate(array $data, array $fields, ?array $languages = null): array` +- `private function validateField(array $data, FormField $field): void` +- `private function validateLangSection(array $data, FormField $section, array $languages): void` +- `private function isEmpty($value): bool` +- `private function isValidDate(string $date): bool` +- `private function isValidDateTime(string $datetime): bool` +- `public function isValid(): bool` +- `public function getErrors(): array` +- `public function getFirstError(): ?string` + +--- + +### admin\ViewModels\Common\PaginatedTableViewModel +File: `autoload/admin/ViewModels/Common/class.PaginatedTableViewModel.php` +Properties: +- `public array $columns` +- `public array $rows` +- `public array $filters` +- `public array $sort` +- `public array $pagination` +- `public array $query` +- `public array $perPageOptions` +- `public array $sortableColumns` +- `public string $basePath` +- `public string $emptyMessage` +- `public ?string $createUrl` +- `public ?string $createLabel` +- `public ?string $customScriptView` + +Methods: +- `public function __construct(array $columns = [], array $rows = [], array $filters = [], array $sort = [], array $pagination = [], array $query = [], array $perPageOptions = [5, 10, 15, 25, 50, 100], array $sortableColumns = [], string $basePath = '', string $emptyMessage = 'Brak danych.', ?string $createUrl = null, ?string $createLabel = null, ?string $customScriptView = null)` + +--- + +### admin\ViewModels\Forms\FormAction +File: `autoload/admin/ViewModels/Forms/FormAction.php` +Properties: +- `public string $name` +- `public string $label` +- `public string $type` +- `public string $url` +- `public ?string $backUrl` +- `public string $cssClass` +- `public array $attributes` + +Methods: +- `public function __construct(string $name, string $label, string $url = '', ?string $backUrl = null, string $cssClass = 'btn btn-primary', string $type = 'submit', array $attributes = [])` +- `public static function save(string $url, string $backUrl = '', string $label = 'Zapisz'): self` +- `public static function cancel(string $backUrl, string $label = 'Anuluj'): self` + +--- + +### admin\ViewModels\Forms\FormEditViewModel +File: `autoload/admin/ViewModels/Forms/FormEditViewModel.php` +Properties: +- `public string $formId` +- `public string $title` +- `public string $method` +- `public string $action` +- `public ?string $backUrl` +- `public array $tabs` +- `public array $fields` +- `public array $hiddenFields` +- `public array $actions` +- `public bool $persist` +- `public array $data` +- `public ?array $validationErrors` +- `public ?array $languages` + +Methods: +- `public function __construct(string $formId, string $title, array $data = [], array $fields = [], array $tabs = [], array $actions = [], string $method = 'POST', string $action = '', ?string $backUrl = null, bool $persist = true, array $hiddenFields = [], ?array $languages = null, ?array $validationErrors = null)` +- `public function hasTabs(): bool` +- `public function hasLangSections(): bool` +- `public function getFieldsForTab(string $tabId): array` +- `public function getLangSectionsForTab(string $tabId): array` +- `public function getFieldValue(FormField $field, $languageId = null, ?string $langFieldName = null)` +- `public function hasError(string $fieldName, $languageId = null): bool` +- `public function getError(string $fieldName, $languageId = null): ?string` +- `public function clearPersist(): void` +- `public function saveToPersist(array $data): void` + +--- + +### admin\ViewModels\Forms\FormField +File: `autoload/admin/ViewModels/Forms/FormField.php` +Properties: +- `public string $name` +- `public string $type` +- `public string $label` +- `public $value` +- `public string $tabId` +- `public bool $required` +- `public array $attributes` +- `public array $options` +- `public ?string $helpText` +- `public ?string $placeholder` +- `public ?string $id` +- `public bool $useFilemanager` +- `public ?string $filemanagerUrl` +- `public string $editorToolbar` +- `public int $editorHeight` +- `public ?array $langFields` +- `public ?string $langSectionParentTab` +- `public ?string $customHtml` + +Methods: +- `public function __construct(string $name, string $type = FormFieldType::TEXT, string $label = '', $value = null, string $tabId = 'default', bool $required = false, array $attributes = [], array $options = [], ?string $helpText = null, ?string $placeholder = null, bool $useFilemanager = false, ?string $filemanagerUrl = null, string $editorToolbar = 'MyTool', int $editorHeight = 300, ?array $langFields = null, ?string $langSectionParentTab = null, ?string $customHtml = null)` +- `public static function text(string $name, array $config = []): self` +- `public static function number(string $name, array $config = []): self` +- `public static function email(string $name, array $config = []): self` +- `public static function password(string $name, array $config = []): self` +- `public static function date(string $name, array $config = []): self` +- `public static function datetime(string $name, array $config = []): self` +- `public static function switch(string $name, array $config = []): self` +- `public static function select(string $name, array $config = []): self` +- `public static function textarea(string $name, array $config = []): self` +- `public static function editor(string $name, array $config = []): self` +- `public static function image(string $name, array $config = []): self` +- `public static function file(string $name, array $config = []): self` +- `public static function color(string $name, array $config = []): self` +- `public static function hidden(string $name, $value = null): self` +- `public static function custom(string $name, string $html, array $config = []): self` +- `public static function langSection(string $name, string $parentTab, array $fields): self` +- `public function getLocalizedName($languageId): string` +- `public function getLocalizedId($languageId): string` + +--- + +### admin\ViewModels\Forms\FormFieldType +File: `autoload/admin/ViewModels/Forms/FormFieldType.php` +Properties: (brak) + +Constants: +- `public const TEXT = 'text'` +- `public const NUMBER = 'number'` +- `public const EMAIL = 'email'` +- `public const PASSWORD = 'password'` +- `public const DATE = 'date'` +- `public const DATETIME = 'datetime'` +- `public const SWITCH = 'switch'` +- `public const SELECT = 'select'` +- `public const TEXTAREA = 'textarea'` +- `public const EDITOR = 'editor'` +- `public const IMAGE = 'image'` +- `public const FILE = 'file'` +- `public const HIDDEN = 'hidden'` +- `public const LANG_SECTION = 'lang_section'` +- `public const CUSTOM = 'custom'` +- `public const COLOR = 'color'` + +Methods: (brak — klasa ze stałymi) + +--- + +### admin\ViewModels\Forms\FormTab +File: `autoload/admin/ViewModels/Forms/FormTab.php` +Properties: +- `public string $id` +- `public string $label` +- `public string $icon` +- `public ?string $parentTabId` + +Methods: +- `public function __construct(string $id, string $label, string $icon = '', ?string $parentTabId = null)` + +--- + + +## 3. front/ — Warstwa frontendowa + +### front\App +File: `autoload/front/App.php` +Properties: (brak) + +Methods: +- `static public function pageTitle()` +- `static public function title()` +- `public static function route($product = '', $category = '')` +- `public static function checkUrlParams()` +- `public static function getControllerFactories()` + +--- + +### front\LayoutEngine +File: `autoload/front/LayoutEngine.php` +Properties (regex constants): +- `const menu_pattern = '/MENU:[0-9]*/'` +- `const menu_main_pattern = '/MENU_GLOWNE:[0-9]*/'` +- `const container_pattern = '/KONTENER:[0-9]*/'` +- `const language_pattern = '/LANG:[a-zA-Z0-9_-]*/'` +- `const products_promoted = '/PROMOWANE_PRODUKTY((:([0-9]*))?)/` +- `const news_pattern = '/AKTUALNOSCI:([0-9]*)((:([0-9]*))?)/` +- `const article_products_category_pattern = '/PRODUKTY_KATEGORIA:([0-9]*)((:([0-9]*))?)/` +- `const news_list_pattern = '/AKTUALNOSCI_LISTA:([0-9]*)((:([0-9]*))?)/` +- `const top_news_pattern = '/NAJPOULARNIEJSZE_ARTYKULY:([0-9]*)((:([0-9]*))?)/` +- `const single_product_pattern = '/PRODUKT:[0-9]*/'` +- `const products_box = '/PRODUKTY_BOX:[0-9,]*/'` +- `const produkty_top = '/PRODUKTY_TOP((:([0-9]*))?)/` +- `const produkty_new = '/PRODUKTY_NEW((:([0-9]*))?)/` + +Methods: +- `public static function show()` +- `public static function facebook($facebook_link)` +- `static public function title($title, $show_title, $page_title)` +- `public static function alert()` +- `public static function copyright()` +- `public static function contact()` +- `public static function cookieInformation()` + +--- + +### front\Controllers\NewsletterController +File: `autoload/front/Controllers/NewsletterController.php` +Properties: +- `private NewsletterRepository $repository` + +Methods: +- `public function __construct(NewsletterRepository $repository)` +- `public function signin()` +- `public function confirm()` +- `public function unsubscribe()` + +--- + +### front\Controllers\SearchController +File: `autoload/front/Controllers/SearchController.php` +Properties: (brak) + +Methods: +- `public function searchResults()` +- `public function searchProducts()` + +--- + +### front\Controllers\ShopBasketController +File: `autoload/front/Controllers/ShopBasketController.php` +Properties: +- `public static $title = ['mainView' => 'Koszyk']` +- `private $orderRepository` +- `private $paymentMethodRepository` + +Methods: +- `public function __construct(\Domain\Order\OrderRepository $orderRepository, \Domain\PaymentMethod\PaymentMethodRepository $paymentMethodRepository)` +- `public function basketMessageSave()` +- `public function basketRemoveProduct()` +- `public function basketIncreaseQuantityProduct()` +- `public function basketDecreaseQuantityProduct()` +- `public function basketChangeQuantityProduct()` +- `public function productMessageChange()` +- `public function basketAddProduct()` +- `public function transportMethodInpostCheck()` +- `public function inpostCheck()` +- `public function orlenSave()` +- `public function inpostSave()` +- `public function basketPaymentMethodSet()` +- `public function basketTransportMethodSet()` +- `public function basketPaymentsMethods()` +- `public function summaryView()` +- `public function basketSave()` +- `public function mainView()` +- `private function jsonBasketResponse($basket, $coupon, $lang_id, $basket_transport_method_id)` + +--- + +### front\Controllers\ShopClientController +File: `autoload/front/Controllers/ShopClientController.php` +Properties: +- `private $clientRepo` + +Methods: +- `public function __construct(ClientRepository $clientRepo)` +- `public function markAddressAsCurrent()` +- `public function addressDelete()` +- `public function addressEdit()` +- `public function addressSave()` +- `public function clientAddresses()` +- `public function clientOrders()` +- `public function newPassword()` +- `public function sendEmailPasswordRecovery()` +- `public function recoverPassword()` +- `public function logout()` +- `public function login()` +- `public function confirm()` +- `public function signup()` +- `public function loginForm()` +- `public function registerForm()` +- `private function buildEmailBody(string $templateName, array $replacements = []): string` + +--- + +### front\Controllers\ShopCouponController +File: `autoload/front/Controllers/ShopCouponController.php` +Properties: +- `private CouponRepository $repository` + +Methods: +- `public function __construct(CouponRepository $repository)` +- `public function useCoupon()` +- `public function deleteCoupon()` + +--- + +### front\Controllers\ShopOrderController +File: `autoload/front/Controllers/ShopOrderController.php` +Properties: +- `private $repository` +- `private $adminService` + +Methods: +- `public function __construct(OrderRepository $repository, OrderAdminService $adminService)` +- `public function paymentConfirmation()` +- `public function paymentStatusTpay()` +- `public function paymentStatusPrzelewy24pl()` +- `public function paymentStatusHotpay()` +- `public function orderDetails()` + +--- + +### front\Controllers\ShopProducerController +File: `autoload/front/Controllers/ShopProducerController.php` +Properties: +- `private ProducerRepository $repository` + +Methods: +- `public function __construct(ProducerRepository $repository)` +- `public function products()` +- `public function list()` + +--- + +### front\Controllers\ShopProductController +File: `autoload/front/Controllers/ShopProductController.php` +Properties: +- `private $categoryRepository` + +Methods: +- `public function __construct(\Domain\Category\CategoryRepository $categoryRepository)` +- `public function lazyLoadingProducts()` +- `public function warehouseMessage()` +- `public function drawProductAttributes()` +- `private static function getPermutation($attributes)` +- `private static function getPermutationQuantity($productId, $permutation)` + +--- + +### front\Views\Articles +File: `autoload/front/Views/Articles.php` +Properties: (brak) + +Methods: +- `public static function fullArticle($article)` +- `public static function miniatureArticlesList($articles, $ls, $bs, $page)` +- `public static function entryArticlesList($articles, $ls, $bs, $page)` +- `public static function fullArticlesList($articles, $ls, $bs, $page)` +- `public static function news($page_id, $articles)` +- `public static function newsList($articles)` +- `public static function generateTableOfContents($content)` +- `public static function processHeaders($matches)` +- `public static function generateHeadersIds($text)` +- `public static function getImage($article)` + +--- + +### front\Views\Banners +File: `autoload/front/Views/Banners.php` +Properties: (brak) + +Methods: +- `public static function banners($banners)` +- `public static function mainBanner($banner)` + +--- + +### front\Views\Languages +File: `autoload/front/Views/Languages.php` +Properties: (brak) + +Methods: +- `public static function render($languages)` + +--- + +### front\Views\Menu +File: `autoload/front/Views/Menu.php` +Properties: (brak) + +Methods: +- `public static function pages($pages, $level = 0, $current_page = 0)` +- `public static function menu($menu, $current_page)` + +--- + +### front\Views\Newsletter +File: `autoload/front/Views/Newsletter.php` +Properties: (brak) + +Methods: +- `public static function render()` + +--- + +### front\Views\Scontainers +File: `autoload/front/Views/Scontainers.php` +Properties: (brak) + +Methods: +- `public static function scontainer($scontainer)` + +--- + +### front\Views\ShopCategory +File: `autoload/front/Views/ShopCategory.php` +Properties: (brak) + +Methods: +- `public static function categoryDescription($category): string` +- `public static function categoryView($category, string $langId, int $currentPage = 1): string` +- `public static function categories($categories, $currentCategory = 0, $level = 0): string` + +--- + +### front\Views\ShopClient +File: `autoload/front/Views/ShopClient.php` +Properties: (brak) + +Methods: +- `public static function addressEdit($values): string` +- `public static function clientAddresses($values): string` +- `public static function clientMenu($values): string` +- `public static function clientOrders($values): string` +- `public static function recoverPassword(): string` +- `public static function miniLogin(): string` +- `public static function loginForm($values = ''): string` +- `public static function registerForm(): string` + +--- + +### front\Views\ShopPaymentMethod +File: `autoload/front/Views/ShopPaymentMethod.php` +Properties: (brak) + +Methods: +- `public static function basketPaymentMethods($payment_methods, $payment_id)` + +--- + +### front\Views\ShopProduct +File: `autoload/front/Views/ShopProduct.php` +Properties: (brak) + +Methods: +- `public static function productUrl($product)` + +--- + +### front\Views\ShopSearch +File: `autoload/front/Views/ShopSearch.php` +Properties: (brak) + +Methods: +- `public static function simpleForm()` + +--- + + +## 4. Shared/ — Klasy współdzielone + +### Shared\Cache\CacheHandler +File: `autoload/Shared/Cache/CacheHandler.php` +Properties: +- `protected $redis` + +Methods: +- `public function __construct()` +- `public function get($key)` +- `public function set($key, $value, $ttl = 86400)` +- `public function exists($key)` +- `public function delete($key)` +- `public function deletePattern($pattern)` + +--- + +### Shared\Cache\RedisConnection +File: `autoload/Shared/Cache/RedisConnection.php` +Properties: +- `private static $instance = null` +- `private $redis` + +Methods: +- `private function __construct()` +- `public static function getInstance()` +- `public function getConnection()` + +--- + +### Shared\Email\Email +File: `autoload/Shared/Email/Email.php` +Properties: +- `public $table = 'pp_newsletter_templates'` + +Methods: +- `public function load_by_name(string $name)` +- `public function email_check($email)` +- `public function send(string $email, string $subject, bool $newsletter_headers = false, string $file = null)` + +--- + +### Shared\Helpers\Helpers +File: `autoload/Shared/Helpers/Helpers.php` +Properties: (brak — wszystkie metody statyczne) + +Methods: +- `static function canAddRedirect($from, $to, $lang_id = null)` +- `static public function clear_product_cache(int $product_id)` +- `static public function remove_special_chars($string)` +- `static public function removeDuplicates($array, $param)` +- `static public function generate_webp_image($file, $compression_quality = 85)` +- `public static function is_array_fix($value)` +- `public static function delete_cache()` +- `public static function pretty_date($format, $timestamp = null)` +- `public static function lang($text)` +- `public static function array_cartesian_product($input)` +- `static public function normalize_decimal($val, int $precision = 2)` +- `public static function decimal($val, $precision = 2, $dec_point = ',', $thousands_sep = ' ')` +- `public static function get_new_version()` +- `public static function get_version()` +- `public static function set_session($var, $val)` +- `public static function get_session($var)` +- `public static function delete_session($var)` +- `public static function get($var, $clear = false)` +- `public static function set_message($text)` +- `public static function alert($text)` +- `public static function error($text)` +- `public static function htacces($dir = '../')` +- `public static function seo($val, $delete_rhombs = false)` +- `public static function noPL($string)` +- `public static function delete_dir($dir)` +- `public static function email_check($email)` +- `public static function send_email($email, $subject, $text, $replay = '', $file = '')` +- `public static function shortPrice($price)` +- `public static function isWholeNumber($value)` + +--- + +### Shared\Html\Html +File: `autoload/Shared/Html/Html.php` +Properties: (brak — wszystkie metody statyczne) + +Methods: +- `public static function form_text(array $params = array())` +- `public static function input_switch(array $params = array())` +- `public static function select(array $params = array())` +- `public static function textarea(array $params = array())` +- `public static function input_icon(array $params = array())` +- `public static function input(array $params = array())` +- `public static function button(array $params = array())` +- `public static function panel(array $params = array())` + +--- + +### Shared\Image\ImageManipulator +File: `autoload/Shared/Image/ImageManipulator.php` +Properties: +- `protected int $width` +- `protected int $height` +- `protected $image` +- `protected ?string $file = null` + +Methods: +- `public function __construct(?string $file = null)` +- `public function setImageFile(string $file): self` +- `public function setImageString(string $data): self` +- `public function resample(int $width, int $height, bool $constrainProportions = true): self` +- `public function enlargeCanvas(int $width, int $height, array $rgb = [], ?int $xpos = null, ?int $ypos = null): self` +- `public function crop($x1, int $y1 = 0, int $x2 = 0, int $y2 = 0): self` +- `protected function _replace($res): self` +- `public function save(string $fileName, ?int $type = null): void` +- `public function getResource()` +- `public function getWidth(): int` +- `public function getHeight(): int` +- `public function __destruct()` +- `private function isValidImageResource($image): bool` + +--- + +### Shared\Tpl\Tpl +File: `autoload/Shared/Tpl/Tpl.php` +Properties: +- `protected $vars = array()` + +Methods: +- `public static function view($file, $values = '')` +- `public function secureHTML($val)` +- `public function render($file)` +- `private function renderFile($path)` +- `public function __set($name, $value)` +- `public function __get($name)` + +--- + +## Podsumowanie + +| Warstwa | Pliki | Klasy | Metody (przybliżone) | +|---------|-------|-------|----------------------| +| Domain/ | 29 | 29 | ~435 | +| admin/ | 39 | 39 | ~340 | +| front/ | 21 | 21 | ~85 | +| Shared/ | 7 | 7 | ~63 | +| **Razem** | **96** | **96** | **~923** | + +### Największe klasy (wg liczby metod) +1. `Domain\Product\ProductRepository` — ~88 metod +2. `admin\Controllers\ShopProductController` — ~42 metody +3. `Shared\Helpers\Helpers` — 29 metod +4. `Domain\Attribute\AttributeRepository` — 28 metod +5. `Domain\Category\CategoryRepository` — 21 metod +6. `admin\Support\Forms\FormFieldRenderer` — 21 metod +7. `admin\Controllers\NewsletterController` — 21 metod +8. `Domain\Order\OrderRepository` — 20 metod +9. `admin\Controllers\PagesController` — 20 metod +10. `admin\ViewModels\Forms\FormField` — 19 metod diff --git a/docs/FRONTEND_REFACTORING_PLAN.md b/docs/FRONTEND_REFACTORING_PLAN.md deleted file mode 100644 index 591fb3c..0000000 --- a/docs/FRONTEND_REFACTORING_PLAN.md +++ /dev/null @@ -1,634 +0,0 @@ -# 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) diff --git a/docs/LEGACY_SHOP_REFACTORING_PLAN.md b/docs/LEGACY_SHOP_REFACTORING_PLAN.md deleted file mode 100644 index ae1b4a6..0000000 --- a/docs/LEGACY_SHOP_REFACTORING_PLAN.md +++ /dev/null @@ -1,234 +0,0 @@ -# Plan refaktoryzacji `autoload/shop/` — usunięcie legacy klas - -## Kontekst - -Katalog `autoload/shop/` zawierał **12 legacy klas** (~2 363 linii kodu), które pełniły rolę fasad/wrapperów nad warstwą `Domain\`. Wszystkie klasy zostały usunięte — logika przeniesiona do warstwy `Domain\` / `Shared\`, wywołania zaktualizowane. - -**Status: UKOŃCZONE** (wszystkie 12 klas usunięte, katalog `autoload/shop/` pusty) - -## Status - -| Faza | Klasy | Status | -|------|-------|--------| -| 1 | Transport, ProductSet, Coupon | ✅ Ukończona | -| 2 | Shop, Search, Basket | ✅ Ukończona | -| 3 | ProductCustomField, Category, ProductAttribute | ✅ Ukończona | -| 4 | Promotion | ✅ Ukończona | -| 5 | Order, Product | ✅ Ukończona | - ---- - -## Faza 1 — Trywialne fasady (0 logiki do migracji) - -### 1.1 `class.Transport.php` ✅ (~31 linii) -**Jedyne zewnętrzne użycie:** -- `admin\Controllers\ShopOrderController.php` → `\shop\Transport::transport_list()` - -**Plan:** -- Dodać metodę `allActiveOrdered()` do `Domain\Transport\TransportRepository` (odpowiednik `transport_list()`) -- Zamienić wywołanie w `ShopOrderController` -- Usunąć `class.Transport.php` - -### 1.2 `class.ProductSet.php` ✅ (~49 linii) -**Jedyne zewnętrzne użycie:** -- `admin\Controllers\ShopProductController.php:224` → `\shop\ProductSet::sets_list()` - -**Plan:** -- `ProductSetRepository::allSets()` już istnieje — zamienić wywołanie na `$this->productSetRepo->allSets()` -- Wstrzyknąć `ProductSetRepository` do `ShopProductController` (jeśli jeszcze nie jest) -- Usunąć `class.ProductSet.php` - -### 1.3 `class.Coupon.php` ✅ (~84 linii) -**Zewnętrzne użycia (3×):** -- `shop\class.Order.php:171` → `new \shop\Coupon($id)` (do przeniesienia w fazie Order) -- `front\Controllers\ShopOrderController.php:139` → `new \shop\Coupon($id)` -- `admin\Controllers\ShopOrderController.php:170` → `new \shop\Coupon($id)` - -**Plan:** -- `CouponRepository::findByName()` / `find()` już istnieje — dodać `findById(int $id): ?array` jeśli brak -- Zamienić `new \shop\Coupon($id)` → `$this->couponRepo->findById($id)` w obu kontrolerach -- Użycie w `class.Order.php` — rozwiąże się w fazie Order -- Usunąć `class.Coupon.php` - ---- - -## Faza 2 — Proste wrappery (minimalna logika do przeniesienia) - -### 2.1 `class.Shop.php` ✅ (~39 linii) — utility cenowe -**Zewnętrzne użycia (~24× w szablonach):** -- `templates/shop-product/product.php`, `product-mini.php` -- `templates/shop-search/product-search.php` -- `templates/shop-basket/alert-product-sets.php` -- `templates/controls/alert-product-sets.php` - -**Plan:** -- Przenieść `shortPrice()` i `isWholeNumber()` do `Shared\Helpers\Helpers` jako metody statyczne -- Zamienić `\shop\Shop::shortPrice()` → `\Shared\Helpers\Helpers::shortPrice()` we wszystkich szablonach (replace_all) -- Usunąć `class.Shop.php` - -### 2.2 `class.Search.php` ✅ (~70 linii) -**Jedyne zewnętrzne użycie:** -- `front\LayoutEngine.php:337` → `\shop\Search::simple_form()` - -**Plan:** -- Przenieść `simple_form()` do `front\Views\ShopSearch` (nowa klasa View, statyczna) -- Przenieść `search_results()` i `search_products()` do `front\Controllers\ShopSearchController` lub odpowiedniej istniejącej lokalizacji -- Zamienić wywołanie w `LayoutEngine` -- Usunąć `class.Search.php` -- Poprawić bug: `use shop\Produt;` (literówka — brakuje 'c') - -### 2.3 `class.Basket.php` ✅ (~92 linii) -**Zewnętrzne użycia (6× w `ShopBasketController`):** -- `validate_basket()` — prosta walidacja tablicy sesji -- `check_product_quantity_in_stock()` — sprawdzenie stanów magazynowych - -**Plan:** -- Przenieść obie metody do `Domain\Basket\BasketCalculator` (już istnieje) -- Zamienić wywołania w `ShopBasketController` -- Usunąć `class.Basket.php` - ---- - -## Faza 3 — Klasy z logiką cache/atrybutów - -### 3.1 `class.ProductCustomField.php` ✅ (~74 linii) -**Zewnętrzne użycia (2×):** -- `Domain\Order\OrderRepository.php:643` → `\shop\ProductCustomField::getFromCache()` -- `templates/shop-basket/_partials/product-custom-fields.php:3` → `\shop\ProductCustomField::getFromCache()` - -**Plan:** -- Dodać `findCached(int $id): ?array` do `Domain\Product\ProductRepository` (lub nowy `ProductCustomFieldRepository`) -- Zamienić wywołania w OrderRepository i szablonie -- Usunąć `class.ProductCustomField.php` - -### 3.2 `class.Category.php` ✅ (~84 linii) -**Zewnętrzne użycia (2×):** -- `index.php:225` → `\shop\Category::get_category_products_id()` (FB Pixel) -- `templates/shop-category/category.php:19` → `\shop\Category::get_subcategory_by_category()` - -**Plan:** -- `CategoryRepository::productsId()` prawdopodobnie istnieje — sprawdzić i użyć lub dodać -- Dodać `subcategoriesCached(int $categoryId): array` do `CategoryRepository` -- Zamienić wywołania -- Usunąć `class.Category.php` - -### 3.3 `class.ProductAttribute.php` ✅ (~119 linii) -**Zewnętrzne użycia (3 lokalizacje):** -- `shop\class.Product.php` — użycia wewnętrzne (rozwiążą się w fazie Product) -- `admin/templates/shop-product/product-combination.php:32` → `getAttributeName()`, `get_value_name()` -- `templates/shop-product/_partial/product-attribute.php:13` → `get_value_name()` - -**Plan:** -- `AttributeRepository::getAttributeNameById()` i `getAttributeValueById()` już istnieją -- Dodać brakujące metody do `AttributeRepository`: `isValueDefault()`, `getAttributeOrder()`, `getAttributeNameByValue()` (z Redis cache) -- Zamienić wywołania w szablonach -- Usunąć `class.ProductAttribute.php` - ---- - -## Faza 4 — Klasy z logiką promocji - -### 4.1 `class.Promotion.php` ✅ (~107 linii) -**Zewnętrzne użycia (3 lokalizacje):** -- `front\Controllers\ShopBasketController.php` (6×) → `\shop\Promotion::find_promotion()` -- `admin\Controllers\ShopPromotionController.php` → `::$condition_type`, `::$discount_type` - -**Plan:** -- Przenieść `get_active_promotions()` do `PromotionRepository` (z Redis cache) -- Przenieść `find_promotion()` do `PromotionRepository` — orkiestracja logiki applyType* -- Przenieść stałe `$condition_type` / `$discount_type` jako stałe klasowe do `PromotionRepository` -- Zamienić wywołania w `ShopBasketController` i `ShopPromotionController` -- Usunąć `class.Promotion.php` - ---- - -## Faza 5 — Duże klasy z wieloma zależnościami - -### 5.1 `class.Order.php` ✅ (~562 linii) -**Główna logika do migracji:** -- `OrderAdminService` ma już: `setOrderAsPaid()`, `setOrderAsUnpaid()`, `changeStatus()`, `resendConfirmationEmail()`, `saveOrderByAdmin()`, `deleteOrder()`, `saveNotes()` -- **Brakuje:** logika Apilo sync (`process_apilo_sync_queue()`, `sync_apilo_payment()`, `sync_apilo_status()`, queue management) -- **Brakuje:** `send_status_change_email()` (ale może być zintegrowane w `changeStatus()`) - -**Plan:** -1. Sprawdzić, czy `OrderAdminService` obsługuje już Apilo sync (metoda `sendOrderToApilo()` istnieje) -2. Przenieść `process_apilo_sync_queue()` + kolejkę Apilo do `Domain\Integrations\ApiloService` (lub do `IntegrationsRepository`) -3. Przenieść `send_status_change_email()` do `OrderAdminService` (jeśli nie jest częścią `changeStatus()`) -4. Usunąć referencję do `new \shop\Coupon()` — użyć `CouponRepository` (z fazy 1.3) -5. Zaktualizować `cron.php` (jeśli woła `process_apilo_sync_queue`) -6. Usunąć `class.Order.php` - -### 5.2 `class.Product.php` ✅ (~952 linii) — NAJWIĘKSZA KLASA -**Stan:** Większość logiki zmigowana do `ProductRepository`, ale klasa pełni rolę entity z ArrayAccess (używana w szablonach jako `$product['name']`). - -**Metody do przeniesienia:** -- `getFromCache()` — cache wrappera entity → przenieść do `ProductRepository::findCached()` -- `getDefaultCombinationPrices()` → `ProductRepository` -- `generateSubtitleFromAttributes()` → `ProductRepository` -- `getProductDataBySelectedAttributes()` → `ProductRepository` -- `is_product_on_promotion()` → `ProductRepository` -- `product_sets_when_add_to_basket()` → `ProductSetRepository` -- `add_visit()` → `ProductRepository` -- `getProductImg()` / `getProductUrl()` → `ProductRepository` -- `searchProductsByName()` / `searchProductByNameAjax()` / `searchProductsByNameCount()` → `ProductRepository` -- `is_stock_0_buy()` → `ProductRepository` -- `get_product_permutation_quantity_options()` → `ProductRepository` -- `calculate_basket_product_price()` → `Domain\Basket\BasketCalculator` -- `get_product_id_by_attributes()` / `get_product_permutation_hash()` / `get_product_attributes()` → `ProductRepository` lub `AttributeRepository` -- `array_cartesian()` / `permutations()` → `ProductRepository` (helper prywatny) -- `generate_sku_code()` → `ProductRepository` -- `product_meta()` → `ProductRepository` - -**Kluczowy problem:** szablony używają `$product['field']` przez ArrayAccess. Po migracji szablony będą dostawać zwykłe `array` z `ProductRepository` — to jest kompatybilne, bo `$product['field']` działa tak samo na tablicy. - -**Plan:** -1. Etapami przenosić metody statyczne do `ProductRepository` / `AttributeRepository` -2. Przenieść `calculate_basket_product_price()` do `BasketCalculator` -3. Zamienić `Product::getFromCache()` → `ProductRepository::findCached()` (zwraca tablicę) -4. Zaktualizować szablony i kontrolery -5. Usunąć `class.Product.php` - ---- - -## Pliki do modyfikacji (podsumowanie) - -| Plik | Zmiany | -|------|--------| -| `autoload/Domain/Transport/TransportRepository.php` | + `allActiveOrdered()` | -| `autoload/Domain/Basket/BasketCalculator.php` | + `validateBasket()`, `checkStock()`, `calculateProductPrice()` | -| `autoload/Domain/Promotion/PromotionRepository.php` | + `getActivePromotions()`, `findPromotion()`, stałe | -| `autoload/Domain/Product/ProductRepository.php` | + ~15 metod z class.Product.php | -| `autoload/Domain/Attribute/AttributeRepository.php` | + `isValueDefault()`, `getAttributeOrder()`, `getAttributeNameByValue()` | -| `autoload/Domain/Category/CategoryRepository.php` | + `subcategoriesCached()`, `categoryProductIds()` | -| `autoload/Domain/Order/OrderAdminService.php` | + email statusu, sprawdzenie Apilo | -| `autoload/Domain/Integrations/` | + `ApiloSyncService` (queue Apilo) | -| `autoload/Domain/Coupon/CouponRepository.php` | + `findById()` | -| `autoload/Shared/Helpers/Helpers.php` | + `shortPrice()`, `isWholeNumber()` | -| `autoload/front/Views/ShopSearch.php` | NOWY — `simple_form()` | -| `autoload/front/LayoutEngine.php` | zamiana `\shop\Search` → `\front\Views\ShopSearch` | -| `autoload/front/Controllers/ShopBasketController.php` | zamiana `\shop\Basket`, `\shop\Promotion` | -| `autoload/admin/Controllers/ShopOrderController.php` | zamiana `\shop\Coupon`, `\shop\Transport` | -| `autoload/admin/Controllers/ShopProductController.php` | zamiana `\shop\ProductSet` | -| `autoload/admin/Controllers/ShopPromotionController.php` | zamiana `::$condition_type`, `::$discount_type` | -| `autoload/front/Controllers/ShopOrderController.php` | zamiana `\shop\Coupon` | -| Szablony (`templates/`) | zamiana `\shop\Shop::shortPrice()`, `\shop\ProductAttribute::*`, `\shop\Category::*` | -| `index.php` | zamiana `\shop\Category::get_category_products_id()` | -| `autoload/Domain/Order/OrderRepository.php` | zamiana `\shop\ProductCustomField` | - -## Weryfikacja - -Po każdej fazie: -1. Uruchomić `./test.ps1` — wszystkie 610+ testów muszą przechodzić -2. Grep po `\shop\` w całym codebase — upewnić się, że usunięta klasa nie jest nigdzie używana -3. Po zakończeniu wszystkich faz: `grep -r "\\\\shop\\\\" autoload/ templates/ admin/ index.php ajax.php cron.php api.php` powinien zwracać 0 wyników - -## Szacunek złożoności - -| Faza | Klasy | Linii do usunięcia | Złożoność | -|------|-------|---------------------|-----------| -| 1 | Transport, ProductSet, Coupon | ~164 | Niska | -| 2 | Shop, Search, Basket | ~201 | Niska-średnia | -| 3 | ProductCustomField, Category, ProductAttribute | ~277 | Średnia | -| 4 | Promotion | ~107 | Średnia | -| 5 | Order, Product | ~1 514 | Wysoka | -| **Razem** | **12 klas** | **~2 363** | | diff --git a/docs/MEMORY.md b/docs/MEMORY.md index f48a7da..ae9de30 100644 --- a/docs/MEMORY.md +++ b/docs/MEMORY.md @@ -12,7 +12,6 @@ Notatki i wnioski zebrane podczas pracy z kodem. Aktualizowane na bieżąco. ## Znane problemy / TODO - `\Shared\Helpers\Helpers::send_email()` i `Shared\Email\Email::send()` — zduplikowana logika PHPMailer. Docelowo zunifikować w `Shared\Email\Email` -- `shop\Search` — typo w `use`: `shop\Produt` zamiast `shop\Product` ## Wzorce potwierdzone w projekcie diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index d4c69bd..2322526 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -1,504 +1,173 @@ # Struktura Projektu shopPRO -Dokumentacja struktury projektu shopPRO do szybkiego odniesienia. +Aktualna architektura po zakonczonej migracji na Domain-Driven Design + Dependency Injection. -## System Cache (Redis) +## Warstwa domenowa (`autoload/Domain/`) -### Klasy odpowiedzialne za cache +Kazdy modul zawiera Repository (i opcjonalnie dodatkowe klasy). Konstruktor DI z `$db` (Medoo). Metody sluza zarowno adminowi, jak i frontendowi (wspolna warstwa). -#### RedisConnection (`Shared\Cache\RedisConnection`) -- **Plik:** `autoload/Shared/Cache/RedisConnection.php` -- **Opis:** Singleton zarządzający połączeniem z Redis -- **Metody:** - - `getInstance()` - pobiera instancję połączenia - - `getConnection()` - zwraca obiekt Redis +| Modul | Klasy | Uwagi | +|-------|-------|-------| +| Article | ArticleRepository | blog, aktualnosci, galerie, pliki | +| Attribute | AttributeRepository | cechy produktow + wartosci | +| Banner | BannerRepository | banery glowne + boczne, Redis cache | +| Basket | BasketCalculator | summary, count, walidacja stanow | +| Cache | CacheRepository | czyszczenie cache z poziomu admin | +| Category | CategoryRepository | drzewa kategorii, produkty w kategorii, Redis cache | +| Client | ClientRepository | CRUD, auth, adresy, zamowienia | +| Coupon | CouponRepository | kupony rabatowe, walidacja, uzycie | +| Dashboard | DashboardRepository | statystyki admin, Redis cache | +| Dictionaries | DictionariesRepository | slowniki admin | +| Integrations | IntegrationsRepository | Apilo sync, ustawienia | +| Languages | LanguagesRepository | jezyki, tlumaczenia | +| Layouts | LayoutsRepository | layouty stron, 3-level fallback | +| Newsletter | NewsletterRepository, NewsletterPreviewRenderer | subskrypcje, szablony, kolejka wysylki | +| Order | OrderRepository, OrderAdminService | CRUD, Apilo sync, webhooki platnosci, kolejka retry | +| Pages | PagesRepository | strony, menu, drzewa stron | +| PaymentMethod | PaymentMethodRepository | metody platnosci, mapowanie Apilo | +| Producer | ProducerRepository | producenci | +| Product | ProductRepository | CRUD, cache, kombinacje, zdjecia, Google Feed XML | +| ProductSet | ProductSetRepository | zestawy produktow | +| Promotion | PromotionRepository | promocje, 5 typow applyType*, silnik dopasowania | +| Scontainers | ScontainersRepository | kontenery sidebaru | +| Settings | SettingsRepository | ustawienia sklepu | +| ShopStatus | ShopStatusRepository | statusy zamowien, mapowanie Apilo | +| Transport | TransportRepository | transport, koszty, powiazanie z platnosci | +| Update | UpdateRepository | aktualizacje, migracje SQL | +| User | UserRepository | uzytkownicy admin, 2FA, logowanie | -#### CacheHandler (`Shared\Cache\CacheHandler`) -- **Plik:** `autoload/Shared/Cache/CacheHandler.php` -- **Opis:** Handler do obsługi cache Redis -- **Metody:** - - `get($key)` - pobiera wartość z cache (zwraca zserializowany string, wymaga `unserialize()`) - - `set($key, $value, $ttl = 86400)` - zapisuje wartość do cache (serializuje wewnętrznie) - - `exists($key)` - sprawdza czy klucz istnieje - - `delete($key)` - usuwa pojedynczy klucz - - `deletePattern($pattern)` - usuwa klucze według wzorca +## Warstwa admin (`autoload/admin/`) -#### USUNIĘTA: Cache (legacy file-based) -- ~~`autoload/class.Cache.php`~~ — usunięta w ver. 0.282 -- Zastąpiona przez `CacheHandler` (Redis) we wszystkich wywołaniach +### Router: `admin\App` +- `getControllerFactories()` — mapa kontrolerow z DI wiring +- Brak fallbacku na legacy — wszystkie moduly na nowych kontrolerach -#### Helpers (`Shared\Helpers\Helpers`) -- **Plik:** `autoload/Shared/Helpers/Helpers.php` -- **Metody cache:** - - `clear_product_cache(int $product_id)` - czyści cache konkretnego produktu +### Kontrolery (`admin\Controllers\`) — 28 kontrolerow +ArticlesArchive, Articles, Banner, Dashboard, Dictionaries, Filemanager, Integrations, Languages, Layouts, Newsletter, Pages, ProductArchive, Scontainers, Settings, ShopAttribute, ShopCategory, ShopClients, ShopCoupon, ShopOrder, ShopPaymentMethod, ShopProducer, ShopProduct, ShopProductSets, ShopPromotion, ShopStatuses, ShopTransport, Update, Users -### Wzorce kluczy Redis +### Support +- `admin\Support\TableListRequestFactory` — paginacja/sortowanie tabel +- `admin\Support\Forms\FormRequestHandler` — obsluga formularzy (persist przy bledach) +- `admin\Support\Forms\FormFieldRenderer` — renderowanie pol formularzy -#### Produkty +### ViewModels +- `admin\ViewModels\Forms\` — FormEditViewModel, FormField, FormTab, FormAction, FormFieldType +- `admin\ViewModels\Common\PaginatedTableViewModel` + +### Walidacja +- `admin\Validation\FormValidator` — reguly per pole, sekcje jezykowe + +## Warstwa frontend (`autoload/front/`) + +### Router: `front\App` +- `route()`, `checkUrlParams()`, `getControllerFactories()` + +### Layout Engine: `front\LayoutEngine` +- `show()` — zamiana tagow szablonowych (kategorie, produkty, menu, banery, artykuly, kontenery, meta) +- `contact()`, `cookieInformation()` + +### Kontrolery (`front\Controllers\`) — 8 kontrolerow +Newsletter, Search, ShopBasket, ShopClient, ShopCoupon, ShopOrder, ShopProducer, ShopProduct + +### Widoki (`front\Views\`) — 11 klas statycznych +Articles, Banners, Languages, Menu, Newsletter, Scontainers, ShopCategory, ShopClient, ShopPaymentMethod, ShopProduct, ShopSearch + +## Warstwa wspoldzielona (`autoload/Shared/`) + +| Klasa | Opis | +|-------|------| +| `Shared\Cache\CacheHandler` | Redis cache: get/set/delete/deletePattern | +| `Shared\Cache\RedisConnection` | Singleton polaczenia Redis | +| `Shared\Email\Email` | Wrapper PHPMailer | +| `Shared\Helpers\Helpers` | SEO, email, cache clearing, shortPrice, utility | +| `Shared\Html\Html` | Helpery HTML | +| `Shared\Image\ImageManipulator` | Obrobka obrazow GD | +| `Shared\Tpl\Tpl` | Silnik szablonow: render(), set() | + +## Cache Redis + +### Klucze ``` -shop\product:{product_id}:{lang_id}:{permutation_hash} -``` -- Przechowuje tablicę danych produktu (z kombinacjami, obrazkami, producentem itd.) -- TTL: 24 godziny (86400 sekund) -- Klasa: `Domain\Product\ProductRepository::findCached()` - `autoload/Domain/Product/ProductRepository.php` - -#### Opcje ilościowe produktu -``` -ProductRepository::getProductPermutationQuantityOptions:v2:{product_id}:{permutation} -``` -- Przechowuje informacje o ilości i komunikatach magazynowych -- Klasa: `Domain\Product\ProductRepository::getProductPermutationQuantityOptions()` - `autoload/Domain/Product/ProductRepository.php` - -#### Zestawy produktów -``` -ProductRepository::productSetsWhenAddToBasket:{product_id} -``` -- Przechowuje produkty często kupowane razem -- Klasa: `Domain\Product\ProductRepository::productSetsWhenAddToBasket()` - `autoload/Domain/Product/ProductRepository.php` - -## Integracje z systemami zewnętrznymi (CRON) - -### Plik: `cron.php` - -#### Apilo -- **Aktualizacja pojedynczego produktu:** synchronizacja cen i stanow - - Czestotliwosc: Co 10 minut -- **Synchronizacja cennika:** masowa aktualizacja cen z Apilo - - Czestotliwosc: Co 1 godzine -- **Synchronizacja zaleglych syncow platnosci/statusow:** kolejka retry dla chwilowej niedostepnosci Apilo (`temp/apilo-sync-queue.json`) - - Przetwarzanie: przy kazdym uruchomieniu `cron.php` (limit wsadowy) - -**Uwaga:** Integracje Sellasist i Baselinker zostaly usuniete w ver. 0.263. - -## Panel Administratora - -### Routing -- Główny katalog: `admin/` -- Template główny: `admin/templates/site/main-layout.php` -- Kontrolery (nowe): `autoload/admin/Controllers/` -- Kontrolery legacy (fallback): `autoload/admin/controls/` - -### Przycisk "Wyczyść cache" -- **Lokalizacja UI:** `admin/templates/site/main-layout.php:172` -- **JavaScript:** `admin/templates/site/main-layout.php:235-274` -- **Endpoint AJAX:** `/admin/settings/clear_cache_ajax/` -- **Kontroler:** `autoload/admin/Controllers/SettingsController.php:43-60` -- **Działanie:** - 1. Pokazuje spinner "Czyszczę cache..." - 2. Czyści katalogi: `temp/`, `thumbs/` - 3. Wykonuje `flushAll()` na Redis - 4. Pokazuje "Cache wyczyszczony!" przez 2 sekundy - 5. Przywraca stan początkowy - -## Struktura katalogów - -``` -shopPRO/ -├── admin/ # Panel administratora -│ ├── templates/ # Szablony widoków -│ └── layout/ # Zasoby CSS/JS/ikony -├── autoload/ # Klasy autoloadowane -│ ├── admin/ # Klasy panelu admin -│ │ ├── Controllers/ # Nowe kontrolery DI -│ │ ├── controls/ # Kontrolery legacy (fallback) -│ │ └── factory/ # Fabryki/helpery -│ ├── Domain/ # Repozytoria/logika domenowa -│ ├── Shared/ # Wspoldzielone narzedzia -│ │ ├── Cache/ # CacheHandler, RedisConnection -│ │ ├── Helpers/ # Helpers (ex class.S.php) -│ │ └── Tpl/ # Tpl (silnik szablonow) -│ ├── front/ # Klasy frontendu -│ │ ├── App.php # Router (ex controls/Site) — route(), checkUrlParams(), getControllerFactories() -│ │ ├── LayoutEngine.php # Layout engine (ex view/Site) — show(), contact(), cookieInformation() -│ │ ├── Controllers/ # Kontrolery DI (Newsletter, ShopBasket, ShopClient, ShopCoupon, ShopOrder, ShopProducer, ShopProduct) -│ │ └── Views/ # Widoki (Newsletter, Articles, Languages, Banners, Menu, Scontainers, ShopCategory, ShopClient) -│ └── shop/ # Klasy sklepu -├── docs/ # Dokumentacja techniczna -├── libraries/ # Biblioteki zewnętrzne -├── temp/ # Cache tymczasowy -├── thumbs/ # Miniatury zdjęć -└── cron.php # Zadania CRON +shop\product:{id}:{lang}:{permutation_hash} — dane produktu (TTL 24h) +ProductRepository::getProductPermutationQuantityOptions:v2:{id}:{perm} — ilosc + komunikaty +ProductRepository::productSetsWhenAddToBasket:{id} — zestawy "kupowane razem" ``` -## Baza danych +### Konwencje +- TTL domyslnie 86400 (24h) +- Dane serializowane — `unserialize()` po `get()` +- Czyszczenie: `CacheHandler::deletePattern("shop\\product:{$id}:*")` +- Czyszczenie z poziomu admin: `Shared\Helpers\Helpers::clear_product_cache($id)` +- Przycisk "Wyczysc cache" w admin: `SettingsController::clearCacheAjax()` → `flushAll()` Redis + `temp/` + `thumbs/` -### Główne tabele produktów -- `pp_shop_products` - produkty główne -- `pp_shop_products_langs` - tłumaczenia produktów -- `pp_shop_products_images` - zdjęcia produktów -- `pp_shop_products_categories` - kategorie produktów -- `pp_shop_products_custom_fields` - pola własne produktów +## Entry pointy -### Tabele integracji -- Kolumny w `pp_shop_products`: - - `apilo_product_id`, `apilo_product_name`, `apilo_get_data_date` -- Tabele ustawien: - - `pp_shop_apilo_settings` (key-value) - - `pp_shop_shoppro_settings` (key-value) +| Plik | Rola | +|------|------| +| `index.php` | Frontend — autoload, sesja, DB, routing (`front\App`), layout (`front\LayoutEngine`), DOM post-processing | +| `ajax.php` | Frontend AJAX — koszyk, transport, kontakt | +| `api.php` | REST API (Ekomi CSV) | +| `admin/index.php` | Admin — autoload, sesja, DB, routing (`admin\App`) | +| `admin/ajax.php` | Admin AJAX | +| `cron.php` | CRON: Apilo sync (ceny/stany co 10min, cennik co 1h, retry queue) | +| `cron-turstmate.php` | TrustMate integracja | +| `cron/cron-xml.php` | Google Feed XML | +| `download.php` | Pobieranie plikow | -### Tabele checkout -- `pp_shop_payment_methods` - metody platnosci sklepu (mapowanie `apilo_payment_type_id`) -- `pp_shop_transports` - rodzaje transportu sklepu (mapowanie `apilo_carrier_account_id`) -- `pp_shop_transport_payment_methods` - powiazanie metod transportu i platnosci +### Autoloader +Kazdy entry point rejestruje `__autoload_my_classes()`: +1. Probuje `autoload/{namespace}/class.{ClassName}.php` (legacy format) +2. Probuje `autoload/{namespace}/{ClassName}.php` (PSR-4 format) -Pelna dokumentacja tabel: `DATABASE_STRUCTURE.md` +### Routing frontend (index.php) +Przed `front\App::route()`: +1. Sprawdza tabele `pp_redirects` → 301 redirect +2. Sprawdza tabele `pp_routes` → regex pattern → destination -## Konfiguracja +### Newsletter queue +`index.php` wywoluje `$newsletterRepo->sendQueued()` na koncu kazdego requestu frontendowego (limit 1 mail/request). -### Redis -- Konfiguracja: `config.php` (zmienna `$config['redis']`) -- Parametry: host, port, password +## Integracje zewnetrzne -### Autoload -- Funkcja: `__autoload_my_classes()` w `cron.php:6` -- Wzorzec: `autoload/{namespace}/class.{ClassName}.php` +### Apilo (cron.php) +- Synchronizacja cen/stanow produktow (co 10 min) +- Synchronizacja cennika (co 1h) +- Kolejka retry: `temp/apilo-sync-queue.json` — `OrderAdminService::processApiloSyncQueue()` +- Mapowanie statusow i platnosci przez tabele `pp_shop_statuses` i `pp_shop_payment_methods` -## Klasy pomocnicze +### Webhooki platnosci (front\Controllers\ShopOrderController) +- tPay, Przelewy24, Hotpay — ujednolicone: `set_as_paid` + `update_status` -### \Shared\Helpers\Helpers (autoload/Shared/Helpers/Helpers.php) -Główna klasa helper (przeniesiona z `class.S.php`) z metodami: -- `seo($val)` - generowanie URL SEO -- `normalize_decimal($val, $precision)` - normalizacja liczb -- `send_email()` - wysyłanie emaili -- `delete_dir($dir)` - usuwanie katalogów -- `htacces()` - generowanie .htaccess i sitemap.xml -- `clear_product_cache($id)` - czyszczenie cache produktu +## Biblioteki (`libraries/`) -### Medoo -- Plik: `libraries/medoo/medoo.php` -- Zmienna: `$mdb` -- ORM do operacji na bazie danych +- `medoo/medoo.php` — Medoo ORM (`$mdb`) +- `rb.php` — RedBeanPHP ORM (`\R::`, `$pdo`) +- `phpmailer/` — PHPMailer -## Najważniejsze wzorce +## Wzorce architektoniczne -### Namespace'y -- `\admin\Controllers\` - nowe kontrolery panelu admin (DI) -- `\admin\controls\` - kontrolery legacy (fallback) -- `\Domain\` - repozytoria/logika domenowa -- `\admin\factory\` - helpery/fabryki admin -- ~~`\front\factory\`~~ - USUNIĘTY — wszystkie fabryki zmigrowane do Domain -- ~~`\front\controls\`~~ - USUNIĘTY — router przeniesiony do `\front\App` -- ~~`\front\view\`~~ - USUNIĘTY — layout engine przeniesiony do `\front\LayoutEngine` -- ~~`\shop\`~~ - USUNIĘTY — wszystkie klasy zmigrowane do `\Domain\` - -### Cachowanie produktów +### DI zamiast global ```php -// Pobranie produktu z cache -$product = (new \Domain\Product\ProductRepository($mdb))->findCached($product_id, $lang_id, $permutation_hash); - -// Czyszczenie cache produktu -\Shared\Helpers\Helpers::clear_product_cache($product_id); +// Kontroler wiring (w admin\App lub front\App) +$repo = new \Domain\Example\ExampleRepository($mdb); +$controller = new \admin\Controllers\ExampleController($repo); ``` -## Refaktoryzacja do Domain-Driven Architecture +### Wspolna warstwa Domain +Metody frontendowe (z Redis cache) dodawane do istniejacych repozytoriow — NIE tworzymy osobnych FrontendService/AdminService. -### Nowa struktura (w trakcie migracji) -``` -autoload/ -├── Domain/ # Nowa warstwa biznesowa (namespace \Domain\) -│ ├── Product/ -│ │ └── ProductRepository.php -│ ├── Banner/ -│ │ └── BannerRepository.php -│ ├── Settings/ -│ │ └── SettingsRepository.php -│ ├── Cache/ -│ │ └── CacheRepository.php -│ ├── Article/ -│ │ └── ArticleRepository.php -│ ├── User/ -│ │ └── UserRepository.php -│ ├── Languages/ -│ │ └── LanguagesRepository.php -│ ├── Layouts/ -│ │ └── LayoutsRepository.php -│ ├── Newsletter/ -│ │ ├── NewsletterRepository.php -│ │ └── NewsletterPreviewRenderer.php -│ ├── Scontainers/ -│ │ └── ScontainersRepository.php -│ ├── Dictionaries/ -│ │ └── DictionariesRepository.php -│ ├── Pages/ -│ │ └── PagesRepository.php -│ ├── Integrations/ -│ │ └── IntegrationsRepository.php -│ ├── Promotion/ -│ │ └── PromotionRepository.php -│ ├── Coupon/ -│ │ └── CouponRepository.php -│ ├── ShopStatus/ -│ │ └── ShopStatusRepository.php -│ ├── Transport/ -│ │ └── TransportRepository.php -│ ├── ProductSet/ -│ │ └── ProductSetRepository.php -│ ├── Producer/ -│ │ └── ProducerRepository.php -│ └── ... -├── admin/ -│ ├── Controllers/ # Nowe kontrolery (namespace \admin\Controllers\) -│ ├── class.Site.php # Router: nowy kontroler → fallback stary -│ ├── controls/ # Stare kontrolery (niezależny fallback) -│ ├── factory/ # Stare helpery (niezależny fallback) -│ └── view/ # Widoki (statyczne - bez zmian) -├── front/ -│ ├── App.php # Router (namespace \front\) — route(), checkUrlParams(), getControllerFactories() -│ ├── LayoutEngine.php # Layout engine (namespace \front\) — show(), contact(), cookieInformation() -│ ├── Controllers/ # Kontrolery frontendowe (namespace \front\Controllers\) z DI -│ └── Views/ # Widoki (namespace \front\Views\) — czyste VIEW, statyczne -├── shop/ # Legacy - fasady do Domain -``` +### Klasy View — statyczne, bezstanowe +`front\Views\*` — nie wymagaja DI. Czyste funkcje: dane wchodza, HTML wychodzi. -**Aktualizacja 2026-02-14 (ver. 0.268):** -- Dodano modul domenowy `Domain/PaymentMethod/PaymentMethodRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopPaymentMethodController.php`. -- Modul `/admin/shop_payment_method/*` dziala na nowych widokach (`payment-methods-list`, `payment-method-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopPaymentMethod.php`, `autoload/admin/factory/class.ShopPaymentMethod.php`, `autoload/admin/view/class.ShopPaymentMethod.php`, `admin/templates/shop-payment-method/view-list.php`. +### Kontrolery — instancyjne z DI +`Controllers\*` — repozytoria wstrzykiwane przez konstruktor. -**Aktualizacja 2026-02-14 (ver. 0.269):** -- Dodano modul domenowy `Domain/Transport/TransportRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopTransportController.php`. -- Modul `/admin/shop_transport/*` dziala na nowych widokach (`transports-list`, `transport-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopTransport.php`, `autoload/admin/view/class.ShopTransport.php`, `admin/templates/shop-transport/view-list.php`. -- `admin\factory\ShopTransport` i `front\factory\ShopTransport` przepiete na repozytorium. +### Nazewnictwo plikow +- Nowe: `ClassName.php` +- Legacy (pozostalosci): `class.ClassName.php` +- Autoloader obsluguje oba formaty -**Aktualizacja 2026-02-14 (ver. 0.270):** -- `OrderAdminService` zapisuje nieudane syncy Apilo (status/platnosc) do kolejki `temp/apilo-sync-queue.json`. -- `cron.php` automatycznie ponawia zalegle syncy (`OrderAdminService::processApiloSyncQueue()`). -- `OrderAdminService::setOrderAsPaid()` wysyla mapowany typ platnosci Apilo (z mapowania metody platnosci), bez stalej wartosci `type`. - -**Aktualizacja 2026-02-15 (ver. 0.276):** -- Dodano modul domenowy `Domain/Order/OrderRepository.php`. -- Dodano serwis aplikacyjny `Domain/Order/OrderAdminService.php`. -- Dodano kontroler DI `admin/Controllers/ShopOrderController.php`. -- Modul `/admin/shop_order/*` dziala na nowych widokach (`orders-list`, `order-details`, `order-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopOrder.php`, `autoload/admin/factory/class.ShopOrder.php`, `admin/templates/shop-order/view-list.php`. - -**Aktualizacja 2026-02-15 (ver. 0.277):** -- Dodano globalna wyszukiwarke admin w `admin/templates/site/main-layout.php` (produkty + zamowienia). -- Dodano endpoint AJAX `SettingsController::globalSearchAjax()` w `autoload/admin/Controllers/SettingsController.php`. -- Usunieto fasade `autoload/admin/factory/class.Integrations.php`. -- Wywołania integracji przepiete bezposrednio na `Domain/Integrations/IntegrationsRepository.php`. - -### Routing admin (admin\Site::route()) -1. Sprawdź mapę `$newControllers` → utwórz instancję z DI → wywołaj -2. Jeśli nowy kontroler nie istnieje (`class_exists()` = false) → fallback na `admin\controls\` -3. Stary kontroler jest NIEZALEŻNY od nowych klas (bezpieczny fallback) - -### Dependency Injection -Nowe klasy używają **Dependency Injection** zamiast `global` variables: -```php -// STARE -global $mdb; -$quantity = $mdb->get('pp_shop_products', 'quantity', ['id' => $id]); - -// NOWE -$repository = new \Domain\Product\ProductRepository($mdb); -$quantity = $repository->getQuantity($id); -``` - -## Testowanie (tylko dla deweloperów) - -**UWAGA:** Pliki testów NIE są częścią aktualizacji dla klientów! - -### Narzędzia -- **PHPUnit 9.6.34** - framework testowy -- **test.bat** - uruchamianie testów -- **composer.json** - autoloading PSR-4 - -Pelna dokumentacja testow: `TESTING.md` - -## Dodatkowa aktualizacja 2026-02-14 (ver. 0.271) -- Dodano modul domenowy `Domain/Attribute/AttributeRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopAttributeController.php`. -- Modul `/admin/shop_attribute/*` zostal przepiety na nowe widoki (`attributes-list`, `attribute-edit`, `values-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopAttribute.php`, `autoload/admin/factory/class.ShopAttribute.php`, `autoload/admin/view/class.ShopAttribute.php`, `admin/templates/shop-attribute/_partials/value.php`. -- Przepieto zaleznosci kombinacji produktu na `Domain\Attribute\AttributeRepository` i `shop\ProductAttribute`. -- Dla `ShopAttribute` routing celowo nie wykonuje fallbacku akcji do legacy kontrolera. - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.272) -- Dodano modul domenowy `Domain/ProductSet/ProductSetRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopProductSetsController.php`. -- Modul `/admin/shop_product_sets/*` dziala na nowych widokach (`product-sets-list`, `product-set-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopProductSets.php`, `autoload/admin/factory/class.ShopProductSet.php`, `admin/templates/shop-product-sets/view-list.php`, `admin/templates/shop-product-sets/set-edit.php`. -- `shop\ProductSet` przepiety na fasade do `Domain\ProductSet\ProductSetRepository`. - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.273) -- Dodano modul domenowy `Domain/Producer/ProducerRepository.php`. - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.274) -- Dodano modul domenowy `Domain/Client/ClientRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopClientsController.php`. -- Modul `/admin/shop_clients/*` dziala na nowych widokach opartych o `components/table-list`. -- Usunieto legacy: `autoload/admin/controls/class.ShopClients.php`, `autoload/admin/factory/class.ShopClients.php`. -- Routing i menu admin przepiete na kanoniczny URL `/admin/shop_clients/list/`. -- Dodano kontroler DI `admin/Controllers/ShopProducerController.php`. -- Modul `/admin/shop_producer/*` dziala na nowych widokach (`producers-list`, `producer-edit`). -- Usunieto legacy: `autoload/admin/controls/class.ShopProducer.php`, `admin/templates/shop-producer/list.php`, `admin/templates/shop-producer/edit.php`. -- `shop\Producer` przepiety na fasade do `Domain\Producer\ProducerRepository`. -- `admin\controls\ShopProduct` uzywa `ProducerRepository::allProducers()`. -- Usunieto 6 pustych factory facades: `admin\factory\Languages`, `admin\factory\Newsletter`, `admin\factory\Scontainers`, `admin\factory\ShopProducer`, `admin\factory\ShopTransport`, `admin\factory\Layouts`. -- Przepieto 2 wywolania `admin\factory\ShopTransport` w `admin\factory\ShopProduct` na `Domain\Transport\TransportRepository`. -- Usuniety fallback do `admin\factory\Layouts` w `admin\controls\ShopProduct`. - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.274) -- Dodano kontroler DI `admin/Controllers/ShopProductController.php` (akcje `mass_edit`, `mass_edit_save`, `get_products_by_category`). -- Routing `admin\Site` rozszerzono o mapowanie `ShopProduct` do nowego kontrolera. -- `Domain/Product/ProductRepository.php` rozszerzono o metody dla mass-edit: `allProductsForMassEdit`, `getProductsByCategory`, `applyDiscountPercent`. -- Usunieto legacy akcje mass-edit z `autoload/admin/controls/class.ShopProduct.php`. -- Widok `/admin/shop_product/mass_edit/` przepiety na nowy partial `admin/templates/shop-product/mass-edit-custom-script.php`. -- Ujednolicono UI drzewek (strzalki/expand) w: - - `admin/templates/pages/pages-list.php` + `admin/templates/pages/subpages-list.php` - - `admin/templates/articles/subpages-list.php` + `admin/templates/articles/article-edit-custom-script.php` - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.275) -- Dodano modul domenowy `Domain/Category/CategoryRepository.php`. -- Dodano kontroler DI `admin/Controllers/ShopCategoryController.php`. -- Modul `/admin/shop_category/*` dziala przez DI i kanoniczny URL `/admin/shop_category/list/` (z zachowaniem aliasu `view_list`). -- Widoki `shop-category/*` maja wydzielone skrypty `*-custom-script.php` i ujednolicone strzalki drzewa (`button + caret + aria-expanded`). -- Endpointy AJAX dla drzewka kategorii i kolejnosci produktow przepiete na `/admin/shop_category/save_categories_order/`, `/admin/shop_category/save_products_order/`, `/admin/shop_category/cookie_categories/`. -- Usunieto legacy: `autoload/admin/controls/class.ShopCategory.php`, `autoload/admin/factory/class.ShopCategory.php`, `autoload/admin/view/class.ShopCategory.php`. -- Przepieto zaleznosci `ShopProduct` z `admin\factory\ShopCategory` na `Domain\Category\CategoryRepository`. -- Usunieto preload `autoload/admin/factory/class.ShopCategory.php` z `libraries/grid/config.php`. - -## Dodatkowa aktualizacja 2026-02-15 (ver. 0.277) - ShopProduct (factory) -- `Domain/Product/ProductRepository.php` rozszerzono o ~40 metod: CRUD, save, delete, duplicate, toggleStatus, updatePrice, kombinacje, zdjecia/pliki, Google Feed XML, custom labels. -- `admin/Controllers/ShopProductController.php` rozszerzono o ~30 akcji obslugujacych caly modul produktow. -- Konstruktor kontrolera teraz przyjmuje `ProductRepository` + `IntegrationsRepository`. -- Routing w `admin\Site` zaktualizowany (dodano `IntegrationsRepository`, blokada fallbacku na legacy). -- Przepieto zaleznosci zewnetrzne: `ProductArchiveController`, `order-details.php`, `cron.php`, `cron-xml.php`, `products-list-table.php`, `stock.php`. -- Przepieto endpointy AJAX z `admin/ajax.php` na kontroler: `product_file_delete`, `product_file_name_change`. -- Przepieto `cookie_categories` w widokach product-edit i mass-edit na `/admin/shop_category/cookie_categories/`. -- Usunieto legacy: `autoload/admin/controls/class.ShopProduct.php`, `autoload/admin/factory/class.ShopProduct.php`, `admin/ajax/shop.php`. -- Usunieto `require_once 'ajax/shop.php'` z `admin/ajax.php`. - -## Dodatkowa aktualizacja 2026-02-16 (ver. 0.277) - Dashboard, Update, legacy cleanup, admin\App -- Dodano `Domain/Dashboard/DashboardRepository.php` (7 metod, Redis caching). -- Dodano `admin/Controllers/DashboardController.php` (DI z DashboardRepository + ShopStatusRepository). -- Dodano `Domain/Update/UpdateRepository.php` (update, runPendingMigrations, helper methods). -- Dodano `admin/Controllers/UpdateController.php` (DI z UpdateRepository). -- Przepisano `admin/templates/update/main-view.php` — usunieto `gridEdit`, `$.prompt()`, zastapiono panelami + `$.confirm()`. -- Usunieto `autoload/admin/factory/class.Articles.php` (martwy kod), przeniesiono `articles_by_date_add` do `ArticleRepository`. -- Przepieto `front\factory\Newsletter` na `ArticleRepository::articlesByDateAdd()`. -- Przeniesiono logike z `admin\view\Page::show()` do `admin\App::render()`. -- Przemianowano `admin\Site` na `admin\App` (plik `App.php`). -- Usunieto fallback na `\admin\controls\` w routing (martwy kod). -- Usunieto puste foldery: `autoload/admin/controls/`, `autoload/admin/factory/`, `autoload/admin/view/`. -- Usunieto stary plik `autoload/admin/class.Site.php`. -- Pelna migracja admin zakonczona — wszystkie moduly na Domain + DI + Controllers. - -## Aktualizacja 2026-02-16 (ver. 0.279) - Newsletter + Languages frontend migration -- Usunięta fasada `front\factory\Languages` — wszystkie 26 zależności przepięte bezpośrednio na `Domain\Languages\LanguagesRepository`. -- Usunięta fasada `front\factory\Newsletter` — logika przeniesiona do `Domain\Newsletter\NewsletterRepository` (6 nowych metod frontendowych). -- Usunięty stary kontroler `front\controls\Newsletter` i widok `front\view\Newsletter`. -- Utworzony nowy namespace `front\Controllers\` — pierwszy frontowy kontroler z DI: `NewsletterController`. -- Utworzony nowy namespace `front\Views\` — czyste widoki statyczne: `Languages`, `Newsletter`, `Banners`. -- Zaktualizowany routing w `front\controls\Site::route()` — `getControllerFactories()` (DI) z fallbackiem na stare `front\controls\`. -- Przepięte 4 wywołania `Newsletter::get_template()` w `front\factory\ShopClient` na `NewsletterRepository::templateByName()`. -- FIX: `newsletter_unsubscribe()` — błędna składnia medoo `delete()`. - -## Aktualizacja 2026-02-16 (ver. 0.281) - Banners frontend migration -- NOWE METODY w `Domain/Banner/BannerRepository.php`: `banners()`, `mainBanner()` (Redis cache, filtrowanie dat). -- NOWY: `front\Views\Banners` — czysty VIEW (renderowanie szablonow banner/). -- USUNIETA: `front\factory\Banners` — logika przeniesiona do `BannerRepository`. -- USUNIETA: `front\view\Banners` — zastapiona przez `front\Views\Banners`. -- UPDATE: `front\view\Site::show()` — przepiecie na repo + Views. - -## Aktualizacja 2026-02-16 (ver. 0.282) - Cache cleanup, Shared namespace -- NOWY: `Shared\Cache\CacheHandler` + `Shared\Cache\RedisConnection` — namespace Shared. -- USUNIETA: `class.CacheHandler.php` — wrapper, 60 odwolan przepietych na `\Shared\Cache\CacheHandler`. -- USUNIETA: `class.RedisConnection.php` — wrapper, 12 odwolan przepietych na `\Shared\Cache\RedisConnection`. -- USUNIETA: `class.Cache.php` — legacy file-based cache. -- UPDATE: 6 plikow przepietych z `\Cache::fetch/store` na `CacheHandler` (ShopProduct, ShopPaymentMethod, ShopCategory, ShopTransport, ShopAttribute, DictionariesRepository). - -## Aktualizacja 2026-02-16 - class.S.php migration, Mobile_Detect removal, S cleanup -- USUNIETA: `class.Mobile_Detect.php` — przestarzala detekcja mobilna (UA v2.8.16), zastapiona responsive design. -- USUNIETA: metoda `S::is_mobile()` i 3 warunki mobilne w `front\view\Site` (m_html/m_css/m_js zawsze puste). -- USUNIETE z `LayoutsRepository`: pola `m_html`, `m_css`, `m_js` (save + defaultLayout). -- CLEANUP `class.S.php`: usunieto 12 nieuzywanych metod (set_array_value, parse_name, clear_redis_cache, get_domain, pre_dump, escape, chmod_r, rrmdir, rcopy, pre, json_to_array, is_empty_dir). -- FIX: `array_cartesian_product()` — iteracja po niezdefiniowanej zmiennej `$array` zamiast parametru `$input`. -- PRZENIESIONA: `class.S.php` → `Shared\Helpers\Helpers` (namespace `Shared\Helpers`, klasa `Helpers`). -- ZAMIENIONE: ~140 plikow — `\S::` → `\Shared\Helpers\Helpers::`. -- NOWY: `tests/stubs/Helpers.php` — stub klasy Helpers dla testow. -- USUNIETA: `autoload/class.S.php` — zastapiona przez `Shared\Helpers\Helpers`. - -## Aktualizacja 2026-02-17 (ver. 0.286) - Layouts, Menu, Pages frontend migration -- NOWE METODY w `Domain/Layouts/LayoutsRepository.php`: `categoryDefaultLayoutId()`, `getDefaultLayout()`, `getProductLayout()`, `getArticleLayout()`, `getCategoryLayout()`, `getActiveLayout()`. -- NOWE METODY w `Domain/Pages/PagesRepository.php`: `frontPageDetails()`, `frontPageSort()`, `frontMainPageId()`, `frontLangUrl()`, `frontMenuDetails()`, `frontMenuPages()`. -- NOWY: `front\Views\Menu` — czysty VIEW (`pages()`, `menu()`). -- USUNIETA: `front\factory\class.Layouts.php` — logika przeniesiona do `LayoutsRepository`. -- USUNIETA: `front\factory\class.Menu.php` — logika przeniesiona do `PagesRepository`. -- USUNIETA: `front\factory\class.Pages.php` — logika przeniesiona do `PagesRepository`. -- USUNIETA: `front\view\class.Menu.php` — zastapiona przez `front\Views\Menu`. -- USUNIETA: `templates\menu\submenu.php` — martwy kod. - -## Aktualizacja 2026-02-17 - Tpl namespace, CurlServer removal, thumb.php fix -- NOWY: `autoload/Shared/Tpl/Tpl.php` — silnik szablonow w namespace `Shared\Tpl`. -- USUNIETA: `autoload/class.Tpl.php` — zastapiona przez `Shared\Tpl\Tpl`. -- USUNIETA: `autoload/curl.class.php` — klasa `CurlServer` bez referencji w projekcie. -- ZAMIENIONE: ~135 plikow — `\Tpl::` / `new \Tpl` → `\Shared\Tpl\Tpl::` / `new \Shared\Tpl\Tpl`. -- FIX: `libraries/thumb.php` — require przepiety na `Shared/Image/ImageManipulator.php`, poprawiony short open tag. -- FIX: `Tpl::render()` branch 3 — sprawdzal `../templates_user/` ale ladowal `../templates/`. - -## Aktualizacja 2026-02-17 (ver. 0.289) - ShopCategory + ShopClient frontend migration -- **ShopCategory (frontend)** — migracja factory + view na Domain + Views - - NOWE METODY w `CategoryRepository`: `getCategorySort()`, `categoryName()`, `categoryUrl()`, `frontCategoryDetails()`, `categoriesTree()`, `blogCategoryProducts()`, `categoryProductsCount()`, `productsId()`, `paginatedCategoryProducts()` — z Redis cache, language fallback SQL, stale zamiast magic numbers - - NOWY: `front\Views\ShopCategory` — czysty VIEW (`categoryDescription()`, `categoryView()`, `categories()`) - - USUNIETA: `front\factory\class.ShopCategory.php` — logika przeniesiona do `CategoryRepository` - - USUNIETA: `front\view\class.ShopCategory.php` — zastapiona przez `front\Views\ShopCategory` -- **ShopClient (frontend)** — migracja factory + view + controls na Domain + Views + Controllers - - NOWE METODY w `ClientRepository`: `clientDetails()`, `clientEmail()`, `clientAddresses()`, `addressDetails()`, `addressDelete()`, `addressSave()`, `markAddressAsCurrent()`, `clientOrders()`, `authenticate()`, `createClient()`, `confirmRegistration()`, `generateNewPassword()`, `initiatePasswordRecovery()` - - NOWY: `front\Views\ShopClient` — czysty VIEW (8 metod camelCase) - - NOWY: `front\Controllers\ShopClientController` — instancyjny kontroler z DI (15 metod + `buildEmailBody()` helper) - - USUNIETA: `front\factory\class.ShopClient.php`, `front\view\class.ShopClient.php`, `front\controls\class.ShopClient.php` - - SECURITY FIX: usuniety hardcoded password bypass `'Legia1916'` - - OPTYMALIZACJA: `buildEmailBody()` deduplikuje 4x powtorzony wzorzec budowania emaili z newslettera - - OPTYMALIZACJA: `addressSave()` przyjmuje `array $data` zamiast 6 parametrow - ---- - -## Aktualizacja 2026-02-17 (ver. 0.290) - ShopCoupon + ShopOrder frontend migration -- **ShopCoupon (frontend)** — migracja controls + factory na Domain + Controllers - - NOWE METODY w `CouponRepository`: `findByName()`, `isAvailable()`, `markAsUsed()`, `incrementUsedCount()` - - NOWY: `front\Controllers\ShopCouponController` — instancyjny kontroler z DI (`useCoupon()`, `deleteCoupon()`) - - KONWERSJA: `shop\Coupon` na fasade z dzialajacymi metodami (`is_one_time()`, `set_as_used()`) - - FIX: kupony jednorazowe nigdy nie byly oznaczane jako uzyte - - USUNIETA: `front\controls\class.ShopCoupon.php`, `front\factory\class.ShopCoupon.php` -- **ShopOrder (frontend)** — migracja controls + factory + view na Domain + Controllers - - NOWE METODY w `OrderRepository`: `findIdByHash()`, `findHashById()`, `orderDetailsFrontend()`, `generateOrderNumber()`, `createFromBasket()` (~180 linii logiki basket_save) - - NOWY: `front\Controllers\ShopOrderController` — instancyjny kontroler z DI (`paymentConfirmation()`, `paymentStatusTpay()`, `paymentStatusPrzelewy24pl()`, `paymentStatusHotpay()`, `orderDetails()`) - - POPRAWA: webhooks przelewy24/hotpay — ujednolicone z tpay (set_as_paid + update_status zamiast recznego $mdb->update) - - UPDATE: `ShopBasketController` — DI OrderRepository, zmiana wywolan basket_save/order_hash - - UPDATE: `ClientRepository::clientOrders()`, `shop\Order::order_resend_confirmation_email()`, `cron-turstmate.php` — przepiete na `OrderRepository` - - USUNIETA: `front\controls\class.ShopOrder.php`, `front\factory\class.ShopOrder.php`, `front\view\class.ShopOrder.php` - -## Aktualizacja 2026-02-17 (ver. 0.291) - ShopProducer frontend migration -- NOWA METODA w `ProducerRepository`: `allActiveProducers()` — pełne dane aktywnych producentów -- NOWY: `front\Controllers\ShopProducerController` — instancyjny kontroler z DI (products, list) -- USUNIETA: `front\controls\class.ShopProducer.php` — logika przeniesiona do kontrolera + repo -- USUNIETA: `autoload\shop\class.Producer.php` — fasada niepotrzebna -- UPDATE: `front\view\Site::show()` — przepiecie na `$producerRepo->findForFrontend()` -- UPDATE: `front\controls\Site::getControllerFactories()` — zarejestrowany `ShopProducer` - -## Aktualizacja 2026-02-17 (ver. 0.293) - front\controls\Site + front\view\Site → front\App + front\LayoutEngine -- Przemianowano `front\controls\Site` na `front\App` (plik `App.php`) — router z camelCase metodami. -- Przemianowano `front\view\Site` na `front\LayoutEngine` (plik `LayoutEngine.php`) — layout engine z camelCase metodami. -- Przepiete call sites: `index.php` (3 miejsca), `ajax.php` (1 miejsce). -- Usuniete pliki: `autoload/front/controls/class.Site.php`, `autoload/front/view/class.Site.php`. -- Usuniete puste foldery: `autoload/front/controls/`, `autoload/front/view/`. -- Pelna migracja frontendu zakonczona — struktura `autoload/front/`: `App.php`, `LayoutEngine.php`, `Controllers/`, `Views/`. - -## Aktualizacja 2026-02-17 (ver. 0.292) - ShopProduct + ShopPaymentMethod + ShopPromotion + ShopStatuses + ShopTransport frontend migration -- **Pelna migracja front\factory\** — USUNIETY caly folder `autoload/front/factory/`; 5 ostatnich klas zmigrowanych: - - `front\factory\ShopProduct` (~410 linii) → `ProductRepository` (~20 nowych metod frontendowych) - - `front\factory\ShopPaymentMethod` → `PaymentMethodRepository` (metody frontendowe z Redis cache) - - `front\factory\ShopPromotion` → `PromotionRepository` (5 metod applyType*) - - `front\factory\ShopStatuses` → przepiecie bezposrednio na `ShopStatusRepository` - - `front\factory\ShopTransport` → `TransportRepository` (4 metody frontendowe z Redis cache) -- Usuniete legacy: `front\controls\class.ShopProduct.php`, `front\view\class.ShopPaymentMethod.php`, `front\view\class.ShopTransport.php`, `shop\class.PaymentMethod.php` -- FIX: broken `transports_list()` w ajax.php → nowa metoda `forPaymentMethod()` -- Pelna migracja frontendu zakonczona — `autoload/front/`: `App.php`, `LayoutEngine.php`, `Controllers/`, `Views/` - ---- -*Dokument aktualizowany: 2026-02-17 (ver. 0.293)* +### Nazewnictwo katalogow +- Nowe: z duzej litery (`Views/`, `Controllers/`) +- Namespace `\admin\` z malej (bo katalog `admin/` jest z malej na serwerze Linux) +- NIE uzywac `\Admin\` (duze A) diff --git a/docs/REFACTORING_PLAN.md b/docs/REFACTORING_PLAN.md deleted file mode 100644 index c1c9968..0000000 --- a/docs/REFACTORING_PLAN.md +++ /dev/null @@ -1,308 +0,0 @@ -# Plan Refaktoryzacji shopPRO - Domain-Driven Architecture - -## Cel -Stopniowe przeniesienie logiki biznesowej do architektury warstwowej: -- **Domain/** - logika biznesowa (core) -- **Admin/** - warstwa administratora -- **Frontend/** - warstwa użytkownika -- **Shared/** - współdzielone narzędzia - -## Docelowa struktura - -``` -autoload/ -├── Domain/ # Logika biznesowa (CORE) - namespace \Domain\ -│ ├── Product/ -│ │ ├── ProductRepository.php -│ │ ├── ProductService.php # (przyszłość) -│ │ └── ProductCacheService.php # (przyszłość) -│ ├── Banner/ -│ │ └── BannerRepository.php -│ ├── Settings/ -│ │ └── SettingsRepository.php -│ ├── Cache/ -│ │ └── CacheRepository.php -│ ├── Order/ -│ ├── Category/ -│ └── ... -│ -├── admin/ # Warstwa administratora (istniejący katalog!) -│ ├── Controllers/ # Nowe kontrolery - namespace \admin\Controllers\ -│ ├── controls/ # Stare kontrolery (legacy fallback) -│ ├── factory/ # Stare helpery (legacy) -│ └── view/ # Widoki (statyczne - OK bez zmian) -│ -├── Frontend/ # Warstwa użytkownika (przyszłość) -│ ├── Controllers/ -│ └── Services/ -│ -├── Shared/ # Współdzielone narzędzia -│ ├── Cache/ -│ │ ├── CacheHandler.php -│ │ └── RedisConnection.php -│ └── Helpers/ -│ └── S.php -│ -└── [LEGACY] # Stare klasy (stopniowo deprecated) - ├── shop/ - ├── admin/factory/ - └── front/factory/ -``` - -### WAŻNE: Konwencja namespace → katalog (Linux case-sensitive!) -- `\Domain\` → `autoload/Domain/` (duże D - nowy katalog) -- `\admin\Controllers\` → `autoload/admin/Controllers/` (małe a - istniejący katalog) -- NIE używać `\Admin\` (duże A) bo na serwerze Linux katalog to `admin/` (małe a) - -## Zasady migracji - -### 1. Stopniowość -- Przenosimy **jedną funkcję na raz** -- Zachowujemy kompatybilność wsteczną -- Stare klasy działają jako fasady do nowych - -### 2. Dependency Injection zamiast statycznych metod -```php -// ❌ STARE - statyczne -class Product { - public static function getQuantity($id) { - global $mdb; - return $mdb->get('pp_shop_products', 'quantity', ['id' => $id]); - } -} - -// ✅ NOWE - instancje z DI -class ProductRepository { - private $db; - - public function __construct($db) { - $this->db = $db; - } - - public function getQuantity($id) { - return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]); - } -} -``` - -### 3. Fasady dla kompatybilności -```php -// Stara klasa wywołuje nową -namespace shop; - -class Product { - public static function getQuantity($id) { - global $mdb; - $repo = new \Domain\Product\ProductRepository($mdb); - return $repo->getQuantity($id); - } -} -``` - -## Proces migracji funkcji - -### Krok 1: Wybór funkcji -- Wybierz prostą funkcję statyczną -- Sprawdź jej zależności -- Przeanalizuj gdzie jest używana - -### Krok 2: Stworzenie nowej struktury -- Utwórz folder `Domain/{Module}/` -- Stwórz odpowiednią klasę (Repository/Service/Entity) -- Przenieś logikę - -### Krok 3: Znalezienie użyć -```bash -grep -r "Product::getQuantity" . -``` - -### Krok 4: Aktualizacja wywołań -- Opcja A: Bezpośrednie wywołanie nowej klasy -- Opcja B: Fasada w starej klasie (zalecane na początek) - -### Krok 5: Testy -- Napisz test jednostkowy dla nowej funkcji -- Sprawdź czy stare wywołania działają - -## Status migracji - -### ✅ Zmigrowane moduły -| # | Modul | Wersja | Zakres | -|---|-------|--------|--------| -| 1 | Cache | 0.237, 0.282 | CacheHandler, RedisConnection, clear_product_cache, Shared\Cache namespace, eliminacja class.Cache.php | -| 2 | Product | 0.238-0.252, 0.274, 0.277 | getQuantity, getPrice, getName, archive/unarchive, allProductsForMassEdit, getProductsByCategory, applyDiscountPercent, pelna migracja factory (CRUD, save, delete, duplicate, kombinacje, zdjecia/pliki, Google Feed XML) | -| 3 | Banner | 0.239, 0.281 | find, delete, save, kontroler DI, frontend: banners(), mainBanner() z Redis cache, usuniete fasady front\factory + front\view | -| 4 | Settings | 0.240/0.250 | saveSettings, getSettings, kontroler DI | -| 5 | Dictionaries | 0.251 | listForAdmin, find, save, delete, kontroler DI | -| 6 | ProductArchive | 0.252 | kontroler DI, table-list | -| 7 | Filemanager | 0.252 | kontroler DI, fix Invalid Key | -| 8 | Users | 0.253 | CRUD, logon, 2FA, kontroler DI | -| 9 | Languages | 0.254 | languages + translations, kontroler DI | -| 10 | Layouts | 0.256 | find, save, delete, menusWithPages, categoriesTree | -| 11 | Newsletter | 0.257-0.258 | subskrybenci, szablony, ustawienia | -| 12 | Scontainers | 0.259 | listForAdmin, find, save, delete | -| 13 | ArticlesArchive | 0.260 | restore, deletePermanently | -| 14 | Articles | 0.261 | pelna migracja (CRUD, AJAX, galeria, pliki) | -| 15 | Pages | 0.262 | menu/page CRUD, drzewo stron, AJAX | -| 16 | Integrations | 0.263 | Apilo/ShopPRO, cleanup Sellasist/Baselinker | -| 17 | ShopPromotion | 0.264-0.265 | listForAdmin, find, save, delete, categoriesTree | -| 18 | ShopCoupon | 0.266 | listForAdmin, find, save, delete, categoriesTree | -| 19 | ShopStatuses | 0.267 | listForAdmin, find, save, color picker | -| 20 | ShopPaymentMethod | 0.268 | listForAdmin, find, save, allActive, mapowanie Apilo, DI kontroler | -| 21 | ShopTransport | 0.269 | listForAdmin, find, save, allActive, allForAdmin, findActiveById, getTransportCost, lowestTransportPrice, getApiloCarrierAccountId, powiazanie z PaymentMethod, DI kontroler | -| 22 | ShopAttribute | 0.271 | list/edit/save/delete/values, nowy edytor wartosci, cleanup legacy, przepiecie zaleznosci kombinacji | -| 23 | ShopProductSets | 0.272 | listForAdmin, find, save, delete, allSets, allProductsMap, multi-select Selectize, DI kontroler | -| 24 | ShopProducer | 0.273 | listForAdmin, find, save, delete, allProducers, producerProducts, fasada shop\Producer, DI kontroler | -| 25 | ShopProduct (mass_edit) | 0.274 | DI kontroler + routing dla `mass_edit`, `mass_edit_save`, `get_products_by_category`, cleanup legacy akcji | -| 26 | ShopClients | 0.274 | DI kontroler + routing dla `list/details`, nowe listy na `components/table-list`, cleanup legacy controls/factory | -| 27 | ShopCategory | 0.275 | CategoryRepository + DI kontroler + routing, endpointy AJAX (`save_categories_order`, `save_products_order`, `cookie_categories`), cleanup legacy controls/factory/view | -| 28 | ShopOrder | 0.276 | OrderRepository + OrderAdminService + DI kontroler + routing + nowe widoki (`orders-list`, `order-details`, `order-edit`) + cleanup legacy controls/factory/view-list | -| 29 | ShopProduct (factory) | 0.277 | Pelna migracja factory: ProductRepository (CRUD, save, delete, duplicate, toggleStatus, updatePrice, kombinacje, zdjecia/pliki, Google Feed XML) + DI kontroler (list, edit, save, operacje, kombinacje, zdjecia/pliki) + routing + przepiecie zaleznosci (ProductArchive, order-details, cron, cron-xml, products-list-table, stock) + usunięcie legacy (controls, factory, ajax/shop.php) | -| 30 | Dashboard | 0.277 | DashboardRepository (7 metod, Redis caching) + DashboardController (DI) + cleanup legacy controls/shop | -| 31 | Update | 0.277 | UpdateRepository (update, runPendingMigrations, helper methods) + UpdateController (DI) + przepisany template (panele, $.confirm) + cleanup legacy controls/factory/view | -| 32 | Legacy cleanup | 0.277 | Usunieto admin/factory/Articles (martwy kod), admin/view/Page → App::render(), puste foldery controls/factory/view | -| 33 | admin\App | 0.277 | Rename Site → App, usunieto fallback na controls, uproszczony routing, plik App.php bez przedrostka class. | - -### Product - szczegolowy status -- ✅ getQuantity (ver. 0.238) -- ✅ getPrice (ver. 0.239) -- ✅ getName (ver. 0.239) -- ✅ archive / unarchive (ver. 0.241/0.252) -- ✅ allProductsForMassEdit (ver. 0.274) -- ✅ getProductsByCategory (ver. 0.274) -- ✅ applyDiscountPercent (ver. 0.274) -- ✅ countProducts, listForAdmin, findForAdmin, allProductsList, productCategoriesText, getParentId (ver. 0.277) -- ✅ saveProduct + helpery (ver. 0.277) -- ✅ delete, duplicate, toggleStatus, updatePriceBrutto/Promo, updateCustomLabel (ver. 0.277) -- ✅ getPermutations, generateCombinations, deleteCombination, countCombinations, saveCombination* (ver. 0.277) -- ✅ deleteImage, updateImageAlt, saveImagesOrder, deleteFile, updateFileName, generateGoogleFeedXml, generateEAN (ver. 0.277) -- ✅ updateCombinationPricesFromBase (ver. 0.277) -- [ ] is_product_on_promotion (frontend — osobna migracja) -- [ ] getFromCache (frontend — osobna migracja) -- [ ] getProductImg (frontend — osobna migracja) - -### 📋 Do zrobienia -- Frontend: migracja `front\factory\ShopProduct` - -## Kolejność refaktoryzacji (priorytet) - -1-33: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses, ShopPaymentMethod, ShopTransport, ShopAttribute, ShopProductSets, ShopProducer, ShopProduct (mass_edit), ShopClients, ShopCategory, ShopOrder, ShopProduct (factory), Dashboard, Update, Legacy cleanup, admin\App -34: ✅ Shared\Cache namespace (ver. 0.282) — CacheHandler + RedisConnection → Shared\Cache\, eliminacja class.Cache.php, przepiecie 6 plikow na CacheHandler -35: ✅ Shared\Tpl namespace (ver. 0.285) — Tpl → Shared\Tpl\Tpl, eliminacja class.Tpl.php + curl.class.php, fix thumb.php -36: ✅ ShopProducer frontend (ver. 0.291) — front\controls\ShopProducer + shop\Producer usunięte, front\Controllers\ShopProducerController z DI, allActiveProducers() w ProducerRepository - -## Form Edit System - -Nowy uniwersalny system formularzy edycji: -- ✅ Klasy ViewModel: `FormFieldType`, `FormField`, `FormTab`, `FormAction`, `FormEditViewModel` -- ✅ Walidacja: `FormValidator` z obsługą reguł per pole i sekcje językowe -- ✅ Persist: `FormRequestHandler` - zapamiętywanie danych przy błędzie walidacji -- ✅ Renderer: `FormFieldRenderer` - renderowanie wszystkich typów pól -- ✅ Szablon: `admin/templates/components/form-edit.php` - uniwersalny layout -- Wspierane typy pól: text, number, email, password, date, datetime, switch, select, textarea, editor, image, file, hidden, lang_section, color -- Obsługa zakładek (vertical) i sekcji językowych (horizontal) -- **Do zrobienia**: Przerobić pozostałe kontrolery/formularze (Product, Category, Pages, itd.) - -Pelna dokumentacja: `docs/FORM_EDIT_SYSTEM.md` - -## Zasady kodu - -### 1. SOLID Principles -- **S**ingle Responsibility - jedna klasa = jedna odpowiedzialność -- **O**pen/Closed - otwarty na rozszerzenia, zamknięty na modyfikacje -- **L**iskov Substitution - podklasy mogą zastąpić nadklasy -- **I**nterface Segregation - wiele małych interfejsów -- **D**ependency Inversion - zależności od abstrakcji - -### 2. Nazewnictwo -- **Entity** - `Product.php` (reprezentuje obiekt domenowy) -- **Repository** - `ProductRepository.php` (dostęp do danych) -- **Service** - `ProductService.php` (logika biznesowa) -- **Controller** - `ProductController.php` (obsługa requestów) - -### 3. Type Hinting -```php -// ✅ DOBRE -public function getQuantity(int $id): ?int { - return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]); -} - -// ❌ ZŁE -public function getQuantity($id) { - return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]); -} -``` - -## Narzędzia pomocnicze - -### Autoloader (produkcja) -Autoloader w 9 entry pointach obsługuje dwie konwencje: -1. `autoload/{namespace}/class.{ClassName}.php` (legacy) -2. `autoload/{namespace}/{ClassName}.php` (PSR-4, fallback) - -Entry pointy: `index.php`, `ajax.php`, `api.php`, `cron.php`, `cron-turstmate.php`, `download.php`, `admin/index.php`, `admin/ajax.php`, `cron/cron-xml.php` - -### Static Analysis -```bash -composer require --dev phpstan/phpstan -vendor/bin/phpstan analyse autoload/Domain -``` - -## Testowanie - -### Framework: PHPUnit -```bash -composer test -``` - -### Struktura testów -``` -tests/ -├── Unit/ -│ ├── Domain/ -│ │ ├── Article/ArticleRepositoryTest.php -│ │ ├── Banner/BannerRepositoryTest.php -│ │ ├── Cache/CacheRepositoryTest.php -│ │ ├── Coupon/CouponRepositoryTest.php -│ │ ├── Dictionaries/DictionariesRepositoryTest.php -│ │ ├── Integrations/IntegrationsRepositoryTest.php -│ │ ├── PaymentMethod/PaymentMethodRepositoryTest.php -│ │ ├── Producer/ProducerRepositoryTest.php -│ │ ├── Product/ProductRepositoryTest.php -│ │ ├── ProductSet/ProductSetRepositoryTest.php -│ │ ├── Promotion/PromotionRepositoryTest.php -│ │ ├── Settings/SettingsRepositoryTest.php -│ │ ├── ShopStatus/ShopStatusRepositoryTest.php -│ │ └── User/UserRepositoryTest.php -│ └── admin/ -│ └── Controllers/ -│ ├── ArticlesControllerTest.php -│ ├── DictionariesControllerTest.php -│ ├── IntegrationsControllerTest.php -│ ├── ProductArchiveControllerTest.php -│ ├── SettingsControllerTest.php -│ ├── ShopCouponControllerTest.php -│ ├── ShopPaymentMethodControllerTest.php -│ ├── ShopProducerControllerTest.php -│ ├── ShopProductSetsControllerTest.php -│ ├── ShopPromotionControllerTest.php -│ ├── ShopStatusesControllerTest.php -│ └── UsersControllerTest.php -└── Integration/ -``` -**Lacznie: 454 testow, 1449 asercji** - -Aktualizacja 2026-02-15 (ver. 0.273): -- dodano testy `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` -- dodano testy `tests/Unit/admin/Controllers/ShopProducerControllerTest.php` - -Aktualizacja 2026-02-14 (ver. 0.271): -- dodano testy `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php` -- dodano testy `tests/Unit/admin/Controllers/ShopAttributeControllerTest.php` - -Pelna dokumentacja testow: `TESTING.md` - ---- -*Rozpoczęto: 2025-02-05* -*Ostatnia aktualizacja: 2026-02-16* -*Changelog zmian: `docs/CHANGELOG.md`* diff --git a/docs/SHOP_ATTRIBUTE_REFACTOR_PLAN.md b/docs/SHOP_ATTRIBUTE_REFACTOR_PLAN.md deleted file mode 100644 index 211bce1..0000000 --- a/docs/SHOP_ATTRIBUTE_REFACTOR_PLAN.md +++ /dev/null @@ -1,176 +0,0 @@ -# Plan Refaktoryzacji - ShopAttribute (`/admin/shop_attribute`) - -Data przygotowania: 2026-02-14 -Tryb realizacji: Human In The Loop (HITL) -Status: Zrealizowano kroki 0-6 (2026-02-14) - -## 1. Cel i zakres - -Celem jest pelna migracja modulu `shop_attribute` z legacy (`admin/controls`, `admin/factory`, `admin/view`, `grid/gridEdit`) na: - -- `Domain/*` (repozytorium + logika zapisu), -- `admin/Controllers/*` (DI), -- nowe widoki oparte o `components/table-list` i `components/form-edit`, -- kanoniczny routing (`list`, `edit`, `save`, `delete`, `values`, `values_save`) z kompatybilnoscia aliasow legacy. - -Zakres obejmuje takze przeglad i przepiecie zaleznosci w innych klasach (admin/front/shop), aby usunac twarde powiazanie ze starym modulem. - -## 2. Stan obecny (baseline) - -### Legacy modułu -- `autoload/admin/controls/class.ShopAttribute.php` -- `autoload/admin/factory/class.ShopAttribute.php` -- `autoload/admin/view/class.ShopAttribute.php` -- `admin/templates/shop-attribute/*` (stare `grid` / `gridEdit` + AJAX `attribute_value_tpl`) - -### Zaleznosci poza modulem -- `autoload/admin/controls/class.ShopProduct.php` (lista atrybutow do kombinacji) -- `admin/templates/shop-product/product-combination.php` (nazwy atrybut/wartosc) -- `autoload/admin/factory/class.ShopProduct.php` (m.in. `value_details`, aktualizacja cen kombinacji) -- `autoload/front/factory/class.ShopAttribute.php` i `autoload/shop/class.ProductAttribute.php` (odczyt front/shop) -- `templates/shop-product/_partial/product-attribute.php`, `autoload/front/factory/class.ShopOrder.php` - -### Ryzyka znalezione w aktualnym UI wartosci -- domyslny jezyk w tytule jest hardcoded (`pl`), -- wybor domyslnej wartosci oparty o indeksy wierszy (podatne na bledy po usuwaniu), -- brak walidacji biznesowej (np. wymagane minimum 1 wartosc i 1 nazwa w jezyku domyslnym), -- UX edycji wartosci jest malo czytelny przy duzej liczbie pozycji. - -## 3. Architektura docelowa - -### Nowe klasy -- `autoload/Domain/Attribute/AttributeRepository.php` -- `autoload/admin/Controllers/ShopAttributeController.php` - -### Nowe widoki -- `admin/templates/shop-attribute/attributes-list.php` (nowy `table-list`) -- `admin/templates/shop-attribute/attribute-edit.php` (nowy `form-edit`) -- `admin/templates/shop-attribute/attribute-values-edit.php` (nowy ekran wartosci) -- `admin/templates/shop-attribute/attribute-values-custom-script.php` (logika JS dla wartosci) -- `admin/templates/shop-attribute/_partials/value-row.php` (opcjonalny partial pojedynczego wiersza) - -### Routing -- kanoniczne: - - `/admin/shop_attribute/list/` - - `/admin/shop_attribute/edit/id={id}` - - `/admin/shop_attribute/save/` - - `/admin/shop_attribute/delete/id={id}` - - `/admin/shop_attribute/values/id={id}` - - `/admin/shop_attribute/values_save/id={id}` -- brak aliasow kompatybilnosci legacy (decyzja: URL-e niekanoniczne nie sa utrzymywane) - -## 4. Plan realizacji HITL (krok po kroku) - -## Krok 0 - Freeze i test baseline -Zakres: -- uruchomienie testow referencyjnych (minimum smoke + wskazane pelne), -- zapisanie stanu wyjsciowego i listy plikow modulu. - -Wyjscie: -- potwierdzony baseline testow przed zmianami. - -Punkt akceptacji HITL: -- akceptacja startu implementacji po weryfikacji baseline. - -## Krok 1 - Domain Repository (bez zmian UI) -Zakres: -- utworzenie `AttributeRepository` z metodami admin: - - `listForAdmin()`, `findAttribute()`, `saveAttribute()`, `deleteAttribute()`, - - `findValues()`, `saveValues()`, - - pomocnicze: `getAttributeNameById()`, `getAttributeValueById()`, `getAttributesListForCombinations()`, `valueDetails()`. -- normalizacja danych i bezpieczne parsowanie inputow (`switch`, liczby, tablice ID). -- centralizacja invalidacji cache/temp po zapisach. - -Wyjscie: -- gotowa warstwa domenowa pod kontroler DI. - -Punkt akceptacji HITL: -- review API repozytorium i nazw metod przed podpieciem kontrolera. - -## Krok 2 - Kontroler DI i routing -Zakres: -- dodanie `ShopAttributeController` (akcje list/edit/save/delete/values/valuesSave), -- podpiecie do `admin\Site::$newControllers`, -- ustawienie kanonicznych URL bez aliasow legacy, -- aktualizacja linku menu do `/admin/shop_attribute/list/`. - -Wyjscie: -- modul dziala przez nowy kontroler, bez usuwania legacy w tym kroku. - -Punkt akceptacji HITL: -- potwierdzenie zgodnosci URL i backward compatibility. - -## Krok 3 - Migracja widokow (lista + formularz cechy) -Zakres: -- przepisanie listy na `components/table-list`, -- przepisanie formularza cechy na `components/form-edit`, -- utrzymanie obecnej funkcjonalnosci (status, typ, kolejnosc, nazwy per jezyk). - -Wyjscie: -- brak zaleznosci od `grid`/`gridEdit` w tych ekranach. - -Punkt akceptacji HITL: -- akceptacja UX i danych na liscie oraz formularzu cechy. - -## Krok 4 - Nowy panel edycji wartosci (UX) -Zakres: -- przebudowa `values-edit` na bardziej intuicyjny formularz: - - jeden czytelny widok tabelaryczny (wiersz = wartosc), - - stabilny identyfikator wiersza zamiast indeksu do wyboru wartosci domyslnej, - - walidacja: co najmniej 1 wartosc, nazwa w jezyku domyslnym, jedna domyslna wartosc, - - jasne komunikaty bledow i podsumowanie zmian. -- usuniecie zaleznosci od endpointu `attribute_value_tpl` (lub utrzymanie tylko jako alias fallback). - -Wyjscie: -- nowy edytor wartosci odporny na bledy indeksowania i wygodniejszy dla operatora. - -Punkt akceptacji HITL: -- decyzja biznesowa o finalnym UX (wariant A/B ponizej) i akceptacja wygladu. - -### Warianty UX do decyzji -- Wariant A (rekomendowany): osobny ekran `values`, ale w nowym ukladzie tabelarycznym + walidacje. -- Wariant B: integracja wartosci bezposrednio w `attribute-edit` (mniej klikniec, ale wieksza zlozonosc formularza). - -## Krok 5 - Przepiecie zaleznosci i usuniecie legacy -Zakres: -- przeszukanie i przepiecie wszystkich uzyc `admin\factory\ShopAttribute` w kodzie admina, -- aktualizacja zaleznosci w miejscach zwiazanych z kombinacjami produktu, -- usuniecie starych klas: - - `autoload/admin/controls/class.ShopAttribute.php` - - `autoload/admin/view/class.ShopAttribute.php` - - `autoload/admin/factory/class.ShopAttribute.php` (po przepieciu wszystkich odwolan) -- cleanup starych szablonow nieuzywanych. - -Wyjscie: -- brak runtime zaleznosci od legacy `ShopAttribute`. - -Punkt akceptacji HITL: -- akceptacja listy usuwanych plikow i finalnego cleanupu. - -## Krok 6 - Testy + dokumentacja + release -Zakres: -- nowe testy: - - `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php` - - `tests/Unit/admin/Controllers/ShopAttributeControllerTest.php` -- uruchomienie regresji (co najmniej testy modułowe + docelowo caly suite), -- aktualizacja dokumentacji: - - `docs/DATABASE_STRUCTURE.md` (tabele atrybutow), - - `docs/PROJECT_STRUCTURE.md`, - - `docs/REFACTORING_PLAN.md`, - - `docs/CHANGELOG.md`, - - `docs/TESTING.md`. - -Wyjscie: -- modul gotowy do release, z domknietym testowaniem i dokumentacja. - -Punkt akceptacji HITL: -- finalna akceptacja pakietu zmian przed procedura releasowa. - -## 5. Kryteria akceptacji - -- `shop_attribute` dziala przez `ShopAttributeController` + `AttributeRepository`. -- Lista i formularze nie korzystaja z `grid/gridEdit`. -- Panel wartosci nie opiera domyslnej wartosci na nietrwalych indeksach. -- Stare klasy `controls/view/factory` modulu zostaja usuniete po przepieciu zaleznosci. -- Testy jednostkowe dla nowego repozytorium i kontrolera przechodza. -- Dokumentacja techniczna jest zaktualizowana. diff --git a/docs/SHOP_PAYMENT_METHOD_REFACTOR_PLAN.md b/docs/SHOP_PAYMENT_METHOD_REFACTOR_PLAN.md deleted file mode 100644 index 3aeb6c1..0000000 --- a/docs/SHOP_PAYMENT_METHOD_REFACTOR_PLAN.md +++ /dev/null @@ -1,138 +0,0 @@ -# Plan Refaktoryzacji: shop_payment_method - -Data utworzenia: 2026-02-14 -Status: ZREALIZOWANY (Etapy 1-4 zakonczone: 2026-02-14) - -## 1. Cel - -Pelna migracja modulu `/admin/shop_payment_method/*` do obecnego standardu projektu: -- `Domain/*` dla logiki danych, -- `admin/Controllers/*` z DI dla routingu, -- widoki oparte o `components/table-list` i `components/form-edit`, -- usuniecie legacy klas/podpiecie zaleznosci. - -## 2. Stan obecny (inwentaryzacja) - -Aktualny modul jest legacy i opiera sie o `grid`: -- `autoload/admin/controls/class.ShopPaymentMethod.php` -- `autoload/admin/factory/class.ShopPaymentMethod.php` -- `autoload/admin/view/class.ShopPaymentMethod.php` (pusta) -- `admin/templates/shop-payment-method/view-list.php` (grid + inline edit) -- menu: `admin/templates/site/main-layout.php` -> `/admin/shop_payment_method/view_list/` - -Zaleznosci wykryte poza modulem: -- `autoload/admin/controls/class.ShopTransport.php` korzysta z `admin\\factory\\ShopPaymentMethod::payments_list()` -- `autoload/front/factory/class.ShopPaymentMethod.php` ma bezposrednie zapytania do `pp_shop_payment_methods` -- `autoload/shop/class.PaymentMethod.php` ma bezposrednie zapytania do `pp_shop_payment_methods` -- `cron.php` korzysta z `front\\factory\\ShopPaymentMethod::get_apilo_payment_method_id()` - -## 3. Zakres refaktoru - -W zakresie: -1. Nowe repozytorium domenowe dla metod platnosci. -2. Nowy kontroler admin z DI i routingiem kanonicznym. -3. Nowe widoki listy i edycji (bez legacy `grid`). -4. Przepiecie zaleznosci (`ShopTransport`, `front\\factory\\ShopPaymentMethod`, `shop\\PaymentMethod`) na nowe API. -5. Cleanup legacy klas/plikow zwiazanych z modulem. -6. Testy jednostkowe + aktualizacja dokumentacji. - -Poza zakresem (na ten etap): -1. Refaktoryzacja calego modulu `shop_transport` (zrobimy tylko przepiecie zaleznosci dot. payment methods). -2. Zmiany biznesowe w checkout poza zachowaniem obecnej logiki. - -## 4. Architektura docelowa - -Planowane nowe pliki: -- `autoload/Domain/PaymentMethod/PaymentMethodRepository.php` -- `autoload/admin/Controllers/ShopPaymentMethodController.php` -- `admin/templates/shop-payment-method/payment-methods-list.php` -- `admin/templates/shop-payment-method/payment-method-edit.php` - -Planowane aktualizacje: -- `autoload/admin/class.Site.php` (rejestracja `ShopPaymentMethod` w DI routerze) -- `admin/templates/site/main-layout.php` (kanoniczny URL `/admin/shop_payment_method/list/`) -- `autoload/admin/controls/class.ShopTransport.php` (usuniecie zaleznosci od legacy factory) -- `autoload/front/factory/class.ShopPaymentMethod.php` (fasada delegujaca do Domain repo) -- `autoload/shop/class.PaymentMethod.php` (fasada delegujaca do Domain repo) - -Planowane usuniecia: -- `autoload/admin/controls/class.ShopPaymentMethod.php` -- `autoload/admin/factory/class.ShopPaymentMethod.php` -- `autoload/admin/view/class.ShopPaymentMethod.php` -- `admin/templates/shop-payment-method/view-list.php` - -## 5. Etapy realizacji (Human In The Loop) - -### Etap 1: Domain + testy repozytorium -Zakres: -- utworzenie `PaymentMethodRepository` z metodami: - - `listForAdmin(...)` - - `find(int $id)` - - `save(int $id, array $data)` - - `allActive()` - - `findActiveById(int $id)` - - `isActive(int $id)` - - `getApiloPaymentTypeId(int $id)` - - `forTransport(int $transportId)` -- dodanie testu: `tests/Unit/Domain/PaymentMethod/PaymentMethodRepositoryTest.php` - -Checkpoint: -- STOP i prosba o akceptacje po wdrozeniu etapu 1. - -### Etap 2: Admin Controller + routing + nowe widoki -Zakres: -- nowy `ShopPaymentMethodController` (akcje: `list`, `edit`, `save`) -- migracja listy/edycji na `table-list` + `form-edit` -- podpiecie w `admin\\Site` (DI factory map) -- kompatybilnosc URL: - - kanoniczne: `/admin/shop_payment_method/list|edit|save/` - - aliasy legacy do decyzji po wdrozeniu (proponuje: tymczasowo wlaczyc) -- test kontrolera: `tests/Unit/admin/Controllers/ShopPaymentMethodControllerTest.php` - -Checkpoint: -- STOP i prosba o akceptacje po wdrozeniu etapu 2. - -### Etap 3: Przepiecie zaleznosci miedzymodulowych -Zakres: -- `ShopTransport` pobiera liste platnosci przez nowe repozytorium (bez legacy factory) -- `front\\factory\\ShopPaymentMethod` jako fasada do repozytorium domenowego -- `shop\\PaymentMethod` jako fasada do repozytorium domenowego -- zachowanie dotychczasowych podpisow metod (BC) - -Checkpoint: -- STOP i prosba o akceptacje po wdrozeniu etapu 3. - -### Etap 4: Cleanup + finalne testy + dokumentacja -Zakres: -- usuniecie legacy klas/plikow dla `shop_payment_method` -- uruchomienie testow: - - najpierw targetowane testy PaymentMethod, - - potem caly suite (`composer test` lub `./test.ps1`) -- aktualizacja dokumentacji: - - `docs/DATABASE_STRUCTURE.md` - - `docs/PROJECT_STRUCTURE.md` - - `docs/REFACTORING_PLAN.md` - - `docs/CHANGELOG.md` - - `docs/TESTING.md` - -Checkpoint: -- STOP i prosba o finalna akceptacje przed etapem release (zip/commit/push wg procedury KONIEC PRACY, jesli zlecisz). - -## 6. Ryzyka i kontrola regresji - -1. Ryzyko: utrata kompatybilnosci URL (`view_list`). - Kontrola: tymczasowe aliasy lub redirect + test akcji. - -2. Ryzyko: regresja checkout przy pobieraniu metod platnosci. - Kontrola: zachowanie podpisow metod we `front\\factory\\ShopPaymentMethod` i `shop\\PaymentMethod`, testy repozytorium. - -3. Ryzyko: `ShopTransport` przestanie pokazywac metody platnosci. - Kontrola: jawne przepiecie zaleznosci i test manualny widoku edycji transportu. - -## 7. Kryteria akceptacji - -1. `/admin/shop_payment_method/list/` dziala na nowym kontrolerze i nowym widoku. -2. `/admin/shop_payment_method/edit/id={id}` i zapis dzialaja bez `grid`. -3. Brak zaleznosci od legacy `admin\\controls\\ShopPaymentMethod` i `admin\\factory\\ShopPaymentMethod`. -4. `ShopTransport`, frontend checkout i `cron.php` dzialaja na niezmienionych API publicznych. -5. Nowe testy przechodza, a pelny suite nie ma regresji. diff --git a/docs/TESTING.md b/docs/TESTING.md index 06a2e2a..95be14b 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -1,187 +1,48 @@ -# Testowanie shopPRO +# Testowanie shopPRO ## Szybki start -### Pelny zestaw testow -```bash -composer test -``` - -Alternatywnie (Windows): ```bash +# Pelny suite (PowerShell — rekomendowane) ./test.ps1 -./test.bat -./test-simple.bat -./test-debug.bat -``` -Alternatywnie (Git Bash): -```bash -./test.sh -``` - -### Konkretny plik testowy -```bash +# Konkretny plik ./test.ps1 tests/Unit/Domain/Product/ProductRepositoryTest.php -./test.ps1 tests/Unit/admin/Controllers/ArticlesControllerTest.php -``` -### Konkretny test (`--filter`) -```bash +# Konkretny test ./test.ps1 --filter testGetQuantityReturnsCorrectValue + +# Alternatywne +composer test # standard +./test.bat # testdox (czytelna lista) +./test-simple.bat # kropki +./test-debug.bat # debug +./test.sh # Git Bash ``` -## Aktualny stan suite - -Ostatnio zweryfikowano: 2026-02-18 +## Aktualny stan ```text OK (610 tests, 1817 assertions) ``` -Aktualizacja po usunieciu autoload/shop/ — 12 legacy klas (2026-02-18, ver. 0.294): -```text -Pelny suite: OK (610 tests, 1817 assertions) -Zmodyfikowane testy: PromotionRepositoryTest (cache key \shop\Promotion → PromotionRepository) -Zmodyfikowane testy: ShopOrderControllerTest (zamiana \shop\Coupon → CouponRepository DI) -``` +Zweryfikowano: 2026-02-18 (ver. 0.294) -Aktualizacja po migracji front\controls\Site + front\view\Site (2026-02-17, ver. 0.293): -```text -Pelny suite: OK (610 tests, 1816 assertions) -Brak nowych testow — czysty rename/restructure (klasy statyczne, bez logiki biznesowej) -``` +## Konfiguracja -Aktualizacja po migracji ShopProduct + ShopPaymentMethod + ShopPromotion + ShopStatuses + ShopTransport frontend (2026-02-17, ver. 0.292): -```text -Pelny suite: OK (610 tests, 1816 assertions) -Nowe testy: ProductRepositoryTest (+20: getSkuWithFallback, getEanWithFallback, isProductActiveCached, productCategoriesFront, getWarehouseMessageZero/Nonzero, topProductIds, newProductIds, promotedProductIdsCached, getMinimalPrice, productImageCached, productNameCached, productUrlCached, randomProductIds, productWp) -Nowe testy: PaymentMethodRepositoryTest (+5: paymentMethodsCached, paymentMethodCached, paymentMethodsByTransportCached, forTransportCached) -Nowe testy: PromotionRepositoryTest (+7: applyTypeWholeBasket, applyTypeCategoriesOr, applyTypeCategoryCondition 2 scenariusze, applyTypeCategoriesAnd) -Nowe testy: TransportRepositoryTest (+5: transportCostCached, findActiveByIdCached, findActiveByIdCachedNull, forPaymentMethod, forPaymentMethodEmpty) -Nowy stub: tests/stubs/ShopProduct.php (shop\Product::is_product_on_promotion, get_product_price) -``` - -Aktualizacja po migracji ShopProducer frontend (2026-02-17, ver. 0.291): -```text -Pelny suite: OK (573 tests, 1738 assertions) -Nowe testy: ProducerRepositoryTest (+5: allActiveProducers full/null, findForFrontend invalid/notFound/withLanguage) -Nowe testy: ShopProducerControllerTest (+3: constructorAcceptsRepository, hasMainActionMethods, constructorRequiresProducerRepository) -``` - -Aktualizacja po migracji ShopCoupon + ShopOrder frontend (2026-02-17, ver. 0.290): -```text -Pelny suite: OK (565 tests, 1716 assertions) -Nowe testy: CouponRepositoryTest (+12: findByName 3 scenariusze, isAvailable 5 scenariuszy, markAsUsed 2 scenariusze, incrementUsedCount 2 scenariusze) -Nowe testy: ShopCouponControllerTest (+3: constructorAcceptsRepository, hasMainActionMethods, constructorRequiresCouponRepository) -Nowe testy: OrderRepositoryTest (+10: findIdByHash 3 scenariusze, findHashById 2 scenariusze, orderDetailsFrontend 3 scenariusze, generateOrderNumber 2 scenariusze) -Nowe testy: ShopOrderControllerTest (+3: constructorAcceptsRepository, hasMainActionMethods, constructorRequiresOrderRepository) -``` - -Aktualizacja po migracji ShopCategory + ShopClient frontend (2026-02-17, ver. 0.289): -```text -Pelny suite: OK (537 tests, 1648 assertions) -Nowe testy: CategoryRepositoryTest (+17: getCategorySort, categoryName, categoryUrl, frontCategoryDetails, categoriesTree, blogCategoryProducts, categoryProductsCount, productsId, paginatedCategoryProducts) -Nowe testy: ClientRepositoryTest (+36: clientDetails, clientEmail, clientAddresses, addressDetails, addressDelete, addressSave, markAddressAsCurrent, authenticate 5 scenariuszy, createClient, confirmRegistration, generateNewPassword, initiatePasswordRecovery, clientOrders) -Zaktualizowane: tests/stubs/Helpers.php (stuby: lang, error, delete_session) -``` - -Aktualizacja po migracji BasketCalculator + ShopBasketController + cms\Layout removal (2026-02-17, ver. 0.288): -```text -Pelny suite: OK (484 tests, 1528 assertions) -Nowe testy: BasketCalculatorTest (+8: summaryWp, countProducts, countProductsText — singular/plural/cast) -``` - -Aktualizacja po migracji Scontainers + ShopAttribute frontend (2026-02-17, ver. 0.287): -```text -Pelny suite: OK (476 tests, 1512 assertions) -Nowe testy: ScontainersRepositoryTest (+2: frontScontainerDetails, frontScontainerDetailsFallback) -Nowe testy: AttributeRepositoryTest (+4: frontAttributeDetails, frontAttributeDetailsFallback, frontValueDetails, frontValueDetailsFallback) -``` - -Aktualizacja po migracji Layouts + Menu/Pages frontend (2026-02-17, ver. 0.286): -```text -Pelny suite: OK (470 tests, 1484 assertions) -Nowe testy: LayoutsRepositoryTest (+8: categoryDefaultLayoutId, getDefaultLayout, getProductLayout fallback, getArticleLayout, getCategoryLayout fallback, getActiveLayout, getActiveLayout fallback, getActiveLayout null) -Nowe testy: PagesRepositoryTest (+8: frontPageDetails, frontPageDetailsNull, frontMainPageId, frontMainPageIdFallback, frontPageSort, frontMenuDetails, frontMenuDetailsNull, frontMenuPages) -``` - -Aktualizacja po migracji Banners frontend (2026-02-16, ver. 0.281): -```text -Pelny suite: OK (454 tests, 1449 assertions) -Nowe testy: BannerRepositoryTest (+4: banners flat languages, banners null, mainBanner flat languages, mainBanner null) -``` - -Aktualizacja po migracji Articles frontend (2026-02-16, ver. 0.280): -```text -Pelny suite: OK (450 tests, 1431 assertions) -Nowe testy: ArticleRepositoryTest (+13: articleDetailsFrontend, copyFromFallback, articlesIds, pageArticlesCount, pageArticlesPagination, articleNoindex, news, topArticles, newsListArticles) -Zaktualizowane: tests/bootstrap.php (stub: S::is_array_fix) -``` - -Aktualizacja po migracji Newsletter + Languages frontend (2026-02-16, ver. 0.279): -```text -Pelny suite: OK (437 tests, 1398 assertions) -Nowe testy: NewsletterRepositoryTest (+10: unsubscribe, confirmSubscription, getHashByEmail, removeByEmail, signup, constructorOptionalDeps) -Zaktualizowane: tests/bootstrap.php (stuby: S::email_check, S::get_session, S::set_session) -``` - -Aktualizacja po migracji Settings + Languages frontend (2026-02-16, ver. 0.278): -```text -Pelny suite: OK (427 tests, 1378 assertions) -Nowe testy: SettingsRepositoryTest (+6: allSettings, getSingleValue, bugfix param), LanguagesRepositoryTest (+7: defaultLanguage, activeLanguages, translations) -Zaktualizowane: tests/bootstrap.php (stub CacheHandler: get/set/exists) -``` - -Aktualizacja po migracji Dashboard + Update + legacy cleanup (2026-02-16, ver. 0.277): -```text -Pelny suite: OK (414 tests, 1335 assertions) -Nowe testy: DashboardControllerTest (4), DashboardRepositoryTest (6), UpdateControllerTest (6), UpdateRepositoryTest (6) -``` - -Aktualizacja po stabilizacji ShopOrder / Integrations / Global Search (2026-02-15, ver. 0.277): -```text -Pelny suite: OK (385 tests, 1246 assertions) -SettingsControllerTest: OK (7 tests, 10 assertions) -``` - -Aktualizacja po migracji ShopClients (2026-02-15, ver. 0.274) - testy punktowe: -```text -OK (10 tests, 34 assertions) -``` - -Aktualizacja po migracji ShopCategory (2026-02-15, ver. 0.275) - testy punktowe: -```text -OK (16 tests, 72 assertions) -``` - -Pelny suite po migracji ShopCategory (2026-02-15, ver. 0.275): -```text -OK (377 tests, 1197 assertions) -``` - -Aktualizacja po migracji ShopOrder (2026-02-15, ver. 0.276) - testy punktowe: -```text -OK (8 tests, 49 assertions) -``` - -Nowe testy dodane 2026-02-15: -- `tests/Unit/Domain/Client/ClientRepositoryTest.php` -- `tests/Unit/admin/Controllers/ShopClientsControllerTest.php` -- `tests/Unit/Domain/Category/CategoryRepositoryTest.php` -- `tests/Unit/admin/Controllers/ShopCategoryControllerTest.php` -- `tests/Unit/Domain/Order/OrderRepositoryTest.php` -- `tests/Unit/admin/Controllers/ShopOrderControllerTest.php` +- **PHPUnit 9.6** via `phpunit.phar` +- **Bootstrap:** `tests/bootstrap.php` +- **Config:** `phpunit.xml` ## Struktura testow -```text +``` tests/ |-- bootstrap.php |-- stubs/ -| |-- CacheHandler.php -| |-- Helpers.php -| `-- ShopProduct.php +| |-- CacheHandler.php (inline w bootstrap) +| |-- Helpers.php (Shared\Helpers\Helpers stub) +| `-- ShopProduct.php (shop\Product stub) |-- Unit/ | |-- Domain/ | | |-- Article/ArticleRepositoryTest.php @@ -189,10 +50,14 @@ tests/ | | |-- Banner/BannerRepositoryTest.php | | |-- Basket/BasketCalculatorTest.php | | |-- Cache/CacheRepositoryTest.php -| | |-- Coupon/CouponRepositoryTest.php | | |-- Category/CategoryRepositoryTest.php +| | |-- Coupon/CouponRepositoryTest.php | | |-- Dictionaries/DictionariesRepositoryTest.php | | |-- Integrations/IntegrationsRepositoryTest.php +| | |-- Languages/LanguagesRepositoryTest.php +| | |-- Layouts/LayoutsRepositoryTest.php +| | |-- Newsletter/NewsletterRepositoryTest.php +| | |-- Pages/PagesRepositoryTest.php | | |-- PaymentMethod/PaymentMethodRepositoryTest.php | | |-- Producer/ProducerRepositoryTest.php | | |-- Product/ProductRepositoryTest.php @@ -220,70 +85,17 @@ tests/ | |-- ShopStatusesControllerTest.php | |-- ShopTransportControllerTest.php | `-- UsersControllerTest.php -`-- Integration/ -``` - -## Tryby uruchamiania - -### 1. TestDox (czytelna lista) -```bash -./test.bat -``` -Uruchamia: -```bash -C:\xampp\php\php.exe phpunit.phar --testdox -``` - -### 2. Standard (kropki) -```bash -./test-simple.bat -``` -Uruchamia: -```bash -C:\xampp\php\php.exe phpunit.phar -``` - -### 3. Debug (pelne logowanie) -```bash -./test-debug.bat -``` -Uruchamia: -```bash -C:\xampp\php\php.exe phpunit.phar --debug -``` - -### 4. PowerShell (najbardziej niezawodne) -```bash -./test.ps1 -``` -- najpierw probuje `php` z PATH -- jesli brak, probuje m.in. `C:\xampp\php\php.exe` -- zawsze dodaje `--do-not-cache-result` - -## Interpretacja wynikow - -```text -. = test przeszedl -E = error (blad wykonania) -F = failure (niezgodna asercja) -``` - -Przyklad sukcesu: -```text -................................................................. 65 / 82 ( 79%) -................. 82 / 82 (100%) - -OK (82 tests, 181 assertions) +`-- Integration/ (puste — zarezerwowane) ``` ## Dodawanie nowych testow -1. Dodaj plik w odpowiednim module, np. `tests/Unit/Domain//Test.php`. +1. Plik w `tests/Unit/Domain//Test.php` lub `tests/Unit/admin/Controllers/Test.php`. 2. Rozszerz `PHPUnit\Framework\TestCase`. 3. Nazwy metod zaczynaj od `test`. -4. Trzymaj sie wzorca AAA: Arrange, Act, Assert. +4. Wzorzec AAA: Arrange, Act, Assert. -## Mockowanie (przyklad) +## Mockowanie Medoo ```php $mockDb = $this->createMock(\medoo::class); @@ -295,317 +107,10 @@ $value = $repo->getQuantity(123); $this->assertEquals(42, $value); ``` -## Przydatne informacje +## Bootstrap — stuby -- Konfiguracja PHPUnit: `phpunit.xml` -- Bootstrap testow: `tests/bootstrap.php` -- Dodatkowy opis: `tests/README.md` - -## Aktualizacja suite - -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (119 tests, 256 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/User/UserRepositoryTest.php` (25 testow: CRUD, logon, 2FA verify/send, checkLogin, updateById) -- `tests/Unit/admin/Controllers/UsersControllerTest.php` (12 testow: kontrakty + normalizeUser) - -Aktualizacja po migracji widokow Users (2026-02-12): -```text -OK (120 tests, 262 assertions) -``` - -## Aktualizacja suite (finalizacja Users) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (120 tests, 262 assertions) -``` - -Aktualizacja po migracji Languages (2026-02-12): -```text -OK (130 tests, 301 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php` -- `tests/Unit/admin/Controllers/LanguagesControllerTest.php` - -## Aktualizacja suite (release 0.254) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (130 tests, 301 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php` -- `tests/Unit/admin/Controllers/LanguagesControllerTest.php` - -## Aktualizacja suite (release 0.255) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (130 tests, 303 assertions) -``` - -## Aktualizacja suite (release 0.256) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (141 tests, 336 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Layouts/LayoutsRepositoryTest.php` -- `tests/Unit/admin/Controllers/LayoutsControllerTest.php` - -Zaktualizowane testy 2026-02-12: -- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php` (defaultLanguageId) -- `tests/Unit/admin/Controllers/ArticlesControllerTest.php` (konstruktor + LayoutsRepository) - -## Aktualizacja suite (release 0.257) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (150 tests, 372 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Newsletter/NewsletterRepositoryTest.php` -- `tests/Unit/admin/Controllers/NewsletterControllerTest.php` - -## Aktualizacja suite (release 0.258) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (150 tests, 372 assertions) -``` - -## Aktualizacja suite (release 0.259) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (158 tests, 397 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Scontainers/ScontainersRepositoryTest.php` -- `tests/Unit/admin/Controllers/ScontainersControllerTest.php` - -## Aktualizacja suite (release 0.260) -Ostatnio zweryfikowano: 2026-02-12 - -```text -OK (165 tests, 424 assertions) -``` - -Nowe testy dodane 2026-02-12: -- `tests/Unit/Domain/Article/ArticleRepositoryTest.php` (rozszerzenie o testy `restore`, `deletePermanently`, `listArchivedForAdmin`) -- `tests/Unit/admin/Controllers/ArticlesArchiveControllerTest.php` - -## Aktualizacja suite (release 0.261) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (176 tests, 439 assertions) -``` - -Nowe testy/rozszerzenia 2026-02-13: -- `tests/Unit/Domain/Article/ArticleRepositoryTest.php` (nowe przypadki dla `pagesSummaryForArticles`, `updateImageAlt`, `markFileToDelete`) -- `tests/Unit/admin/Controllers/ArticlesControllerTest.php` (nowe kontrakty dla akcji `imageAltChange`, `fileNameChange`, `imageDelete`, `fileDelete`) - -## Aktualizacja suite (release 0.261) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (178 tests, 443 assertions) -``` - -Nowe testy/rozszerzenia 2026-02-13: -- `tests/Unit/Domain/Article/ArticleRepositoryTest.php` (nowe przypadki dla `saveFilesOrder`) - -## Aktualizacja suite (Pages migration) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (186 tests, 478 assertions) -``` - -Nowe testy dodane 2026-02-13: -- `tests/Unit/Domain/Pages/PagesRepositoryTest.php` -- `tests/Unit/admin/Controllers/PagesControllerTest.php` - -Zaktualizowane testy 2026-02-13: -- `tests/Unit/admin/Controllers/ArticlesControllerTest.php` (konstruktor z `Domain\\Pages\\PagesRepository`) - -## Aktualizacja suite (Integrations refactor, ver. 0.263) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (212 tests, 577 assertions) -``` - -Nowe testy dodane 2026-02-13: -- `tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php` (16 testow: getSettings, getSetting, saveSetting, linkProduct, unlinkProduct, getProductSku, apiloGetAccessToken, invalid provider, settings table mapping) -- `tests/Unit/admin/Controllers/IntegrationsControllerTest.php` (10 testow: kontrakty metod, return types, brak metod sellasist/baselinker) - -Zaktualizowane pliki: -- `tests/bootstrap.php` (dodany stub `S::remove_special_chars()`) - -## Aktualizacja suite (ShopPromotion refactor, ver. 0.264) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (222 tests, 609 assertions) -``` - -Nowe testy dodane 2026-02-13: -- `tests/Unit/Domain/Promotion/PromotionRepositoryTest.php` (6 testow: find default, save insert, delete, whitelist sortowania, drzewo kategorii) -- `tests/Unit/admin/Controllers/ShopPromotionControllerTest.php` (4 testy: kontrakty metod i DI konstruktora) - -## Aktualizacja suite (ShopPromotion fix + date_from, ver. 0.265) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (222 tests, 614 assertions) -``` - -Zmiany testowe 2026-02-13: -- rozszerzenie `tests/Unit/Domain/Promotion/PromotionRepositoryTest.php` o asercje `date_from` - -## Aktualizacja suite (ShopCoupon refactor, ver. 0.266) -Ostatnio zweryfikowano: 2026-02-13 - -```text -OK (235 tests, 682 assertions) -``` - -Nowe testy dodane 2026-02-13: -- `tests/Unit/Domain/Coupon/CouponRepositoryTest.php` (8 testow: find default/normalize, save insert/update, delete, whitelist sortowania, drzewo kategorii) -- `tests/Unit/admin/Controllers/ShopCouponControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, DI konstruktora) - -Ponowna weryfikacja po poprawkach UI (drzewko + checkboxy): 2026-02-13 -- `OK (235 tests, 682 assertions)` - -## Aktualizacja suite (ShopStatuses refactor, ver. 0.267) -Ostatnio zweryfikowano: 2026-02-14 - -```text -OK (254 tests, 736 assertions) -``` - -Nowe testy dodane 2026-02-14: -- `tests/Unit/Domain/ShopStatus/ShopStatusRepositoryTest.php` (9 testow: find z ID=0, find null apilo, save update, save z ID=0, empty apilo sets null, reject negative ID, getApiloStatusId, getByIntegrationStatusId, allStatuses, whitelist sortowania) -- `tests/Unit/admin/Controllers/ShopStatusesControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora) - -## Aktualizacja suite (ShopPaymentMethod refactor, ver. 0.268) -Ostatnio zweryfikowano: 2026-02-14 - -```text -OK (280 tests, 828 assertions) -``` - -Nowe testy dodane 2026-02-14: -- `tests/Unit/Domain/PaymentMethod/PaymentMethodRepositoryTest.php` (14 testow: find invalid/null/normalize, save update/null/non-numeric apilo, listForAdmin whitelist, allActive, allForAdmin, findActiveById, isActive, getApiloPaymentTypeId, forTransport) -- `tests/Unit/admin/Controllers/ShopPaymentMethodControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora) - -## Aktualizacja suite (ShopTransport refactor, ver. 0.269) -Ostatnio zweryfikowano: 2026-02-14 - -```text -OK (300 tests, 895 assertions) -``` - -Nowe testy dodane 2026-02-14: -- `tests/Unit/Domain/Transport/TransportRepositoryTest.php` (14 testow: find invalid/null/normalize/nullables, save insert/update/failure/default reset/switch normalization, listForAdmin whitelist, allActive, getApiloCarrierAccountId, getTransportCost, allForAdmin) -- `tests/Unit/admin/Controllers/ShopTransportControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora z 2 repo) - -## Aktualizacja suite (Apilo sync hardening, ver. 0.270) -Ostatnio zweryfikowano: 2026-02-14 - -```text -OK (300 tests, 895 assertions) -``` - -Zmiany testowe 2026-02-14: -- brak nowych testow; pelna regresja po zmianach sync Apilo (TPAY -> Apilo) przeszla bez bledow - -## Aktualizacja suite (ShopAttribute refactor, ver. 0.271) -Ostatnio zweryfikowano: 2026-02-14 - -```text -OK (312 tests, 948 assertions) -``` - -Nowe testy dodane 2026-02-14: -- `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php` (5 testow: domyslne dane cechy, whitelist sortowania/paginacji, zapis wartosci i domyslnej, usuwanie pustych tlumaczen, jezyk domyslny) -- `tests/Unit/admin/Controllers/ShopAttributeControllerTest.php` (7 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora, walidacja `validateValuesRows`) - -## Aktualizacja suite (ShopProductSets refactor, ver. 0.272) -Ostatnio zweryfikowano: 2026-02-15 - -```text -OK (324 tests, 1000 assertions) -``` - -Nowe testy dodane 2026-02-15: -- `tests/Unit/Domain/ProductSet/ProductSetRepositoryTest.php` (7 testow: find default/normalize, save insert/update, delete invalid, whitelist sortowania/paginacji, allSets) -- `tests/Unit/admin/Controllers/ShopProductSetsControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, return types, DI konstruktora) - -## Aktualizacja suite (ShopProducer refactor, ver. 0.273) -Ostatnio zweryfikowano: 2026-02-15 - -```text -OK (338 tests, 1063 assertions) -``` - -Nowe testy dodane 2026-02-15: -- `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` (9 testow: find default/normalize, save insert/update, delete invalid/success, whitelist sortowania/paginacji, allProducers, producerProducts) -- `tests/Unit/admin/Controllers/ShopProducerControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, return types, DI konstruktora) - -## Aktualizacja suite (ShopProduct mass_edit, ver. 0.274) -Ostatnio zweryfikowano: 2026-02-15 - -```text -OK (351 tests, 1091 assertions) -``` - -Nowe testy dodane 2026-02-15: -- `tests/Unit/Domain/Product/ProductRepositoryTest.php` (rozszerzenie: `allProductsForMassEdit`, `getProductsByCategory`, `applyDiscountPercent`) -- `tests/Unit/admin/Controllers/ShopProductControllerTest.php` (7 testow: kontrakty metod, return types, DI konstruktora) - -## Aktualizacja suite (Layouts + Menu/Pages frontend, ver. 0.286) -Ostatnio zweryfikowano: 2026-02-17 - -```text -OK (470 tests, 1484 assertions) -``` - -Nowe testy dodane 2026-02-17: -- `tests/Unit/Domain/Layouts/LayoutsRepositoryTest.php` (rozszerzenie: +8 testow frontend: categoryDefaultLayoutId, getDefaultLayout, getProductLayout, getArticleLayout, getCategoryLayout, getActiveLayout) -- `tests/Unit/Domain/Pages/PagesRepositoryTest.php` (rozszerzenie: +8 testow frontend: frontPageDetails, frontMainPageId, frontPageSort, frontLangUrl, frontMenuDetails, frontMenuPages) - -## Aktualizacja suite (BasketCalculator + ShopBasketController, ver. 0.288) -Ostatnio zweryfikowano: 2026-02-17 - -```text -OK (484 tests, 1528 assertions) -``` - -Nowe testy dodane 2026-02-17: -- `tests/Unit/Domain/Basket/BasketCalculatorTest.php` (8 testow: summaryWp, summaryWpEmpty, countProducts, countProductsEmpty, countProductsTextSingular, countProductsTextPlural2to4, countProductsTextPlural5Plus, countProductsTextCastsToInt) - -## Aktualizacja suite (Scontainers + ShopAttribute frontend, ver. 0.287) -Ostatnio zweryfikowano: 2026-02-17 - -```text -OK (476 tests, 1512 assertions) -``` - -Nowe testy dodane 2026-02-17: -- `tests/Unit/Domain/Scontainers/ScontainersRepositoryTest.php` (rozszerzenie: +2 testow frontend: frontScontainerDetails, frontScontainerDetailsFallback) -- `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php` (rozszerzenie: +4 testow frontend: frontAttributeDetails, frontAttributeDetailsFallback, frontValueDetails, frontValueDetailsFallback) +`tests/bootstrap.php` rejestruje autoloader i definiuje stuby: +- `Redis`, `RedisConnection` — klasy Redis (aby nie wymagac rozszerzenia) +- `Shared\Cache\CacheHandler` — inline stub z `get()`/`set()`/`exists()`/`delete()`/`deletePattern()` +- `Shared\Helpers\Helpers` — z `tests/stubs/Helpers.php` +- `shop\Product` — z `tests/stubs/ShopProduct.php`