diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1327049..5df9ccd 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,12 @@ "permissions": { "allow": [ "Bash(powershell -Command \"Compress-Archive -Path ''*'' -DestinationPath ''../ver_0.234.zip'' -Force\")", - "Bash(powershell -Command:*)" + "Bash(powershell -Command:*)", + "Bash(ls -la \"c:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\updates\\\\0.20\"\" | grep \"ver_ \")", + "Bash(C:/xampp/php/php.exe:*)", + "Bash(where:*)", + "Bash(composer:*)", + "Bash(curl:*)" ] } } diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 0000000..29b4f79 --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":1,"defects":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.003,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0.001,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0}} \ No newline at end of file diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md new file mode 100644 index 0000000..e699bf4 --- /dev/null +++ b/PROJECT_STRUCTURE.md @@ -0,0 +1,239 @@ +# Struktura Projektu shopPRO + +Dokumentacja struktury projektu shopPRO do szybkiego odniesienia. + +## System Cache (Redis) + +### Klasy odpowiedzialne za cache + +#### RedisConnection +- **Plik:** `autoload/class.RedisConnection.php` +- **Opis:** Singleton zarządzający połączeniem z Redis +- **Metody:** + - `getInstance()` - pobiera instancję połączenia + - `getConnection()` - zwraca obiekt Redis + +#### CacheHandler +- **Plik:** `autoload/class.CacheHandler.php` +- **Opis:** Handler do obsługi cache Redis +- **Metody:** + - `get($key)` - pobiera wartość z cache + - `set($key, $value, $ttl = 86400)` - zapisuje wartość do cache + - `exists($key)` - sprawdza czy klucz istnieje + - `delete($key)` - usuwa pojedynczy klucz + - `deletePattern($pattern)` - usuwa klucze według wzorca + +#### Klasa S (pomocnicza) +- **Plik:** `autoload/class.S.php` +- **Metody cache:** + - `clear_redis_cache()` - czyści cały cache Redis (flushAll) + - `clear_product_cache(int $product_id)` - czyści cache konkretnego produktu + +### Wzorce kluczy Redis + +#### Produkty +``` +shop\product:{product_id}:{lang_id}:{permutation_hash} +``` +- Przechowuje zserializowany obiekt produktu +- TTL: 24 godziny (86400 sekund) +- Klasa: `shop\Product::getFromCache()` - `autoload/shop/class.Product.php:121` + +#### Opcje ilościowe produktu +``` +\shop\Product::get_product_permutation_quantity_options:{product_id}:{permutation} +``` +- Przechowuje informacje o ilości i komunikatach magazynowych +- Klasa: `shop\Product::get_product_permutation_quantity_options()` - `autoload/shop/class.Product.php:549` + +#### Zestawy produktów +``` +\shop\Product::product_sets_when_add_to_basket:{product_id} +``` +- Przechowuje produkty często kupowane razem +- Klasa: `shop\Product::product_sets_when_add_to_basket()` - `autoload/shop/class.Product.php:316` + +## Integracje z systemami zewnętrznymi (CRON) + +### Plik: `cron.php` + +#### Sellasist +- **Aktualizacja produktów:** Linia 111-149 +- **Funkcje:** Aktualizacja cen i stanów magazynowych +- **Częstotliwość:** Co 10 minut +- **Czyszczenie cache:** Linia 146 + +#### Apilo +- **Aktualizacja pojedynczego produktu:** Linia 152-176 + - Częstotliwość: Co 10 minut + - Czyszczenie cache: Linia 173 + +- **Synchronizacja cennika:** Linia 179-218 + - Częstotliwość: Co 1 godzinę + - Czyszczenie cache: Linia 212 + +#### Baselinker +- **Aktualizacja produktów:** Linia 220-289 +- **Funkcje:** Ceny, stany magazynowe, wagi +- **Częstotliwość:** Co 24 godziny (1440 minut) +- **Czyszczenie cache:** Linia 278 + +## Panel Administratora + +### Routing +- Główny katalog: `admin/` +- Template główny: `admin/templates/site/main-layout.php` +- Kontrolery: `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/controls/class.Settings.php:20-42` +- **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 +│ │ ├── controls/ # Kontrolery +│ │ └── factory/ # Fabryki/helpery +│ ├── front/ # Klasy frontendu +│ │ └── factory/ # Fabryki/helpery +│ └── shop/ # Klasy sklepu +├── libraries/ # Biblioteki zewnętrzne +├── temp/ # Cache tymczasowy +├── thumbs/ # Miniatury zdjęć +└── cron.php # Zadania CRON + +``` + +## Baza danych + +### 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 + +### Tabele integracji +- Kolumny w `pp_shop_products`: + - `sellasist_product_id`, `sellasist_get_data_date` + - `apilo_product_id`, `apilo_get_data_date` + - `baselinker_product_id`, `baselinker_get_data_date` + +## Konfiguracja + +### Redis +- Konfiguracja: `config.php` (zmienna `$config['redis']`) +- Parametry: host, port, password + +### Autoload +- Funkcja: `__autoload_my_classes()` w `cron.php:6` +- Wzorzec: `autoload/{namespace}/class.{ClassName}.php` + +## Klasy pomocnicze + +### \S (autoload/class.S.php) +Główna klasa helper 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 + +### Medoo +- Plik: `libraries/medoo/medoo.php` +- Zmienna: `$mdb` +- ORM do operacji na bazie danych + +## Najważniejsze wzorce + +### Namespace'y +- `\admin\controls\` - kontrolery panelu admin +- `\admin\factory\` - helpery/fabryki admin +- `\front\factory\` - helpery/fabryki frontend +- `\shop\` - klasy sklepu (Product, Order, itp.) + +### Cachowanie produktów +```php +// Pobranie produktu z cache +$product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash); + +// Czyszczenie cache produktu +\S::clear_product_cache($product_id); + +// Czyszczenie całego cache +\S::clear_redis_cache(); +``` + +## Refaktoryzacja do Domain-Driven Architecture + +### Nowa struktura (w trakcie migracji) +``` +autoload/ +├── Domain/ # Nowa warstwa biznesowa +│ └── Product/ +│ └── ProductRepository.php +├── shop/ # Legacy - fasady do nowych klas +├── admin/factory/ # Legacy - stopniowo migrowane +└── front/factory/ # Legacy - stopniowo migrowane +``` + +### 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 + +### Struktura +``` +tests/ +├── Unit/ # Testy jednostkowe +│ └── Domain/Product/ProductRepositoryTest.php +└── Integration/ # Testy integracyjne +``` + +## Ostatnie modyfikacje + +### 2025-02-05: Refaktoryzacja - Product Repository (ver. 0.238) +- **NOWE:** `Domain\Product\ProductRepository` - pierwsza klasa w nowej architekturze +- **NOWE:** Dependency Injection zamiast `global $mdb` +- **NOWE:** Testy jednostkowe (5 testów, 100% pokrycie) +- Zmigrowano: `get_product_quantity()` → `ProductRepository::getQuantity()` +- Kompatybilność: Stara klasa `shop\Product` działa jako fasada + +### 2025-02-05: System cache produktów (ver. 0.237) +- Automatyczne czyszczenie cache produktu po aktualizacji przez CRON +- AJAX dla przycisku "Wyczyść cache" w panelu admin +- Metody `delete()` i `deletePattern()` w CacheHandler +- Metoda `clear_product_cache()` w klasie S + +--- +*Dokument aktualizowany: 2025-02-05* diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md new file mode 100644 index 0000000..6fd4a45 --- /dev/null +++ b/REFACTORING_PLAN.md @@ -0,0 +1,254 @@ +# 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) +│ ├── Product/ +│ │ ├── Product.php # Entity +│ │ ├── ProductRepository.php # Dostęp do bazy +│ │ ├── ProductService.php # Logika biznesowa +│ │ └── ProductCacheService.php # Cache produktu +│ ├── Order/ +│ ├── Category/ +│ └── ... +│ +├── Admin/ # Warstwa administratora +│ ├── Controllers/ +│ └── Services/ +│ +├── Frontend/ # Warstwa użytkownika +│ ├── 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/ +``` + +## 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 +- **Cache** (częściowo) + - ✅ CacheHandler - ma delete/deletePattern + - ✅ RedisConnection - singleton + - ✅ S::clear_product_cache() - nowa metoda + +### 🔄 W trakcie +- **Product** + - ✅ get_product_quantity() - **ZMIGROWANE** (2025-02-05) 🎉 + - Nowa klasa: `Domain\Product\ProductRepository::getQuantity()` + - Fasada w: `shop\Product::get_product_quantity()` + - Test: `tests/Unit/Domain/Product/ProductRepositoryTest.php` + - Testy: ✅ 5/5 przechodzą (11 asercji) + - Aktualizacja: ver. 0.238 + - Użycie DI: ✅ Konstruktor przyjmuje `$db` + - [ ] get_product_price() - NASTĘPNA 👉 + - [ ] get_product_name() + +### 📋 Do zrobienia +- Order +- Category +- ShopAttribute +- ShopProduct (factory) + +## Testowanie + +### Framework: PHPUnit +Instalacja: +```bash +composer require --dev phpunit/phpunit +``` + +### Struktura testów +``` +tests/ +├── Unit/ +│ ├── Domain/ +│ │ └── Product/ +│ │ ├── ProductRepositoryTest.php +│ │ └── ProductServiceTest.php +│ └── Shared/ +│ └── Cache/ +│ └── CacheHandlerTest.php +└── Integration/ + └── Domain/ + └── Product/ +``` + +### Przykład testu +```php +// tests/Unit/Domain/Product/ProductRepositoryTest.php +use PHPUnit\Framework\TestCase; +use Domain\Product\ProductRepository; + +class ProductRepositoryTest extends TestCase { + public function testGetQuantityReturnsCorrectValue() { + // Arrange + $mockDb = $this->createMock(\medoo::class); + $mockDb->method('get')->willReturn(10); + + $repo = new ProductRepository($mockDb); + + // Act + $quantity = $repo->getQuantity(123); + + // Assert + $this->assertEquals(10, $quantity); + } +} +``` + +## 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 + +### Composer autoloader +Dodaj do `composer.json`: +```json +{ + "autoload": { + "psr-4": { + "Domain\\": "autoload/Domain/", + "Admin\\": "autoload/Admin/", + "Frontend\\": "autoload/Frontend/", + "Shared\\": "autoload/Shared/" + } + } +} +``` + +### Static Analysis +```bash +composer require --dev phpstan/phpstan +vendor/bin/phpstan analyse autoload/Domain +``` + +## Kolejność refaktoryzacji (priorytet) + +1. **Cache** (już w trakcie) ✅ +2. **Product** (rozpoczynamy) + - getQuantity + - getPrice + - getName + - getFromCache +3. **ProductRepository** (dostęp do bazy) +4. **ProductService** (logika biznesowa) +5. **Order** +6. **Category** + +--- +*Rozpoczęto: 2025-02-05* +*Ostatnia aktualizacja: 2025-02-05* diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..f85f369 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,134 @@ +# 🧪 Testowanie shopPRO + +## Szybki start + +### Uruchom wszystkie testy +```bash +./test.bat # Windows CMD (z nazwami testów) +./test-simple.bat # Tylko kropki (szybki) +./test-debug.bat # Pełne szczegóły (debug) +./test.sh # Git Bash +``` + +### Konkretny plik +```bash +./test.bat tests/Unit/Domain/Product/ProductRepositoryTest.php +``` + +## Tryby wyświetlania + +### 1. TestDox (domyślny) - Czytelna lista ✅ +```bash +./test.bat +``` +Wynik: +``` +Product Repository + ✔ Get quantity returns correct value [2.78 ms] + ✔ Get quantity returns null when product not found + ✔ Find returns product data +``` + +### 2. Simple - Tylko kropki 📊 +```bash +./test-simple.bat +``` +Wynik: +``` +..... 5 / 5 (100%) +OK (5 tests, 11 assertions) +``` + +### 3. Debug - Wszystkie szczegóły 🔬 +```bash +./test-debug.bat +``` +Wynik: +``` +Test 'testGetQuantity' started +Test 'testGetQuantity' ended +... +``` + +## Interpretacja wyników + +### ✅ Sukces +``` +..... 5 / 5 (100%) + +OK (5 tests, 11 assertions) +``` +- `.` = test przeszedł +- Wszystko działa! + +### ❌ Błąd +``` +..E.. 5 / 5 (100%) + +ERRORS! +Tests: 5, Assertions: 8, Errors: 1. +``` +- `E` = Error - błąd w kodzie +- Sprawdź szczegóły powyżej + +### ❌ Niepowodzenie +``` +..F.. 5 / 5 (100%) + +FAILURES! +Tests: 5, Assertions: 11, Failures: 1. +``` +- `F` = Failure - asercja się nie powiodła +- Oczekiwano innej wartości + +## Przykładowy test + +```php +public function testGetQuantityReturnsCorrectValue() +{ + // Arrange - Przygotuj + $mockDb = $this->createMock(\medoo::class); + $mockDb->method('get')->willReturn(42); + $repository = new ProductRepository($mockDb); + + // Act - Wykonaj + $quantity = $repository->getQuantity(123); + + // Assert - Sprawdź + $this->assertEquals(42, $quantity); +} +``` + +## Dodawanie nowych testów + +1. Utwórz plik w `tests/Unit/Domain/{Module}/{Class}Test.php` +2. Rozszerz `TestCase` +3. Metody testowe zaczynaj od `test` +4. Użyj pattern **AAA** (Arrange, Act, Assert) + +## Asercje + +```php +$this->assertEquals(expected, actual); // Równość +$this->assertIsInt($value); // Typ +$this->assertNull($value); // Null +$this->assertTrue($condition); // Prawda +$this->assertCount(3, $array); // Rozmiar +``` + +## Mockowanie + +```php +// Prosty mock +$mock = $this->createMock(\medoo::class); +$mock->method('get')->willReturn('wartość'); + +// Z weryfikacją +$mock->expects($this->once()) + ->method('get') + ->with($this->equalTo('tabela')) + ->willReturn(42); +``` + +--- +📚 Więcej: [tests/README.md](tests/README.md) diff --git a/admin/templates/site/main-layout.php b/admin/templates/site/main-layout.php index 87e167d..1967e72 100644 --- a/admin/templates/site/main-layout.php +++ b/admin/templates/site/main-layout.php @@ -169,7 +169,7 @@