ver. 0.293: Code review fixes — 6 repositories, 16 fixes

- ArticleRepository: SQL injection fix (addslashes→parameterized), DRY refactor topArticles/newsListArticles
- AttributeRepository: dead class_exists('\S') blocking cache/temp clear
- CategoryRepository: dead class_exists('\S') blocking SEO link generation (critical)
- BannerRepository: parameterize $today in SQL + null guard on query()
- BasketCalculator: null guard checkProductQuantityInStock + optional DI params
- PromotionRepository: null guard on $basket (production fatal)
- OrderRepository/ShopBasketController/ajax.php: explicit DI in BasketCalculator callers

614 tests, 1821 assertions (+4 new)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 01:07:39 +01:00
parent 37528c6af9
commit dd39587f95
19 changed files with 297 additions and 218 deletions

View File

@@ -4,7 +4,34 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
---
## ver. 0.294 (2026-02-18) - Usuniecie autoload/shop/ — 12 legacy klas
## ver. 0.293 (2026-02-19) - Code review: fixes ArticleRepository, AttributeRepository, BannerRepository, BasketCalculator, CategoryRepository, PromotionRepository
- **ArticleRepository** (7 fixes):
- FIX: `articlesByDateAdd()` — SQL injection (addslashes→parameterized queries), dodano parametr `$langId`
- FIX: `articleDetailsFrontend()` — uproszczono select()+foreach→get()
- FIX: `articlesIds()`, `pageArticlesCount()` — parametryzacja `$langId`
- FIX: `topArticles()`, `newsListArticles()` — DRY refactor via `fetchArticlesByPage()`, parametryzacja
- **AttributeRepository** (1 fix):
- FIX: `clearTempAndCache()` — martwy `class_exists('\S')` blokował czyszczenie cache/temp
- **CategoryRepository** (2 fixes):
- FIX: `refreshCategoryArtifacts()` — martwy `class_exists('\S')` blokował czyszczenie htaccess/temp
- FIX: `normalizeSeoLink()`**krytyczny bug** — linki SEO kategorii nigdy nie były generowane od usunięcia `\S`
- **BannerRepository** (2 fixes):
- FIX: `banners()`, `mainBanner()` — parametryzacja `$today` w SQL + null guard na `query()`
- **BasketCalculator** (3 fixes):
- FIX: `checkProductQuantityInStock()` — dodano `is_array()` guard (foreach na null→fatal)
- FIX: `summaryPrice()` — dodano opcjonalne DI params `$langId`, `$productRepo` z fallbackiem do globals
- FIX: `calculateBasketProductPrice()` — dodano opcjonalny `$productRepo` z fallbackiem do globals
- **PromotionRepository** (1 fix):
- FIX: `findPromotion()` — null guard na `$basket` (produkcyjny fatal error)
- **OrderRepository** — zaktualizowano callery BasketCalculator (jawne DI zamiast globals), usunięto redundantne tworzenie ProductRepository w pętli
- **ShopBasketController**, **ajax.php** — zaktualizowano callery summaryPrice (jawne `$lang_id`)
- **CLASS_CATALOG.md** — zaktualizowano katalog dla 5 klas (rzeczywiste metody + znaczniki przeglądu)
- Testy: 614 OK, 1821 asercji (+4 nowe testy BasketCalculator)
---
## ver. 0.292 (2026-02-18) - Usuniecie autoload/shop/ — 12 legacy klas
- **Faza 5.1: class.Order.php (~562 linii) USUNIETA**
- Logika Apilo sync przeniesiona do `OrderAdminService::processApiloSyncQueue()`

View File

@@ -17,101 +17,140 @@ Wygenerowano: 2026-02-18
<a id="domain"></a>
## 1. Domain/ — Warstwa domenowa
### Domain\Article\ArticleRepository
### Domain\Article\ArticleRepository ✅ REVIEWED
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`
Public Methods:
- `public function __construct($db)`
- `public function find(int $articleId): ?array` — try/catch na brak kolumny `o` (kompatybilność)
- `public function save(int $articleId, array $data, int $userId): int`
- `public function archive(int $articleId): bool`
- `public function restore(int $articleId): bool`
- `public function deletePermanently(int $articleId): bool`
- `public function listForAdmin(array $filters, string $sortColumn = 'date_add', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array`
- `public function listArchivedForAdmin(array $filters, string $sortColumn = 'date_add', string $sortDir = 'DESC', int $page = 1, int $perPage = 15): array`
- `public function saveGalleryOrder(int $articleId, string $order): bool`
- `public function saveFilesOrder(int $articleId, string $order): bool`
- `public function pagesSummaryForArticles(array $articleIds): array`
- `public function updateImageAlt(int $imageId, string $imageAlt): bool`
- `public function updateFileName(int $fileId, string $fileName): bool`
- `public function markFileToDelete(int $fileId): bool`
- `public function markImageToDelete(int $imageId): bool`
- `public function deleteNonassignedFiles(): void`
- `public function deleteNonassignedImages(): void`
- 🔧 `public function articlesByDateAdd(string $dateStart, string $dateEnd, string $langId = 'pl'): array` — naprawiono SQL injection (addslashes→parameterized), dodano parametr $langId
- 🔧 `public function articleDetailsFrontend(int $articleId, string $langId): ?array` — select+foreach → get() (uproszczono)
- 🔧 `public function articlesIds(int $pageId, string $langId, int $limit, int $sortType, int $from): ?array` — parametryzacja $langId
- 🔧 `public function pageArticlesCount(int $pageId, string $langId): int` — parametryzacja $langId
-`public function pageArticles(array $page, string $langId, int $bs): array`
-`public function news(int $pageId, int $limit, string $langId): ?array`
-`public function articleNoindex(int $articleId, string $langId): bool`
- 🔧 `public function topArticles(int $pageId, int $limit, string $langId): ?array` — parametryzacja $langId + DRY via fetchArticlesByPage()
- 🔧 `public function newsListArticles(int $pageId, int $limit, string $langId): ?array` — parametryzacja $langId + DRY via fetchArticlesByPage()
Private Methods:
-`private function createArticle(array $data, int $userId): int`
-`private function updateArticle(int $articleId, array $data, int $userId): int`
-`private function buildArticleRow(array $data, int $userId, bool $isNew): array`
-`private function buildLangRow($langId, array $data): array`
-`private function applyGalleryOrderIfProvided(int $articleId, array $data): void`
-`private function applyFilesOrderIfProvided(int $articleId, array $data): void`
-`private function saveTranslations(int $articleId, array $data, bool $isNew): void`
-`private function savePages(int $articleId, $pages, bool $isNew): void`
-`private function assignTempFiles(int $articleId): void`
-`private function assignTempImages(int $articleId): void`
-`private function deleteMarkedImages(int $articleId): void`
-`private function deleteMarkedFiles(int $articleId): void`
-`private function maxPageOrder(): int`
-`private function isCheckedValue($value): bool`
-`private function appendDateRangeFilter(array &$where, array &$params, string $column, string $fromKey, string $toKey, array $filters): void`
- 🔧 `private function fetchArticlesByPage(string $cachePrefix, int $pageId, int $limit, string $langId, string $orderBy): ?array` — NOWA, wspólna logika topArticles/newsListArticles
---
### Domain\Attribute\AttributeRepository
### Domain\Attribute\AttributeRepository ✅ REVIEWED
File: `autoload/Domain/Attribute/AttributeRepository.php`
Properties:
- `private $db`
- `private ?string $defaultLangId = null`
- `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`
Public Methods:
- `public function __construct($db)`
- `public function listForAdmin(array $filters, string $sortColumn = 'o', string $sortDir = 'ASC', int $page = 1, int $perPage = 15): array`
- `public function findAttribute(int $attributeId): array`
- `public function saveAttribute(array $data): ?int`
- `public function deleteAttribute(int $attributeId): bool`
- `public function findValues(int $attributeId): array`
- `public function saveValues(int $attributeId, array $payload): bool`
- `public function saveLegacyValues(int $attributeId, array $names, array $values, array $ids, $defaultValue, array $impactOnThePrice): ?int`
- `public function valueDetails(int $valueId): array`
- `public function getAttributeNameById(int $attributeId, ?string $langId = null): string`
- `public function getAttributeValueById(int $valueId, ?string $langId = null): string` — z cache
- `public function getAttributesListForCombinations(): array`
- `public function frontAttributeDetails(int $attributeId, string $langId): array` — z cache
- `public function frontValueDetails(int $valueId, string $langId): array` — z cache
- `public function isValueDefault(int $valueId)`
- `public function getAttributeOrder(int $attributeId)`
- `public function getAttributeNameByValue(int $valueId, string $langId)` — parametryzowane query
Private Methods:
- `private function buildAdminWhere(array $filters): array`
- `private function saveAttributeTranslations(int $attributeId, array $names): void`
- `private function saveValueTranslations(int $valueId, array $translations): void`
- `private function valueBelongsToAttribute(int $valueId, int $attributeId): bool`
- `private function normalizeValueRows(array $rows): array`
- `private function refreshCombinationPricesForValue(int $valueId, ?string $impactOnThePrice): void`
- `private function toSwitchValue($value): int`
- `private function toTypeValue($value): int`
-`private function toNullableNumeric($value): ?string`
-`private function defaultAttribute(): array`
-`private function nextOrder(): int`
-`private function defaultLanguageId(): string`
-`private function clearFrontCache(int $id, string $type): void`
- 🔧 `private function clearTempAndCache(): void` — usunięto martwy check `class_exists('\S')`
-`private function normalizeDecimal(float $value, int $precision = 2): float`
---
### Domain\Banner\BannerRepository
### Domain\Banner\BannerRepository ✅ REVIEWED
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`
Public Methods:
- `public function __construct($db)`
- `public function find(int $bannerId): ?array` — pobiera baner + tłumaczenia
- `public function delete(int $bannerId): bool`
- `public function save(array $data)` — insert/update, obsługuje nowy i legacy format
- `public function listForAdmin(array $filters, string $sortColumn = 'name', string $sortDir = 'ASC', int $page = 1, int $perPage = 15): array` — z thumbnailami
- 🔧 `public function banners(string $langId): ?array` — parametryzacja $today + null guard na query()
- 🔧 `public function mainBanner(string $langId): ?array` — parametryzacja $today + null guard na query()
Private Methods:
-`private function fetchThumbnailsByBannerIds(array $bannerIds): array` — parametryzowane IN
-`private function saveTranslations(int $bannerId, array $src, array $url, array $html, array $text): void` — legacy format
-`private function saveTranslationsFromArray(int $bannerId, array $translations): void` — nowy format
-`private function upsertTranslation(int $bannerId, $langId, array $fields): void` — count+insert/update
---
### Domain\Basket\BasketCalculator
### Domain\Basket\BasketCalculator ✅ REVIEWED
File: `autoload/Domain/Basket/BasketCalculator.php`
Properties: (brak)
Properties: (brak — klasa statyczna)
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`
- `public static function summaryWp($basket)` — suma wag, is_array guard
- `public static function countProductsText($count)` — polska pluralizacja
- 🔧 `public static function summaryPrice($basket, $coupon = null, $langId = null, $productRepo = null)` — dodano opcjonalne DI params z fallbackiem do globals
- `public static function countProducts($basket)` — suma ilości, is_array guard
- `public static function validateBasket($basket)` — null guard
- 🔧 `public static function checkProductQuantityInStock($basket, bool $message = false)` — dodano is_array guard (bug: foreach na null)
- 🔧 `public static function calculateBasketProductPrice(float $price_brutto_promo, float $price_brutto, $coupon, $basket_position, $productRepo = null)` — dodano opcjonalny $productRepo z fallbackiem do globals
---

View File

@@ -23,10 +23,10 @@ composer test # standard
## Aktualny stan
```text
OK (610 tests, 1817 assertions)
OK (614 tests, 1821 assertions)
```
Zweryfikowano: 2026-02-18 (ver. 0.294)
Zweryfikowano: 2026-02-19 (ver. 0.293)
## Konfiguracja

View File

@@ -18,16 +18,16 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią
## Procedura tworzenia nowej aktualizacji
## Status biezacej aktualizacji (ver. 0.292)
## Status biezacej aktualizacji (ver. 0.293)
- Wersja udostepniona: `0.292` (data: 2026-02-18).
- Wersja udostepniona: `0.293` (data: 2026-02-19).
- Pliki publikacyjne:
- `updates/0.20/ver_0.292.zip`, `ver_0.292_files.txt`
- `updates/0.20/ver_0.293.zip`
- Pliki metadanych aktualizacji:
- `updates/changelog.php` (skonsolidowany wpis `ver. 0.292` z 0.292+0.293+0.294)
- `updates/versions.php` (`$current_ver = 292`)
- `updates/changelog.php`
- `updates/versions.php` (`$current_ver = 293`)
- Weryfikacja testow przed publikacja:
- `OK (610 tests, 1817 assertions)`
- `OK (614 tests, 1821 assertions)`
### 1. Określ numer wersji
Sprawdź ostatnią wersję w `updates/` i zwiększ o 1.