Add new settings and cache repository files, update admin settings controller and templates
- Introduced new `SettingsRepository` and `CacheRepository` classes in the `autoload\Domain` namespace. - Updated `SettingsController` in the `admin\Controllers` namespace to enhance settings management. - Added new templates for settings in `admin\templates\settings` and `admin\templates\site`. - Improved overall structure and organization of settings-related files.
This commit is contained in:
@@ -7,7 +7,20 @@
|
|||||||
"Bash(C:/xampp/php/php.exe:*)",
|
"Bash(C:/xampp/php/php.exe:*)",
|
||||||
"Bash(where:*)",
|
"Bash(where:*)",
|
||||||
"Bash(composer:*)",
|
"Bash(composer:*)",
|
||||||
"Bash(curl:*)"
|
"Bash(curl:*)",
|
||||||
|
"Bash(C:xamppphpphp.exe phpunit.phar --testdox)",
|
||||||
|
"Bash(php phpunit.phar:*)",
|
||||||
|
"Bash(ls -la \"c:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\autoload\"\" 2>nul | findstr /i \"admin Admin \")",
|
||||||
|
"Bash(php -r:*)",
|
||||||
|
"Bash(php list_zip.php:*)",
|
||||||
|
"Bash(php create_update_239.php:*)",
|
||||||
|
"Bash(php vendor/bin/phpunit:*)",
|
||||||
|
"Bash(python:*)",
|
||||||
|
"Bash(\"C:/Program Files/7-Zip/7z.exe\" l \"updates/0.20/ver_0.239.zip\")",
|
||||||
|
"Bash(powershell.exe -Command \"[System.IO.Compression.ZipFile]::OpenRead\\(''c:/visual studio code/projekty/shopPRO/updates/0.20/ver_0.239.zip''\\).Entries | ForEach-Object { Write-Host $_.FullName }\")",
|
||||||
|
"Bash(powershell.exe -Command \"Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::OpenRead\\(''c:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\updates\\\\0.20\\\\ver_0.239.zip''\\).Entries | ForEach-Object { Write-Host $_.FullName }\")",
|
||||||
|
"Bash(powershell.exe -NoProfile -Command 'Add-Type -AssemblyName System.IO.Compression.FileSystem; $z = [System.IO.Compression.ZipFile]::OpenRead\\(\"\"c:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\updates\\\\0.20\\\\ver_0.239.zip\"\"\\); foreach \\($e in $z.Entries\\) { $e.FullName }; $z.Dispose\\(\\)')",
|
||||||
|
"Bash(powershell.exe -NoProfile -Command:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -183,14 +183,32 @@ $product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash)
|
|||||||
### Nowa struktura (w trakcie migracji)
|
### Nowa struktura (w trakcie migracji)
|
||||||
```
|
```
|
||||||
autoload/
|
autoload/
|
||||||
├── Domain/ # Nowa warstwa biznesowa
|
├── Domain/ # Nowa warstwa biznesowa (namespace \Domain\)
|
||||||
│ └── Product/
|
│ ├── Product/
|
||||||
│ └── ProductRepository.php
|
│ │ └── ProductRepository.php # getQuantity, getPrice, getName, find, updateQuantity
|
||||||
├── shop/ # Legacy - fasady do nowych klas
|
│ ├── Banner/
|
||||||
├── admin/factory/ # Legacy - stopniowo migrowane
|
│ │ └── BannerRepository.php # find, delete, save
|
||||||
└── front/factory/ # Legacy - stopniowo migrowane
|
│ ├── Settings/
|
||||||
|
│ │ └── SettingsRepository.php # saveSettings, getSettings (fasada → factory)
|
||||||
|
│ └── Cache/
|
||||||
|
│ └── CacheRepository.php # clearCache (dirs + Redis)
|
||||||
|
├── admin/
|
||||||
|
│ ├── Controllers/ # Nowe kontrolery (namespace \admin\Controllers\)
|
||||||
|
│ │ ├── BannerController.php # DI, instancyjny
|
||||||
|
│ │ └── SettingsController.php # DI, instancyjny (clearCache, save, view)
|
||||||
|
│ ├── 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)
|
||||||
|
├── shop/ # Legacy - fasady do Domain
|
||||||
|
└── front/factory/ # Legacy - stopniowo migrowane
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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
|
### Dependency Injection
|
||||||
Nowe klasy używają **Dependency Injection** zamiast `global` variables:
|
Nowe klasy używają **Dependency Injection** zamiast `global` variables:
|
||||||
```php
|
```php
|
||||||
@@ -215,13 +233,39 @@ $quantity = $repository->getQuantity($id);
|
|||||||
### Struktura
|
### Struktura
|
||||||
```
|
```
|
||||||
tests/
|
tests/
|
||||||
├── Unit/ # Testy jednostkowe
|
├── Unit/
|
||||||
│ └── Domain/Product/ProductRepositoryTest.php
|
│ ├── Domain/
|
||||||
└── Integration/ # Testy integracyjne
|
│ │ ├── Product/ProductRepositoryTest.php # 11 testów
|
||||||
|
│ │ ├── Banner/BannerRepositoryTest.php # 4 testy
|
||||||
|
│ │ ├── Settings/SettingsRepositoryTest.php # 3 testy
|
||||||
|
│ │ └── Cache/CacheRepositoryTest.php # 4 testy
|
||||||
|
│ └── admin/
|
||||||
|
│ └── Controllers/SettingsControllerTest.php # 7 testów
|
||||||
|
└── Integration/
|
||||||
```
|
```
|
||||||
|
**Łącznie: 29 testów, 60 asercji**
|
||||||
|
|
||||||
## Ostatnie modyfikacje
|
## Ostatnie modyfikacje
|
||||||
|
|
||||||
|
### 2026-02-05: Migracja Settings + Cache (ver. 0.240)
|
||||||
|
- **NOWE:** `Domain\Settings\SettingsRepository` - repozytorium ustawień (fasada → factory)
|
||||||
|
- **NOWE:** `Domain\Cache\CacheRepository` - repozytorium cache (dirs + Redis)
|
||||||
|
- **NOWE:** `admin\Controllers\SettingsController` - kontroler z DI (clearCache, save, view)
|
||||||
|
- **FIX:** Brakujący `id="content"` w main-layout.php (komunikaty grid.js)
|
||||||
|
- **FIX:** `persist_edit = true` w settings.php (komunikat po zapisie)
|
||||||
|
- Stary kontroler `admin\controls\Settings` zachowany jako fallback
|
||||||
|
- Testy: 29 testów, 60 asercji (+14 nowych)
|
||||||
|
- Bootstrap testów: stuby klas systemowych (S, RedisConnection, Redis, CacheHandler)
|
||||||
|
|
||||||
|
### 2026-02-05: Migracja Banner + Product (ver. 0.239)
|
||||||
|
- **NOWE:** `Domain\Banner\BannerRepository` - repozytorium banerów (find, delete, save)
|
||||||
|
- **NOWE:** `admin\Controllers\BannerController` - pierwszy kontroler z DI
|
||||||
|
- **NOWE:** Router z mapą `$newControllers` + fallback na stare kontrolery
|
||||||
|
- **NOWE:** Autoloader PSR-4 fallback w 9 entry pointach
|
||||||
|
- Zmigrowano: `get_product_price()` → `ProductRepository::getPrice()`
|
||||||
|
- Zmigrowano: `get_product_name()` → `ProductRepository::getName()`
|
||||||
|
- Testy: 15 testów, 31 asercji
|
||||||
|
|
||||||
### 2025-02-05: Refaktoryzacja - Product Repository (ver. 0.238)
|
### 2025-02-05: Refaktoryzacja - Product Repository (ver. 0.238)
|
||||||
- **NOWE:** `Domain\Product\ProductRepository` - pierwsza klasa w nowej architekturze
|
- **NOWE:** `Domain\Product\ProductRepository` - pierwsza klasa w nowej architekturze
|
||||||
- **NOWE:** Dependency Injection zamiast `global $mdb`
|
- **NOWE:** Dependency Injection zamiast `global $mdb`
|
||||||
@@ -236,4 +280,4 @@ tests/
|
|||||||
- Metoda `clear_product_cache()` w klasie S
|
- Metoda `clear_product_cache()` w klasie S
|
||||||
|
|
||||||
---
|
---
|
||||||
*Dokument aktualizowany: 2025-02-05*
|
*Dokument aktualizowany: 2026-02-05*
|
||||||
|
|||||||
@@ -11,21 +11,30 @@ Stopniowe przeniesienie logiki biznesowej do architektury warstwowej:
|
|||||||
|
|
||||||
```
|
```
|
||||||
autoload/
|
autoload/
|
||||||
├── Domain/ # Logika biznesowa (CORE)
|
├── Domain/ # Logika biznesowa (CORE) - namespace \Domain\
|
||||||
│ ├── Product/
|
│ ├── Product/
|
||||||
│ │ ├── Product.php # Entity
|
│ │ ├── ProductRepository.php # ✅ Zmigrowane (getQuantity, getPrice, getName, find, updateQuantity)
|
||||||
│ │ ├── ProductRepository.php # Dostęp do bazy
|
│ │ ├── ProductService.php # Logika biznesowa (przyszłość)
|
||||||
│ │ ├── ProductService.php # Logika biznesowa
|
│ │ └── ProductCacheService.php # Cache produktu (przyszłość)
|
||||||
│ │ └── ProductCacheService.php # Cache produktu
|
│ ├── Banner/
|
||||||
|
│ │ └── BannerRepository.php # ✅ Zmigrowane (find, delete, save)
|
||||||
|
│ ├── Settings/
|
||||||
|
│ │ └── SettingsRepository.php # ✅ Zmigrowane (saveSettings, getSettings) - fasada → factory
|
||||||
|
│ ├── Cache/
|
||||||
|
│ │ └── CacheRepository.php # ✅ Zmigrowane (clearCache)
|
||||||
│ ├── Order/
|
│ ├── Order/
|
||||||
│ ├── Category/
|
│ ├── Category/
|
||||||
│ └── ...
|
│ └── ...
|
||||||
│
|
│
|
||||||
├── Admin/ # Warstwa administratora
|
├── admin/ # Warstwa administratora (istniejący katalog!)
|
||||||
│ ├── Controllers/
|
│ ├── Controllers/ # Nowe kontrolery - namespace \admin\Controllers\
|
||||||
│ └── Services/
|
│ │ ├── BannerController.php
|
||||||
|
│ │ └── SettingsController.php
|
||||||
|
│ ├── controls/ # Stare kontrolery (legacy fallback)
|
||||||
|
│ ├── factory/ # Stare helpery (legacy)
|
||||||
|
│ └── view/ # Widoki (statyczne - OK bez zmian)
|
||||||
│
|
│
|
||||||
├── Frontend/ # Warstwa użytkownika
|
├── Frontend/ # Warstwa użytkownika (przyszłość)
|
||||||
│ ├── Controllers/
|
│ ├── Controllers/
|
||||||
│ └── Services/
|
│ └── Services/
|
||||||
│
|
│
|
||||||
@@ -42,6 +51,11 @@ autoload/
|
|||||||
└── front/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
|
## Zasady migracji
|
||||||
|
|
||||||
### 1. Stopniowość
|
### 1. Stopniowość
|
||||||
@@ -126,11 +140,46 @@ grep -r "Product::getQuantity" .
|
|||||||
- Nowa klasa: `Domain\Product\ProductRepository::getQuantity()`
|
- Nowa klasa: `Domain\Product\ProductRepository::getQuantity()`
|
||||||
- Fasada w: `shop\Product::get_product_quantity()`
|
- Fasada w: `shop\Product::get_product_quantity()`
|
||||||
- Test: `tests/Unit/Domain/Product/ProductRepositoryTest.php`
|
- Test: `tests/Unit/Domain/Product/ProductRepositoryTest.php`
|
||||||
- Testy: ✅ 5/5 przechodzą (11 asercji)
|
- Testy: ✅ 5/5 przechodzą
|
||||||
- Aktualizacja: ver. 0.238
|
- Aktualizacja: ver. 0.238
|
||||||
- Użycie DI: ✅ Konstruktor przyjmuje `$db`
|
- Użycie DI: ✅ Konstruktor przyjmuje `$db`
|
||||||
- [ ] get_product_price() - NASTĘPNA 👉
|
- ✅ get_product_price() - **ZMIGROWANE** (2026-02-05) 🎉
|
||||||
- [ ] get_product_name()
|
- Nowa metoda: `Domain\Product\ProductRepository::getPrice()`
|
||||||
|
- Fasada w: `shop\Product::get_product_price()`
|
||||||
|
- Testy: ✅ 4 nowe testy (cena regularna, promocyjna, promo wyższa, nie znaleziono)
|
||||||
|
- Użycie: `front\factory\ShopPromotion` (linia 132)
|
||||||
|
- Aktualizacja: ver. 0.239
|
||||||
|
- ✅ get_product_name() - **ZMIGROWANE** (2026-02-05) 🎉
|
||||||
|
- Nowa metoda: `Domain\Product\ProductRepository::getName()`
|
||||||
|
- Fasada w: `shop\Product::get_product_name()`
|
||||||
|
- Testy: ✅ 2 nowe testy (nazwa znaleziona, nie znaleziona)
|
||||||
|
- Użycie: brak aktywnych wywołań (przygotowane na przyszłość)
|
||||||
|
- Aktualizacja: ver. 0.239
|
||||||
|
- [ ] is_product_on_promotion() - NASTĘPNA 👉
|
||||||
|
|
||||||
|
- **Banner** (DEMO pełnej migracji kontrolera)
|
||||||
|
- ✅ BannerRepository - **ZMIGROWANE** (2026-02-05) 🎉
|
||||||
|
- Nowa klasa: `Domain\Banner\BannerRepository` (find, delete, save, saveTranslations)
|
||||||
|
- Nowy kontroler: `admin\Controllers\BannerController` (DI, instancyjny)
|
||||||
|
- Router: `admin\Site::route()` → sprawdza nowy kontroler → fallback na stary
|
||||||
|
- Testy: ✅ 4 testy (find z tłumaczeniami, not found, delete, save)
|
||||||
|
- Stary kontroler `admin\controls\Banners` działa jako niezależny fallback
|
||||||
|
- Stara factory `admin\factory\Banners` zachowana bez zmian (fallback)
|
||||||
|
- Aktualizacja: ver. 0.239
|
||||||
|
|
||||||
|
- **Settings** (migracja kontrolera - krok pośredni)
|
||||||
|
- ✅ SettingsRepository - **ZMIGROWANE** (2026-02-05) 🎉
|
||||||
|
- Nowa klasa: `Domain\Settings\SettingsRepository` (saveSettings, getSettings)
|
||||||
|
- Krok pośredni: fasada nad `admin\factory\Settings` (docelowo DI z $db)
|
||||||
|
- Nowy kontroler: `admin\Controllers\SettingsController` (DI, instancyjny)
|
||||||
|
- Testy: ✅ 3 testy (instancja, metody)
|
||||||
|
- Stary kontroler `admin\controls\Settings` zachowany jako fallback
|
||||||
|
- Aktualizacja: ver. 0.240
|
||||||
|
- ✅ CacheRepository - **ZMIGROWANE** (2026-02-05) 🎉
|
||||||
|
- Nowa klasa: `Domain\Cache\CacheRepository` (clearCache)
|
||||||
|
- Używa `\S::delete_dir()` + `\RedisConnection`
|
||||||
|
- Testy: ✅ 4 testy (z Redis, bez Redis, niedostępny, struktura)
|
||||||
|
- Aktualizacja: ver. 0.240
|
||||||
|
|
||||||
### 📋 Do zrobienia
|
### 📋 Do zrobienia
|
||||||
- Order
|
- Order
|
||||||
@@ -151,16 +200,15 @@ composer require --dev phpunit/phpunit
|
|||||||
tests/
|
tests/
|
||||||
├── Unit/
|
├── Unit/
|
||||||
│ ├── Domain/
|
│ ├── Domain/
|
||||||
│ │ └── Product/
|
│ │ ├── Product/ProductRepositoryTest.php # 11 testów
|
||||||
│ │ ├── ProductRepositoryTest.php
|
│ │ ├── Banner/BannerRepositoryTest.php # 4 testy
|
||||||
│ │ └── ProductServiceTest.php
|
│ │ ├── Settings/SettingsRepositoryTest.php # 3 testy
|
||||||
│ └── Shared/
|
│ │ └── Cache/CacheRepositoryTest.php # 4 testy
|
||||||
│ └── Cache/
|
│ └── admin/
|
||||||
│ └── CacheHandlerTest.php
|
│ └── Controllers/SettingsControllerTest.php # 7 testów
|
||||||
└── Integration/
|
└── Integration/
|
||||||
└── Domain/
|
|
||||||
└── Product/
|
|
||||||
```
|
```
|
||||||
|
**Łącznie: 29 testów, 60 asercji**
|
||||||
|
|
||||||
### Przykład testu
|
### Przykład testu
|
||||||
```php
|
```php
|
||||||
@@ -215,20 +263,12 @@ public function getQuantity($id) {
|
|||||||
|
|
||||||
## Narzędzia pomocnicze
|
## Narzędzia pomocnicze
|
||||||
|
|
||||||
### Composer autoloader
|
### Autoloader (produkcja)
|
||||||
Dodaj do `composer.json`:
|
Autoloader w 9 entry pointach obsługuje dwie konwencje:
|
||||||
```json
|
1. `autoload/{namespace}/class.{ClassName}.php` (legacy)
|
||||||
{
|
2. `autoload/{namespace}/{ClassName}.php` (PSR-4, fallback)
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
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`
|
||||||
"Domain\\": "autoload/Domain/",
|
|
||||||
"Admin\\": "autoload/Admin/",
|
|
||||||
"Frontend\\": "autoload/Frontend/",
|
|
||||||
"Shared\\": "autoload/Shared/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Static Analysis
|
### Static Analysis
|
||||||
```bash
|
```bash
|
||||||
@@ -238,17 +278,20 @@ vendor/bin/phpstan analyse autoload/Domain
|
|||||||
|
|
||||||
## Kolejność refaktoryzacji (priorytet)
|
## Kolejność refaktoryzacji (priorytet)
|
||||||
|
|
||||||
1. **Cache** (już w trakcie) ✅
|
1. **Cache** ✅
|
||||||
2. **Product** (rozpoczynamy)
|
2. **Product** (w trakcie)
|
||||||
- getQuantity
|
- ✅ getQuantity (ver. 0.238)
|
||||||
- getPrice
|
- ✅ getPrice (ver. 0.239)
|
||||||
- getName
|
- ✅ getName (ver. 0.239)
|
||||||
- getFromCache
|
- [ ] is_product_on_promotion - NASTĘPNA 👉
|
||||||
3. **ProductRepository** (dostęp do bazy)
|
- [ ] getFromCache
|
||||||
4. **ProductService** (logika biznesowa)
|
- [ ] getProductImg
|
||||||
|
3. **Banner** ✅ (pełna migracja kontrolera, ver. 0.239)
|
||||||
|
4. **Settings** ✅ (migracja kontrolera - krok pośredni, ver. 0.240)
|
||||||
5. **Order**
|
5. **Order**
|
||||||
6. **Category**
|
5. **Category**
|
||||||
|
6. **ShopAttribute**
|
||||||
|
|
||||||
---
|
---
|
||||||
*Rozpoczęto: 2025-02-05*
|
*Rozpoczęto: 2025-02-05*
|
||||||
*Ostatnia aktualizacja: 2025-02-05*
|
*Ostatnia aktualizacja: 2026-02-05*
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ function __autoload_my_classes( $classname )
|
|||||||
}
|
}
|
||||||
if ( file_exists( $f ) )
|
if ( file_exists( $f ) )
|
||||||
require_once( $f );
|
require_once( $f );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$f = '../autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||||
|
if ( file_exists( $f ) )
|
||||||
|
require_once( $f );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
spl_autoload_register( '__autoload_my_classes' );
|
spl_autoload_register( '__autoload_my_classes' );
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,15 @@ function __autoload_my_classes( $classname )
|
|||||||
$q = explode( '\\', $classname );
|
$q = explode( '\\', $classname );
|
||||||
$c = array_pop( $q );
|
$c = array_pop( $q );
|
||||||
$f = '../autoload/' . implode( '/', $q ) . '/class.' . $c . '.php';
|
$f = '../autoload/' . implode( '/', $q ) . '/class.' . $c . '.php';
|
||||||
|
|
||||||
if ( file_exists( $f ) )
|
if ( file_exists( $f ) )
|
||||||
require_once( $f );
|
require_once( $f );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$f = '../autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||||
|
if ( file_exists( $f ) )
|
||||||
|
require_once( $f );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spl_autoload_register( '__autoload_my_classes' );
|
spl_autoload_register( '__autoload_my_classes' );
|
||||||
|
|||||||
@@ -361,13 +361,14 @@ $grid -> gdb_opt = $gdb;
|
|||||||
$grid -> include_plugins = true;
|
$grid -> include_plugins = true;
|
||||||
$grid -> title = 'Edycja ustawień';
|
$grid -> title = 'Edycja ustawień';
|
||||||
$grid -> actions = [
|
$grid -> actions = [
|
||||||
'save' => [ 'url' => '/admin/settings/settings_save/', 'back_url' => '/admin/settings/view/' ],
|
'save' => [ 'url' => '/admin/settings/settings_save/', 'back_url' => '' ],
|
||||||
];
|
];
|
||||||
|
$grid -> persist_edit = true;
|
||||||
$grid -> external_code = $out;
|
$grid -> external_code = $out;
|
||||||
echo $grid -> draw();
|
echo $grid -> draw();
|
||||||
?>
|
?>
|
||||||
<script>CKEDITOR.dtd.$removeEmpty['span'] = false;</script>
|
<script>CKEDITOR.dtd.$removeEmpty['span'] = false;</script>
|
||||||
<style type="text/css">#g-edit-cancel { display: none; }</style>
|
<style type="text/css">#g-edit-cancel, #g-edit-save.btn-system { display: none; }</style>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$( document).ready( function ()
|
$( document).ready( function ()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -201,7 +201,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<? endif;?>
|
<? endif;?>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 dashboard-page">
|
<div class="col-12 dashboard-page" id="content">
|
||||||
<?= $this -> content;?>
|
<?= $this -> content;?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
6
ajax.php
6
ajax.php
@@ -8,6 +8,12 @@ function __autoload_my_classes( $classname )
|
|||||||
|
|
||||||
if ( file_exists( $f ) )
|
if ( file_exists( $f ) )
|
||||||
require_once( $f );
|
require_once( $f );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$f = 'autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||||
|
if ( file_exists( $f ) )
|
||||||
|
require_once( $f );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
spl_autoload_register( '__autoload_my_classes' );
|
spl_autoload_register( '__autoload_my_classes' );
|
||||||
date_default_timezone_set( 'Europe/Warsaw' );
|
date_default_timezone_set( 'Europe/Warsaw' );
|
||||||
|
|||||||
6
api.php
6
api.php
@@ -9,6 +9,12 @@ function __autoload_my_classes( $classname )
|
|||||||
|
|
||||||
if ( file_exists( $f ) )
|
if ( file_exists( $f ) )
|
||||||
require_once( $f );
|
require_once( $f );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$f = 'autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||||
|
if ( file_exists( $f ) )
|
||||||
|
require_once( $f );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spl_autoload_register( '__autoload_my_classes' );
|
spl_autoload_register( '__autoload_my_classes' );
|
||||||
|
|||||||
109
autoload/Domain/Banner/BannerRepository.php
Normal file
109
autoload/Domain/Banner/BannerRepository.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
namespace Domain\Banner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository odpowiedzialny za dostęp do danych banerów
|
||||||
|
*/
|
||||||
|
class BannerRepository
|
||||||
|
{
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
public function __construct($db)
|
||||||
|
{
|
||||||
|
$this->db = $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pobiera baner po ID wraz z tłumaczeniami
|
||||||
|
*
|
||||||
|
* @param int $bannerId ID banera
|
||||||
|
* @return array|null Dane banera lub null
|
||||||
|
*/
|
||||||
|
public function find(int $bannerId): ?array
|
||||||
|
{
|
||||||
|
$banner = $this->db->get('pp_banners', '*', ['id' => $bannerId]);
|
||||||
|
|
||||||
|
if (!$banner) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = $this->db->select('pp_banners_langs', '*', ['id_banner' => $bannerId]);
|
||||||
|
if (is_array($results)) {
|
||||||
|
foreach ($results as $row) {
|
||||||
|
$banner['languages'][$row['id_lang']] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $banner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usuwa baner
|
||||||
|
*
|
||||||
|
* @param int $bannerId ID banera
|
||||||
|
* @return bool Czy usunięto
|
||||||
|
*/
|
||||||
|
public function delete(int $bannerId): bool
|
||||||
|
{
|
||||||
|
$result = $this->db->delete('pp_banners', ['id' => $bannerId]);
|
||||||
|
return $result !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zapisuje baner (insert lub update)
|
||||||
|
*
|
||||||
|
* @param array $data Dane banera
|
||||||
|
* @return int|false ID banera lub false
|
||||||
|
*/
|
||||||
|
public function save(array $data)
|
||||||
|
{
|
||||||
|
$bannerId = $data['id'] ?? null;
|
||||||
|
|
||||||
|
$bannerData = [
|
||||||
|
'name' => $data['name'],
|
||||||
|
'status' => $data['status'] == 'on' ? 1 : 0,
|
||||||
|
'date_start' => $data['date_start'] != '' ? $data['date_start'] : null,
|
||||||
|
'date_end' => $data['date_end'] != '' ? $data['date_end'] : null,
|
||||||
|
'home_page' => $data['home_page'] == 'on' ? 1 : 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!$bannerId) {
|
||||||
|
$this->db->insert('pp_banners', $bannerData);
|
||||||
|
$bannerId = $this->db->id();
|
||||||
|
if (!$bannerId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->db->update('pp_banners', $bannerData, ['id' => (int)$bannerId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->saveTranslations($bannerId, $data['src'], $data['url'], $data['html'], $data['text']);
|
||||||
|
|
||||||
|
return (int)$bannerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zapisuje tłumaczenia banera
|
||||||
|
*/
|
||||||
|
private function saveTranslations(int $bannerId, array $src, array $url, array $html, array $text): void
|
||||||
|
{
|
||||||
|
foreach ($src as $langId => $val) {
|
||||||
|
$translationData = [
|
||||||
|
'id_banner' => $bannerId,
|
||||||
|
'id_lang' => $langId,
|
||||||
|
'src' => $src[$langId],
|
||||||
|
'url' => $url[$langId],
|
||||||
|
'html' => $html[$langId],
|
||||||
|
'text' => $text[$langId],
|
||||||
|
];
|
||||||
|
|
||||||
|
$existingId = $this->db->get('pp_banners_langs', 'id', ['AND' => ['banner_id' => $bannerId, 'lang_id' => $langId]]);
|
||||||
|
|
||||||
|
if ($existingId) {
|
||||||
|
$this->db->update('pp_banners_langs', $translationData, ['id' => $existingId]);
|
||||||
|
} else {
|
||||||
|
$this->db->insert('pp_banners_langs', $translationData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
autoload/Domain/Cache/CacheRepository.php
Normal file
53
autoload/Domain/Cache/CacheRepository.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
namespace Domain\Cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repozytorium zarządzania cache (katalogi + Redis)
|
||||||
|
*
|
||||||
|
* Wyodrębniona logika czyszczenia cache z wstrzykiwanymi zależnościami.
|
||||||
|
* Obecnie nie używane przez SettingsController (cache czyszczony bezpośrednio).
|
||||||
|
* Przygotowane na przyszłe użycie w innych kontrolerach.
|
||||||
|
*/
|
||||||
|
class CacheRepository
|
||||||
|
{
|
||||||
|
private $redisConnection;
|
||||||
|
private $basePath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \RedisConnection $redisConnection Połączenie z Redis (nullable)
|
||||||
|
* @param string $basePath Ścieżka bazowa do katalogów cache
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
?\RedisConnection $redisConnection = null,
|
||||||
|
string $basePath = '../'
|
||||||
|
) {
|
||||||
|
$this->redisConnection = $redisConnection;
|
||||||
|
$this->basePath = $basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Czyszczenie całego cache (katalogi + Redis)
|
||||||
|
*
|
||||||
|
* @return array ['success' => bool, 'message' => string]
|
||||||
|
*/
|
||||||
|
public function clearCache(): array
|
||||||
|
{
|
||||||
|
\S::delete_dir( $this->basePath . 'temp/' );
|
||||||
|
\S::delete_dir( $this->basePath . 'thumbs/' );
|
||||||
|
|
||||||
|
$redisCleared = false;
|
||||||
|
if ( $this->redisConnection ) {
|
||||||
|
$redis = $this->redisConnection->getConnection();
|
||||||
|
if ( $redis ) {
|
||||||
|
$redis->flushAll();
|
||||||
|
$redisCleared = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Cache został wyczyszczony.',
|
||||||
|
'redisCleared' => $redisCleared
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,41 @@ class ProductRepository
|
|||||||
return $product ?: null;
|
return $product ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pobiera cenę produktu (promocyjną jeśli jest niższa, w przeciwnym razie regularną)
|
||||||
|
*
|
||||||
|
* @param int $productId ID produktu
|
||||||
|
* @return float|null Cena brutto lub null jeśli nie znaleziono
|
||||||
|
*/
|
||||||
|
public function getPrice(int $productId): ?float
|
||||||
|
{
|
||||||
|
$prices = $this->db->get('pp_shop_products', ['price_brutto', 'price_brutto_promo'], ['id' => $productId]);
|
||||||
|
|
||||||
|
if (!$prices) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($prices['price_brutto_promo'] != '' && $prices['price_brutto_promo'] < $prices['price_brutto']) {
|
||||||
|
return (float)$prices['price_brutto_promo'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float)$prices['price_brutto'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pobiera nazwę produktu w danym języku
|
||||||
|
*
|
||||||
|
* @param int $productId ID produktu
|
||||||
|
* @param string $langId ID języka
|
||||||
|
* @return string|null Nazwa produktu lub null jeśli nie znaleziono
|
||||||
|
*/
|
||||||
|
public function getName(int $productId, string $langId): ?string
|
||||||
|
{
|
||||||
|
$name = $this->db->get('pp_shop_products_langs', 'name', ['AND' => ['product_id' => $productId, 'lang_id' => $langId]]);
|
||||||
|
|
||||||
|
return $name ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aktualizuje ilość produktu
|
* Aktualizuje ilość produktu
|
||||||
*
|
*
|
||||||
|
|||||||
66
autoload/Domain/Settings/SettingsRepository.php
Normal file
66
autoload/Domain/Settings/SettingsRepository.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
namespace Domain\Settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repozytorium ustawień - deleguje do admin\factory\Settings (legacy)
|
||||||
|
*
|
||||||
|
* Krok pośredni migracji: wyodrębnia logikę zapisu z kontrolera.
|
||||||
|
* Docelowo zastąpi factory bezpośrednim dostępem do bazy (jak BannerRepository).
|
||||||
|
*/
|
||||||
|
class SettingsRepository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Zapis ustawień
|
||||||
|
*
|
||||||
|
* @param array $values Tablica wartości z formularza
|
||||||
|
* @return array Odpowiedź z factory ['status' => string, 'msg' => string]
|
||||||
|
*/
|
||||||
|
public function saveSettings(array $values): array
|
||||||
|
{
|
||||||
|
$settings = \admin\factory\Settings::settings_details();
|
||||||
|
|
||||||
|
$response = \admin\factory\Settings::settings_save(
|
||||||
|
$values['firm_name'], $values['firm_adress'], $values['additional_info'], $values['contact_form'], $values['contact_email'], $values['email_host'],
|
||||||
|
$values['email_port'], $values['email_login'], $values['email_password'], $values['google_maps'], $values['facebook_link'], $values['statistic_code'], $values['htaccess'],
|
||||||
|
$values['robots'], $values['shop_bank_account_info'], $values['update'], $values['boot_animation'], $settings['newsletter_header'], $settings['newsletter_footer'], $values['hotpay_api']
|
||||||
|
);
|
||||||
|
|
||||||
|
\admin\factory\Settings::settings_update( 'devel', $values['devel'] == 'on' ? 1 : 0 );
|
||||||
|
\admin\factory\Settings::settings_update( 'ssl', $values['ssl'] == 'on' ? 1 : 0 );
|
||||||
|
\admin\factory\Settings::settings_update( 'htaccess_cache', $values['htaccess_cache'] == 'on' ? 1 : 0 );
|
||||||
|
\admin\factory\Settings::settings_update( 'free_delivery', $values['free_delivery'] );
|
||||||
|
\admin\factory\Settings::settings_update( 'przelewy24_sandbox', $values['przelewy24_sandbox'] == 'on' ? 1 : 0 );
|
||||||
|
\admin\factory\Settings::settings_update( 'przelewy24_merchant_id', $values['przelewy24_merchant_id'] );
|
||||||
|
\admin\factory\Settings::settings_update( 'przelewy24_crc_key', $values['przelewy24_crc_key'] );
|
||||||
|
\admin\factory\Settings::settings_update( 'update_key', $values['update_key'] );
|
||||||
|
\admin\factory\Settings::settings_update( 'tpay_id', $values['tpay_id'] );
|
||||||
|
\admin\factory\Settings::settings_update( 'tpay_sandbox', $values['tpay_sandbox'] == 'on' ? 1 : 0 );
|
||||||
|
\admin\factory\Settings::settings_update( 'tpay_security_code', $values['tpay_security_code'] );
|
||||||
|
\admin\factory\Settings::settings_update( 'piksel', $values['piksel'] );
|
||||||
|
\admin\factory\Settings::settings_update( 'generate_webp', $values['generate_webp'] == 'on' ? 1 : 0 );
|
||||||
|
\admin\factory\Settings::settings_update( 'lazy_loading', $values['lazy_loading'] == 'on' ? 1 : 0 );
|
||||||
|
\admin\factory\Settings::settings_update( 'orlen_paczka_map_token', $values['orlen_paczka_map_token'] );
|
||||||
|
\admin\factory\Settings::settings_update( 'google_tag_manager_id', $values['google_tag_manager_id'] );
|
||||||
|
\admin\factory\Settings::settings_update( 'infinitescroll', $values['infinitescroll'] == 'on' ? 1 : 0 );
|
||||||
|
\admin\factory\Settings::settings_update( 'own_gtm_js', $values['own_gtm_js'] );
|
||||||
|
\admin\factory\Settings::settings_update( 'own_gtm_html', $values['own_gtm_html'] );
|
||||||
|
|
||||||
|
foreach ( $values['warehouse_message_zero'] as $key => $val )
|
||||||
|
\admin\factory\Settings::settings_update( 'warehouse_message_zero_' . $key, $val );
|
||||||
|
|
||||||
|
foreach ( $values['warehouse_message_nonzero'] as $key => $val )
|
||||||
|
\admin\factory\Settings::settings_update( 'warehouse_message_nonzero_' . $key, $val );
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pobranie wszystkich ustawień
|
||||||
|
*
|
||||||
|
* @return array Tablica ustawień [param => value]
|
||||||
|
*/
|
||||||
|
public function getSettings(): array
|
||||||
|
{
|
||||||
|
return \admin\factory\Settings::settings_details() ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
76
autoload/admin/Controllers/BannerController.php
Normal file
76
autoload/admin/Controllers/BannerController.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
namespace admin\Controllers;
|
||||||
|
|
||||||
|
use Domain\Banner\BannerRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kontroler banerów w panelu administratora (nowa architektura)
|
||||||
|
*
|
||||||
|
* Porównanie z starym kontrolerem admin\controls\Banners:
|
||||||
|
* - Używa Dependency Injection zamiast global $mdb
|
||||||
|
* - Deleguje logikę do Domain\Banner\BannerRepository
|
||||||
|
* - Kontroler zajmuje się TYLKO obsługą requestów i odpowiedzi
|
||||||
|
*/
|
||||||
|
class BannerController
|
||||||
|
{
|
||||||
|
private BannerRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(BannerRepository $repository)
|
||||||
|
{
|
||||||
|
$this->repository = $repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lista banerów
|
||||||
|
*/
|
||||||
|
public function list(): string
|
||||||
|
{
|
||||||
|
// Widok nie zmienia się - nadal używamy starego systemu szablonów
|
||||||
|
return \admin\view\Banners::banners_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edycja banera
|
||||||
|
*/
|
||||||
|
public function edit(): string
|
||||||
|
{
|
||||||
|
$bannerId = (int)\S::get('id');
|
||||||
|
$banner = $this->repository->find($bannerId);
|
||||||
|
$languages = \admin\factory\Languages::languages_list();
|
||||||
|
|
||||||
|
return \admin\view\Banners::banner_edit($banner, $languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zapisanie banera (AJAX)
|
||||||
|
*/
|
||||||
|
public function save(): void
|
||||||
|
{
|
||||||
|
$response = ['status' => 'error', 'msg' => 'Podczas zapisywania baneru wystąpił błąd. Proszę spróbować ponownie.'];
|
||||||
|
|
||||||
|
$values = json_decode(\S::get('values'), true);
|
||||||
|
$bannerId = $this->repository->save($values);
|
||||||
|
if ($bannerId) {
|
||||||
|
\S::delete_dir('../temp/');
|
||||||
|
$response = ['status' => 'ok', 'msg' => 'Baner został zapisany.', 'id' => $bannerId];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usunięcie banera
|
||||||
|
*/
|
||||||
|
public function delete(): void
|
||||||
|
{
|
||||||
|
$bannerId = (int)\S::get('id');
|
||||||
|
if ($this->repository->delete($bannerId)) {
|
||||||
|
\S::delete_dir('../temp/');
|
||||||
|
\S::alert('Baner został usunięty.');
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: /admin/banners/view_list/');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
88
autoload/admin/Controllers/SettingsController.php
Normal file
88
autoload/admin/Controllers/SettingsController.php
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
namespace admin\Controllers;
|
||||||
|
|
||||||
|
use Domain\Settings\SettingsRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kontroler ustawień w panelu administratora (nowa architektura)
|
||||||
|
*
|
||||||
|
* Używa Dependency Injection zamiast static methods
|
||||||
|
* Deleguje logikę do Domain\Settings\SettingsRepository
|
||||||
|
*/
|
||||||
|
class SettingsController
|
||||||
|
{
|
||||||
|
private SettingsRepository $settingsRepository;
|
||||||
|
|
||||||
|
public function __construct(SettingsRepository $settingsRepository)
|
||||||
|
{
|
||||||
|
$this->settingsRepository = $settingsRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Czyszczenie cache
|
||||||
|
*/
|
||||||
|
public function clearCache(): void
|
||||||
|
{
|
||||||
|
\S::delete_dir( '../temp/' );
|
||||||
|
\S::delete_dir( '../thumbs/' );
|
||||||
|
|
||||||
|
$redis = \RedisConnection::getInstance()->getConnection();
|
||||||
|
if ( $redis )
|
||||||
|
$redis->flushAll();
|
||||||
|
|
||||||
|
\S::alert( 'Cache został wyczyszczony.' );
|
||||||
|
\S::htacces();
|
||||||
|
|
||||||
|
header( 'Location: /admin/dashboard/main_view/' );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Czyszczenie cache (AJAX)
|
||||||
|
*/
|
||||||
|
public function clearCacheAjax(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
\S::delete_dir( '../temp/' );
|
||||||
|
\S::delete_dir( '../thumbs/' );
|
||||||
|
|
||||||
|
$redis = \RedisConnection::getInstance()->getConnection();
|
||||||
|
if ( $redis )
|
||||||
|
$redis->flushAll();
|
||||||
|
|
||||||
|
\S::htacces();
|
||||||
|
|
||||||
|
echo json_encode( [ 'status' => 'success', 'message' => 'Cache został wyczyszczony.' ] );
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
echo json_encode( [ 'status' => 'error', 'message' => 'Błąd podczas czyszczenia cache: ' . $e->getMessage() ] );
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zapis ustawień (AJAX)
|
||||||
|
*/
|
||||||
|
public function save(): void
|
||||||
|
{
|
||||||
|
$values = json_decode( \S::get( 'values' ), true );
|
||||||
|
|
||||||
|
$response = $this->settingsRepository->saveSettings( $values );
|
||||||
|
|
||||||
|
\S::delete_dir( '../temp/' );
|
||||||
|
\S::htacces();
|
||||||
|
|
||||||
|
echo json_encode( $response );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Widok ustawień
|
||||||
|
*/
|
||||||
|
public function view(): string
|
||||||
|
{
|
||||||
|
return \Tpl::view( 'settings/settings', [
|
||||||
|
'languages' => \admin\factory\Languages::languages_list(),
|
||||||
|
'settings' => $this->settingsRepository->getSettings()
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -184,6 +184,70 @@ class Site
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapa nowych kontrolerów: module => fabryka kontrolera (DI)
|
||||||
|
* Przy migracji kolejnego kontrolera - dodaj wpis tutaj
|
||||||
|
*/
|
||||||
|
private static $newControllers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zwraca mapę fabryk kontrolerów (inicjalizacja runtime)
|
||||||
|
*/
|
||||||
|
private static function getControllerFactories(): array
|
||||||
|
{
|
||||||
|
if ( !empty( self::$newControllers ) )
|
||||||
|
return self::$newControllers;
|
||||||
|
|
||||||
|
self::$newControllers = [
|
||||||
|
'Banners' => function() {
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
return new \admin\Controllers\BannerController(
|
||||||
|
new \Domain\Banner\BannerRepository( $mdb )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'Settings' => function() {
|
||||||
|
return new \admin\Controllers\SettingsController(
|
||||||
|
new \Domain\Settings\SettingsRepository()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return self::$newControllers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tworzy instancję nowego kontrolera z Dependency Injection
|
||||||
|
*/
|
||||||
|
private static function createController( string $moduleName )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$factories = self::getControllerFactories();
|
||||||
|
if ( !isset( $factories[$moduleName] ) )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
$factory = $factories[$moduleName];
|
||||||
|
if ( !is_callable( $factory ) )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return $factory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapowanie nazw akcji: stara_nazwa => nowa_nazwa
|
||||||
|
* Potrzebne gdy stary routing używa innej konwencji nazw
|
||||||
|
*/
|
||||||
|
private static $actionMap = [
|
||||||
|
'view_list' => 'list',
|
||||||
|
'banner_edit' => 'edit',
|
||||||
|
'banner_save' => 'save',
|
||||||
|
'banner_delete' => 'delete',
|
||||||
|
'clear_cache' => 'clearCache',
|
||||||
|
'clear_cache_ajax' => 'clearCacheAjax',
|
||||||
|
'settings_save' => 'save',
|
||||||
|
];
|
||||||
|
|
||||||
public static function route()
|
public static function route()
|
||||||
{
|
{
|
||||||
$_SESSION['admin'] = true;
|
$_SESSION['admin'] = true;
|
||||||
@@ -193,14 +257,35 @@ class Site
|
|||||||
|
|
||||||
$page = \S::get_session( 'p' );
|
$page = \S::get_session( 'p' );
|
||||||
|
|
||||||
$class = '\admin\controls\\';
|
// Budowanie nazwy modułu
|
||||||
|
$moduleName = '';
|
||||||
$results = explode( '_', \S::get( 'module' ) );
|
$results = explode( '_', \S::get( 'module' ) );
|
||||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||||
$class .= ucfirst( $row );
|
$moduleName .= ucfirst( $row );
|
||||||
|
|
||||||
$action = \S::get( 'action' );
|
$action = \S::get( 'action' );
|
||||||
|
|
||||||
|
// 1. Sprawdź czy istnieje nowy kontroler
|
||||||
|
$factories = self::getControllerFactories();
|
||||||
|
if ( isset( $factories[$moduleName] ) )
|
||||||
|
{
|
||||||
|
$controller = self::createController( $moduleName );
|
||||||
|
if ( $controller )
|
||||||
|
{
|
||||||
|
// Mapuj nazwę akcji (stara → nowa) lub użyj oryginalnej
|
||||||
|
$newAction = self::$actionMap[$action] ?? $action;
|
||||||
|
|
||||||
|
if ( method_exists( $controller, $newAction ) )
|
||||||
|
{
|
||||||
|
return $controller->$newAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fallback na stary kontroler
|
||||||
|
$class = '\admin\controls\\' . $moduleName;
|
||||||
|
|
||||||
if ( class_exists( $class ) and method_exists( new $class, $action ) )
|
if ( class_exists( $class ) and method_exists( new $class, $action ) )
|
||||||
return call_user_func_array( array( $class, $action ), array() );
|
return call_user_func_array( array( $class, $action ), array() );
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
namespace admin\controls;
|
namespace admin\controls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stary kontroler - fallback dla routingu
|
||||||
|
* Nowy routing kieruje do admin\Controllers\BannerController
|
||||||
|
* Ten plik jest używany tylko gdy nowy kontroler nie jest dostępny
|
||||||
|
*/
|
||||||
class Banners
|
class Banners
|
||||||
{
|
{
|
||||||
public static function banner_delete()
|
public static function banner_delete()
|
||||||
@@ -40,4 +45,4 @@ class Banners
|
|||||||
return \admin\view\Banners::banners_list();
|
return \admin\view\Banners::banners_list();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -105,4 +105,4 @@ class Banners
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -289,19 +289,16 @@ class Product implements \ArrayAccess
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pobierz cenę produktu
|
// pobierz cenę produktu
|
||||||
|
// FASADA - wywołuje nową klasę Domain\Product\ProductRepository
|
||||||
static public function get_product_price( int $product_id )
|
static public function get_product_price( int $product_id )
|
||||||
{
|
{
|
||||||
global $mdb;
|
global $mdb;
|
||||||
|
$repository = new \Domain\Product\ProductRepository($mdb);
|
||||||
$prices = $mdb -> get( 'pp_shop_products', [ 'price_brutto', 'price_brutto_promo' ], [ 'id' => $product_id ] );
|
return $repository->getPrice($product_id);
|
||||||
|
|
||||||
if ( $prices['price_brutto_promo'] != '' and $prices['price_brutto_promo'] < $prices['price_brutto'] )
|
|
||||||
return $prices['price_brutto_promo'];
|
|
||||||
else
|
|
||||||
return $prices['price_brutto'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pobierz nazwę produktu
|
// pobierz nazwę produktu
|
||||||
|
// FASADA - wywołuje nową klasę Domain\Product\ProductRepository
|
||||||
static public function get_product_name( int $product_id, string $lang_id = null )
|
static public function get_product_name( int $product_id, string $lang_id = null )
|
||||||
{
|
{
|
||||||
global $mdb;
|
global $mdb;
|
||||||
@@ -309,7 +306,8 @@ class Product implements \ArrayAccess
|
|||||||
if ( !$lang_id )
|
if ( !$lang_id )
|
||||||
$lang_id = \front\factory\Languages::default_language();
|
$lang_id = \front\factory\Languages::default_language();
|
||||||
|
|
||||||
return $mdb -> get( 'pp_shop_products_langs', 'name', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lang_id ] ] );
|
$repository = new \Domain\Product\ProductRepository($mdb);
|
||||||
|
return $repository->getName($product_id, $lang_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pobierz i wyświetl produktu do zestawu po dodaniu do koszyka
|
// pobierz i wyświetl produktu do zestawu po dodaniu do koszyka
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ function __autoload_my_classes( $classname )
|
|||||||
|
|
||||||
if ( file_exists( $f ) )
|
if ( file_exists( $f ) )
|
||||||
require_once( $f );
|
require_once( $f );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$f = 'autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||||
|
if ( file_exists( $f ) )
|
||||||
|
require_once( $f );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spl_autoload_register( '__autoload_my_classes' );
|
spl_autoload_register( '__autoload_my_classes' );
|
||||||
|
|||||||
6
cron.php
6
cron.php
@@ -11,6 +11,12 @@ function __autoload_my_classes( $classname )
|
|||||||
|
|
||||||
if ( file_exists( $f ) )
|
if ( file_exists( $f ) )
|
||||||
require_once( $f );
|
require_once( $f );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$f = 'autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||||
|
if ( file_exists( $f ) )
|
||||||
|
require_once( $f );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spl_autoload_register( '__autoload_my_classes' );
|
spl_autoload_register( '__autoload_my_classes' );
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ function __autoload_my_classes( $classname )
|
|||||||
|
|
||||||
if ( file_exists( $f ) )
|
if ( file_exists( $f ) )
|
||||||
require_once( $f );
|
require_once( $f );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$f = '../autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||||
|
if ( file_exists( $f ) )
|
||||||
|
require_once( $f );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spl_autoload_register( '__autoload_my_classes' );
|
spl_autoload_register( '__autoload_my_classes' );
|
||||||
|
|||||||
@@ -5,9 +5,15 @@ function __autoload_my_classes( $classname )
|
|||||||
$q = explode( '\\' , $classname );
|
$q = explode( '\\' , $classname );
|
||||||
$c = array_pop( $q );
|
$c = array_pop( $q );
|
||||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||||
|
|
||||||
if ( file_exists( $f ) )
|
if ( file_exists( $f ) )
|
||||||
require_once( $f );
|
require_once( $f );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$f = 'autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||||
|
if ( file_exists( $f ) )
|
||||||
|
require_once( $f );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
spl_autoload_register( '__autoload_my_classes' );
|
spl_autoload_register( '__autoload_my_classes' );
|
||||||
date_default_timezone_set( 'Europe/Warsaw' );
|
date_default_timezone_set( 'Europe/Warsaw' );
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ function __autoload_my_classes( $classname )
|
|||||||
|
|
||||||
if ( file_exists( $f ) )
|
if ( file_exists( $f ) )
|
||||||
require_once( $f );
|
require_once( $f );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$f = 'autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||||
|
if ( file_exists( $f ) )
|
||||||
|
require_once( $f );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spl_autoload_register( '__autoload_my_classes' );
|
spl_autoload_register( '__autoload_my_classes' );
|
||||||
|
|||||||
119
tests/Unit/Domain/Banner/BannerRepositoryTest.php
Normal file
119
tests/Unit/Domain/Banner/BannerRepositoryTest.php
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
namespace Tests\Unit\Domain\Banner;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Domain\Banner\BannerRepository;
|
||||||
|
|
||||||
|
class BannerRepositoryTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Test pobierania banera - zwraca dane z tłumaczeniami
|
||||||
|
*/
|
||||||
|
public function testFindReturnsBannerWithTranslations()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
|
||||||
|
$mockDb->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('pp_banners', '*', ['id' => 1])
|
||||||
|
->willReturn(['id' => 1, 'name' => 'Baner testowy', 'status' => 1]);
|
||||||
|
|
||||||
|
$mockDb->expects($this->once())
|
||||||
|
->method('select')
|
||||||
|
->with('pp_banners_langs', '*', ['id_banner' => 1])
|
||||||
|
->willReturn([
|
||||||
|
['id_lang' => 'pl', 'src' => 'banner.jpg', 'url' => '/promo'],
|
||||||
|
['id_lang' => 'en', 'src' => 'banner-en.jpg', 'url' => '/promo-en'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$repository = new BannerRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$banner = $repository->find(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertIsArray($banner);
|
||||||
|
$this->assertEquals('Baner testowy', $banner['name']);
|
||||||
|
$this->assertArrayHasKey('languages', $banner);
|
||||||
|
$this->assertCount(2, $banner['languages']);
|
||||||
|
$this->assertEquals('banner.jpg', $banner['languages']['pl']['src']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test pobierania banera - nie istnieje
|
||||||
|
*/
|
||||||
|
public function testFindReturnsNullWhenNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->method('get')->willReturn(false);
|
||||||
|
|
||||||
|
$repository = new BannerRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$banner = $repository->find(999);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertNull($banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test usuwania banera - sukces
|
||||||
|
*/
|
||||||
|
public function testDeleteReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->expects($this->once())
|
||||||
|
->method('delete')
|
||||||
|
->with('pp_banners', ['id' => 5])
|
||||||
|
->willReturn($this->createMock(\PDOStatement::class));
|
||||||
|
|
||||||
|
$repository = new BannerRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = $repository->delete(5);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test zapisywania nowego banera
|
||||||
|
*/
|
||||||
|
public function testSaveInsertsNewBanner()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
|
||||||
|
// insert() wywoływane 2x: raz dla banera, raz dla tłumaczenia
|
||||||
|
$mockDb->expects($this->exactly(2))
|
||||||
|
->method('insert');
|
||||||
|
|
||||||
|
$mockDb->expects($this->once())
|
||||||
|
->method('id')
|
||||||
|
->willReturn(10);
|
||||||
|
|
||||||
|
// get() for checking existing translations - returns false (no existing)
|
||||||
|
$mockDb->method('get')->willReturn(false);
|
||||||
|
|
||||||
|
$repository = new BannerRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = $repository->save([
|
||||||
|
'name' => 'Nowy baner',
|
||||||
|
'status' => 'on',
|
||||||
|
'date_start' => '',
|
||||||
|
'date_end' => '',
|
||||||
|
'home_page' => 'on',
|
||||||
|
'src' => ['pl' => 'banner.jpg'],
|
||||||
|
'url' => ['pl' => '/promo'],
|
||||||
|
'html' => ['pl' => ''],
|
||||||
|
'text' => ['pl' => 'Tekst'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertEquals(10, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
76
tests/Unit/Domain/Cache/CacheRepositoryTest.php
Normal file
76
tests/Unit/Domain/Cache/CacheRepositoryTest.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
namespace Tests\Unit\Domain\Cache;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Domain\Cache\CacheRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testy dla CacheRepository
|
||||||
|
*
|
||||||
|
* Testujemy logikę Redis (mockowaną) oraz strukturę odpowiedzi.
|
||||||
|
* Czyszczenie katalogów delegowane do \S::delete_dir() - nietestowalne unit testami.
|
||||||
|
*/
|
||||||
|
class CacheRepositoryTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Test: Czyszczenie cache z Redis
|
||||||
|
*/
|
||||||
|
public function testClearCacheWithRedis(): void
|
||||||
|
{
|
||||||
|
$mockRedis = $this->createMock(\Redis::class);
|
||||||
|
$mockRedis->expects($this->once())->method('flushAll')->willReturn(true);
|
||||||
|
|
||||||
|
$mockRedisConnection = $this->createMock(\RedisConnection::class);
|
||||||
|
$mockRedisConnection->expects($this->once())->method('getConnection')->willReturn($mockRedis);
|
||||||
|
|
||||||
|
$repository = new CacheRepository($mockRedisConnection);
|
||||||
|
$result = $repository->clearCache();
|
||||||
|
|
||||||
|
$this->assertTrue($result['success']);
|
||||||
|
$this->assertTrue($result['redisCleared']);
|
||||||
|
$this->assertStringContainsString('wyczyszczony', $result['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test: Redis niedostępny (getConnection zwraca null)
|
||||||
|
*/
|
||||||
|
public function testClearCacheRedisUnavailable(): void
|
||||||
|
{
|
||||||
|
$mockRedisConnection = $this->createMock(\RedisConnection::class);
|
||||||
|
$mockRedisConnection->expects($this->once())->method('getConnection')->willReturn(null);
|
||||||
|
|
||||||
|
$repository = new CacheRepository($mockRedisConnection);
|
||||||
|
$result = $repository->clearCache();
|
||||||
|
|
||||||
|
$this->assertTrue($result['success']);
|
||||||
|
$this->assertFalse($result['redisCleared']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test: Bez RedisConnection (null)
|
||||||
|
*/
|
||||||
|
public function testClearCacheWithoutRedis(): void
|
||||||
|
{
|
||||||
|
$repository = new CacheRepository(null);
|
||||||
|
$result = $repository->clearCache();
|
||||||
|
|
||||||
|
$this->assertTrue($result['success']);
|
||||||
|
$this->assertFalse($result['redisCleared']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test: Struktura odpowiedzi
|
||||||
|
*/
|
||||||
|
public function testClearCacheReturnStructure(): void
|
||||||
|
{
|
||||||
|
$repository = new CacheRepository(null);
|
||||||
|
$result = $repository->clearCache();
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('success', $result);
|
||||||
|
$this->assertArrayHasKey('message', $result);
|
||||||
|
$this->assertArrayHasKey('redisCleared', $result);
|
||||||
|
$this->assertIsBool($result['success']);
|
||||||
|
$this->assertIsString($result['message']);
|
||||||
|
$this->assertIsBool($result['redisCleared']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -115,6 +115,132 @@ class ProductRepositoryTest extends TestCase
|
|||||||
$this->assertTrue($result);
|
$this->assertTrue($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test pobierania ceny - zwraca cenę regularną gdy brak promocji
|
||||||
|
*/
|
||||||
|
public function testGetPriceReturnsRegularPrice()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->method('get')->willReturn([
|
||||||
|
'price_brutto' => '99.99',
|
||||||
|
'price_brutto_promo' => ''
|
||||||
|
]);
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$price = $repository->getPrice(123);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertEquals(99.99, $price);
|
||||||
|
$this->assertIsFloat($price);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test pobierania ceny - zwraca cenę promocyjną gdy jest niższa
|
||||||
|
*/
|
||||||
|
public function testGetPriceReturnsPromoPrice()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->method('get')->willReturn([
|
||||||
|
'price_brutto' => '99.99',
|
||||||
|
'price_brutto_promo' => '79.99'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$price = $repository->getPrice(123);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertEquals(79.99, $price);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test pobierania ceny - zwraca regularną gdy promo jest wyższa
|
||||||
|
*/
|
||||||
|
public function testGetPriceReturnsRegularWhenPromoIsHigher()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->method('get')->willReturn([
|
||||||
|
'price_brutto' => '49.99',
|
||||||
|
'price_brutto_promo' => '79.99'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$price = $repository->getPrice(123);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertEquals(49.99, $price);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test pobierania ceny - zwraca null gdy produkt nie istnieje
|
||||||
|
*/
|
||||||
|
public function testGetPriceReturnsNullWhenNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->method('get')->willReturn(false);
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$price = $repository->getPrice(999);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertNull($price);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test pobierania nazwy produktu - zwraca nazwę
|
||||||
|
*/
|
||||||
|
public function testGetNameReturnsProductName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with(
|
||||||
|
$this->equalTo('pp_shop_products_langs'),
|
||||||
|
$this->equalTo('name'),
|
||||||
|
$this->equalTo(['AND' => ['product_id' => 123, 'lang_id' => 'pl']])
|
||||||
|
)
|
||||||
|
->willReturn('Testowy produkt');
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$name = $repository->getName(123, 'pl');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertEquals('Testowy produkt', $name);
|
||||||
|
$this->assertIsString($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test pobierania nazwy - produkt nie istnieje
|
||||||
|
*/
|
||||||
|
public function testGetNameReturnsNullWhenNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->method('get')->willReturn(false);
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$name = $repository->getName(999, 'pl');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertNull($name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test typu zwracanej wartości
|
* Test typu zwracanej wartości
|
||||||
*/
|
*/
|
||||||
|
|||||||
33
tests/Unit/Domain/Settings/SettingsRepositoryTest.php
Normal file
33
tests/Unit/Domain/Settings/SettingsRepositoryTest.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
namespace Tests\Unit\Domain\Settings;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Domain\Settings\SettingsRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testy dla SettingsRepository
|
||||||
|
*
|
||||||
|
* UWAGA: SettingsRepository jest krokiem pośrednim migracji - deleguje do
|
||||||
|
* statycznych metod admin\factory\Settings. Pełne testy jednostkowe z mockami
|
||||||
|
* będą możliwe po migracji do DI (jak ProductRepository/BannerRepository).
|
||||||
|
*
|
||||||
|
* Na razie testujemy tylko to, co da się zweryfikować bez bazy danych.
|
||||||
|
*/
|
||||||
|
class SettingsRepositoryTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCanBeInstantiated(): void
|
||||||
|
{
|
||||||
|
$repository = new SettingsRepository();
|
||||||
|
$this->assertInstanceOf(SettingsRepository::class, $repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasSaveSettingsMethod(): void
|
||||||
|
{
|
||||||
|
$this->assertTrue(method_exists(SettingsRepository::class, 'saveSettings'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasGetSettingsMethod(): void
|
||||||
|
{
|
||||||
|
$this->assertTrue(method_exists(SettingsRepository::class, 'getSettings'));
|
||||||
|
}
|
||||||
|
}
|
||||||
66
tests/Unit/admin/Controllers/SettingsControllerTest.php
Normal file
66
tests/Unit/admin/Controllers/SettingsControllerTest.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
namespace Tests\Unit\admin\Controllers;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use admin\Controllers\SettingsController;
|
||||||
|
use Domain\Settings\SettingsRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testy dla SettingsController
|
||||||
|
*
|
||||||
|
* Kontroler używa SettingsRepository (DI).
|
||||||
|
* Cache czyszczony bezpośrednio przez \S::delete_dir() i \RedisConnection.
|
||||||
|
*/
|
||||||
|
class SettingsControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
private $mockSettingsRepository;
|
||||||
|
private $controller;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->mockSettingsRepository = $this->createMock(SettingsRepository::class);
|
||||||
|
$this->controller = new SettingsController($this->mockSettingsRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConstructorAcceptsRepository(): void
|
||||||
|
{
|
||||||
|
$controller = new SettingsController($this->mockSettingsRepository);
|
||||||
|
$this->assertInstanceOf(SettingsController::class, $controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasClearCacheMethod(): void
|
||||||
|
{
|
||||||
|
$this->assertTrue(method_exists($this->controller, 'clearCache'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasClearCacheAjaxMethod(): void
|
||||||
|
{
|
||||||
|
$this->assertTrue(method_exists($this->controller, 'clearCacheAjax'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasSaveMethod(): void
|
||||||
|
{
|
||||||
|
$this->assertTrue(method_exists($this->controller, 'save'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasViewMethod(): void
|
||||||
|
{
|
||||||
|
$this->assertTrue(method_exists($this->controller, 'view'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsNotAbstract(): void
|
||||||
|
{
|
||||||
|
$reflection = new \ReflectionClass($this->controller);
|
||||||
|
$this->assertFalse($reflection->isAbstract());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testActionMethodReturnTypes(): void
|
||||||
|
{
|
||||||
|
$reflection = new \ReflectionClass($this->controller);
|
||||||
|
|
||||||
|
$this->assertEquals('void', (string)$reflection->getMethod('clearCache')->getReturnType());
|
||||||
|
$this->assertEquals('void', (string)$reflection->getMethod('clearCacheAjax')->getReturnType());
|
||||||
|
$this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType());
|
||||||
|
$this->assertEquals('string', (string)$reflection->getMethod('view')->getReturnType());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,21 +7,23 @@
|
|||||||
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
||||||
require_once __DIR__ . '/../vendor/autoload.php';
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
} else {
|
} else {
|
||||||
// Ręczny autoloader dla Domain
|
// Ręczny autoloader PSR-4 dla autoload/
|
||||||
spl_autoload_register(function ($class) {
|
spl_autoload_register(function ($class) {
|
||||||
$prefix = 'Domain\\';
|
$prefixes = [
|
||||||
$baseDir = __DIR__ . '/../autoload/Domain/';
|
'Domain\\' => __DIR__ . '/../autoload/Domain/',
|
||||||
|
'admin\\Controllers\\' => __DIR__ . '/../autoload/admin/Controllers/',
|
||||||
|
];
|
||||||
|
|
||||||
$len = strlen($prefix);
|
foreach ($prefixes as $prefix => $baseDir) {
|
||||||
if (strncmp($prefix, $class, $len) !== 0) {
|
$len = strlen($prefix);
|
||||||
return;
|
if (strncmp($prefix, $class, $len) === 0) {
|
||||||
}
|
$relativeClass = substr($class, $len);
|
||||||
|
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
||||||
$relativeClass = substr($class, $len);
|
if (file_exists($file)) {
|
||||||
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
require $file;
|
||||||
|
return;
|
||||||
if (file_exists($file)) {
|
}
|
||||||
require $file;
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -31,3 +33,44 @@ require_once __DIR__ . '/../libraries/medoo/medoo.php';
|
|||||||
|
|
||||||
// Ustaw timezone
|
// Ustaw timezone
|
||||||
date_default_timezone_set('Europe/Warsaw');
|
date_default_timezone_set('Europe/Warsaw');
|
||||||
|
|
||||||
|
// Stuby klas systemowych (nie dostępnych w testach unit)
|
||||||
|
if (!class_exists('S')) {
|
||||||
|
class S {
|
||||||
|
public static function delete_dir($path) {}
|
||||||
|
public static function alert($msg) {}
|
||||||
|
public static function htacces() {}
|
||||||
|
public static function get($key) { return null; }
|
||||||
|
public static function set_message($msg) {}
|
||||||
|
public static function clear_redis_cache() {}
|
||||||
|
public static function clear_product_cache($id) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists('RedisConnection')) {
|
||||||
|
class RedisConnection {
|
||||||
|
private static $instance;
|
||||||
|
public static function getInstance() {
|
||||||
|
if (!self::$instance) self::$instance = new self();
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
public function getConnection() { return null; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists('Redis')) {
|
||||||
|
class Redis {
|
||||||
|
public function flushAll() { return true; }
|
||||||
|
public function get($key) { return null; }
|
||||||
|
public function set($key, $value) { return true; }
|
||||||
|
public function del($key) { return 1; }
|
||||||
|
public function keys($pattern) { return []; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists('CacheHandler')) {
|
||||||
|
class CacheHandler {
|
||||||
|
public function delete($key) {}
|
||||||
|
public function deletePattern($pattern) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
BIN
updates/0.20/ver_0.239.zip
Normal file
BIN
updates/0.20/ver_0.239.zip
Normal file
Binary file not shown.
BIN
updates/0.20/ver_0.240.zip
Normal file
BIN
updates/0.20/ver_0.240.zip
Normal file
Binary file not shown.
@@ -1,3 +1,15 @@
|
|||||||
|
<b>ver. 0.240</b><br />
|
||||||
|
- NEW - refaktoryzacja: Domain\Settings\SettingsRepository + admin\Controllers\SettingsController (architektura Domain-Driven)
|
||||||
|
- NEW - refaktoryzacja: Domain\Cache\CacheRepository - czyszczenie cache z obsługą Redis
|
||||||
|
- FIX - komunikat potwierdzenia zapisu ustawień w panelu administratora
|
||||||
|
- FIX - naprawiono element #content w layoucie admina (powiadomienia grid.js)
|
||||||
|
<hr>
|
||||||
|
<b>ver. 0.239</b><br />
|
||||||
|
- NEW - refaktoryzacja: Domain\Banner\BannerRepository + admin\Controllers\BannerController (pełna migracja kontrolera)
|
||||||
|
- NEW - refaktoryzacja: Domain\Product\ProductRepository::getPrice(), getName() - migracja kolejnych metod
|
||||||
|
- NEW - router admin z obsługą nowych kontrolerów (fallback na stare)
|
||||||
|
- UPDATE - shop\Product::get_product_price(), get_product_name() używają nowego repozytorium (kompatybilność zachowana)
|
||||||
|
<hr>
|
||||||
<b>ver. 0.238</b><br />
|
<b>ver. 0.238</b><br />
|
||||||
- NEW - refaktoryzacja: Domain\Product\ProductRepository - pierwsza klasa w nowej architekturze Domain-Driven
|
- NEW - refaktoryzacja: Domain\Product\ProductRepository - pierwsza klasa w nowej architekturze Domain-Driven
|
||||||
- NEW - Dependency Injection zamiast global variables
|
- NEW - Dependency Injection zamiast global variables
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?
|
<?
|
||||||
$current_ver = 238;
|
$current_ver = 240;
|
||||||
|
|
||||||
for ($i = 1; $i <= $current_ver; $i++)
|
for ($i = 1; $i <= $current_ver; $i++)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user