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`