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(where:*)",
|
||||
"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)
|
||||
```
|
||||
autoload/
|
||||
├── Domain/ # Nowa warstwa biznesowa
|
||||
│ └── Product/
|
||||
│ └── ProductRepository.php
|
||||
├── shop/ # Legacy - fasady do nowych klas
|
||||
├── admin/factory/ # Legacy - stopniowo migrowane
|
||||
└── front/factory/ # Legacy - stopniowo migrowane
|
||||
├── Domain/ # Nowa warstwa biznesowa (namespace \Domain\)
|
||||
│ ├── Product/
|
||||
│ │ └── ProductRepository.php # getQuantity, getPrice, getName, find, updateQuantity
|
||||
│ ├── Banner/
|
||||
│ │ └── BannerRepository.php # find, delete, save
|
||||
│ ├── 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
|
||||
Nowe klasy używają **Dependency Injection** zamiast `global` variables:
|
||||
```php
|
||||
@@ -215,13 +233,39 @@ $quantity = $repository->getQuantity($id);
|
||||
### Struktura
|
||||
```
|
||||
tests/
|
||||
├── Unit/ # Testy jednostkowe
|
||||
│ └── Domain/Product/ProductRepositoryTest.php
|
||||
└── Integration/ # Testy integracyjne
|
||||
├── Unit/
|
||||
│ ├── Domain/
|
||||
│ │ ├── 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
|
||||
|
||||
### 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)
|
||||
- **NOWE:** `Domain\Product\ProductRepository` - pierwsza klasa w nowej architekturze
|
||||
- **NOWE:** Dependency Injection zamiast `global $mdb`
|
||||
@@ -236,4 +280,4 @@ tests/
|
||||
- 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/
|
||||
├── Domain/ # Logika biznesowa (CORE)
|
||||
├── Domain/ # Logika biznesowa (CORE) - namespace \Domain\
|
||||
│ ├── Product/
|
||||
│ │ ├── Product.php # Entity
|
||||
│ │ ├── ProductRepository.php # Dostęp do bazy
|
||||
│ │ ├── ProductService.php # Logika biznesowa
|
||||
│ │ └── ProductCacheService.php # Cache produktu
|
||||
│ │ ├── ProductRepository.php # ✅ Zmigrowane (getQuantity, getPrice, getName, find, updateQuantity)
|
||||
│ │ ├── ProductService.php # Logika biznesowa (przyszłość)
|
||||
│ │ └── ProductCacheService.php # Cache produktu (przyszłość)
|
||||
│ ├── Banner/
|
||||
│ │ └── BannerRepository.php # ✅ Zmigrowane (find, delete, save)
|
||||
│ ├── Settings/
|
||||
│ │ └── SettingsRepository.php # ✅ Zmigrowane (saveSettings, getSettings) - fasada → factory
|
||||
│ ├── Cache/
|
||||
│ │ └── CacheRepository.php # ✅ Zmigrowane (clearCache)
|
||||
│ ├── Order/
|
||||
│ ├── Category/
|
||||
│ └── ...
|
||||
│
|
||||
├── Admin/ # Warstwa administratora
|
||||
│ ├── Controllers/
|
||||
│ └── Services/
|
||||
├── admin/ # Warstwa administratora (istniejący katalog!)
|
||||
│ ├── Controllers/ # Nowe kontrolery - namespace \admin\Controllers\
|
||||
│ │ ├── 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/
|
||||
│ └── Services/
|
||||
│
|
||||
@@ -42,6 +51,11 @@ autoload/
|
||||
└── front/factory/
|
||||
```
|
||||
|
||||
### WAŻNE: Konwencja namespace → katalog (Linux case-sensitive!)
|
||||
- `\Domain\` → `autoload/Domain/` (duże D - nowy katalog)
|
||||
- `\admin\Controllers\` → `autoload/admin/Controllers/` (małe a - istniejący katalog)
|
||||
- NIE używać `\Admin\` (duże A) bo na serwerze Linux katalog to `admin/` (małe a)
|
||||
|
||||
## Zasady migracji
|
||||
|
||||
### 1. Stopniowość
|
||||
@@ -126,11 +140,46 @@ grep -r "Product::getQuantity" .
|
||||
- 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)
|
||||
- Testy: ✅ 5/5 przechodzą
|
||||
- Aktualizacja: ver. 0.238
|
||||
- Użycie DI: ✅ Konstruktor przyjmuje `$db`
|
||||
- [ ] get_product_price() - NASTĘPNA 👉
|
||||
- [ ] get_product_name()
|
||||
- ✅ get_product_price() - **ZMIGROWANE** (2026-02-05) 🎉
|
||||
- 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
|
||||
- Order
|
||||
@@ -151,16 +200,15 @@ composer require --dev phpunit/phpunit
|
||||
tests/
|
||||
├── Unit/
|
||||
│ ├── Domain/
|
||||
│ │ └── Product/
|
||||
│ │ ├── ProductRepositoryTest.php
|
||||
│ │ └── ProductServiceTest.php
|
||||
│ └── Shared/
|
||||
│ └── Cache/
|
||||
│ └── CacheHandlerTest.php
|
||||
│ │ ├── 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/
|
||||
└── Domain/
|
||||
└── Product/
|
||||
```
|
||||
**Łącznie: 29 testów, 60 asercji**
|
||||
|
||||
### Przykład testu
|
||||
```php
|
||||
@@ -215,20 +263,12 @@ public function getQuantity($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/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
### Autoloader (produkcja)
|
||||
Autoloader w 9 entry pointach obsługuje dwie konwencje:
|
||||
1. `autoload/{namespace}/class.{ClassName}.php` (legacy)
|
||||
2. `autoload/{namespace}/{ClassName}.php` (PSR-4, fallback)
|
||||
|
||||
Entry pointy: `index.php`, `ajax.php`, `api.php`, `cron.php`, `cron-turstmate.php`, `download.php`, `admin/index.php`, `admin/ajax.php`, `cron/cron-xml.php`
|
||||
|
||||
### Static Analysis
|
||||
```bash
|
||||
@@ -238,17 +278,20 @@ 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)
|
||||
1. **Cache** ✅
|
||||
2. **Product** (w trakcie)
|
||||
- ✅ getQuantity (ver. 0.238)
|
||||
- ✅ getPrice (ver. 0.239)
|
||||
- ✅ getName (ver. 0.239)
|
||||
- [ ] is_product_on_promotion - NASTĘPNA 👉
|
||||
- [ ] getFromCache
|
||||
- [ ] getProductImg
|
||||
3. **Banner** ✅ (pełna migracja kontrolera, ver. 0.239)
|
||||
4. **Settings** ✅ (migracja kontrolera - krok pośredni, ver. 0.240)
|
||||
5. **Order**
|
||||
6. **Category**
|
||||
5. **Category**
|
||||
6. **ShopAttribute**
|
||||
|
||||
---
|
||||
*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 ) )
|
||||
require_once( $f );
|
||||
else
|
||||
{
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
|
||||
|
||||
@@ -17,8 +17,15 @@ function __autoload_my_classes( $classname )
|
||||
$q = explode( '\\', $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = '../autoload/' . implode( '/', $q ) . '/class.' . $c . '.php';
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
else
|
||||
{
|
||||
$f = '../autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
}
|
||||
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
|
||||
@@ -361,13 +361,14 @@ $grid -> gdb_opt = $gdb;
|
||||
$grid -> include_plugins = true;
|
||||
$grid -> title = 'Edycja ustawień';
|
||||
$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;
|
||||
echo $grid -> draw();
|
||||
?>
|
||||
<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">
|
||||
$( document).ready( function ()
|
||||
{
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
</div>
|
||||
<? endif;?>
|
||||
<div class="row">
|
||||
<div class="col-12 dashboard-page">
|
||||
<div class="col-12 dashboard-page" id="content">
|
||||
<?= $this -> content;?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
6
ajax.php
6
ajax.php
@@ -8,6 +8,12 @@ function __autoload_my_classes( $classname )
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
else
|
||||
{
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
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 ) )
|
||||
require_once( $f );
|
||||
else
|
||||
{
|
||||
$f = 'autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
||||
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()
|
||||
{
|
||||
$_SESSION['admin'] = true;
|
||||
@@ -193,14 +257,35 @@ class Site
|
||||
|
||||
$page = \S::get_session( 'p' );
|
||||
|
||||
$class = '\admin\controls\\';
|
||||
|
||||
// Budowanie nazwy modułu
|
||||
$moduleName = '';
|
||||
$results = explode( '_', \S::get( 'module' ) );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$class .= ucfirst( $row );
|
||||
$moduleName .= ucfirst( $row );
|
||||
|
||||
$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 ) )
|
||||
return call_user_func_array( array( $class, $action ), array() );
|
||||
else
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
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
|
||||
{
|
||||
public static function banner_delete()
|
||||
@@ -40,4 +45,4 @@ class Banners
|
||||
return \admin\view\Banners::banners_list();
|
||||
}
|
||||
}
|
||||
?>
|
||||
?>
|
||||
|
||||
@@ -105,4 +105,4 @@ class Banners
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
?>
|
||||
|
||||
@@ -289,19 +289,16 @@ class Product implements \ArrayAccess
|
||||
}
|
||||
|
||||
// pobierz cenę produktu
|
||||
// FASADA - wywołuje nową klasę Domain\Product\ProductRepository
|
||||
static public function get_product_price( int $product_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$prices = $mdb -> get( 'pp_shop_products', [ 'price_brutto', 'price_brutto_promo' ], [ 'id' => $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'];
|
||||
$repository = new \Domain\Product\ProductRepository($mdb);
|
||||
return $repository->getPrice($product_id);
|
||||
}
|
||||
|
||||
// pobierz nazwę produktu
|
||||
// FASADA - wywołuje nową klasę Domain\Product\ProductRepository
|
||||
static public function get_product_name( int $product_id, string $lang_id = null )
|
||||
{
|
||||
global $mdb;
|
||||
@@ -309,7 +306,8 @@ class Product implements \ArrayAccess
|
||||
if ( !$lang_id )
|
||||
$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
|
||||
|
||||
@@ -11,6 +11,12 @@ function __autoload_my_classes( $classname )
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
else
|
||||
{
|
||||
$f = 'autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
}
|
||||
|
||||
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 ) )
|
||||
require_once( $f );
|
||||
else
|
||||
{
|
||||
$f = 'autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
}
|
||||
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
|
||||
@@ -9,6 +9,12 @@ function __autoload_my_classes( $classname )
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
else
|
||||
{
|
||||
$f = '../autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
}
|
||||
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
|
||||
@@ -5,9 +5,15 @@ function __autoload_my_classes( $classname )
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
else
|
||||
{
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
@@ -9,6 +9,12 @@ function __autoload_my_classes( $classname )
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
else
|
||||
{
|
||||
$f = 'autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
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')) {
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
} else {
|
||||
// Ręczny autoloader dla Domain
|
||||
// Ręczny autoloader PSR-4 dla autoload/
|
||||
spl_autoload_register(function ($class) {
|
||||
$prefix = 'Domain\\';
|
||||
$baseDir = __DIR__ . '/../autoload/Domain/';
|
||||
$prefixes = [
|
||||
'Domain\\' => __DIR__ . '/../autoload/Domain/',
|
||||
'admin\\Controllers\\' => __DIR__ . '/../autoload/admin/Controllers/',
|
||||
];
|
||||
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$relativeClass = substr($class, $len);
|
||||
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
||||
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
foreach ($prefixes as $prefix => $baseDir) {
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) === 0) {
|
||||
$relativeClass = substr($class, $len);
|
||||
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -31,3 +33,44 @@ require_once __DIR__ . '/../libraries/medoo/medoo.php';
|
||||
|
||||
// Ustaw timezone
|
||||
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 />
|
||||
- NEW - refaktoryzacja: Domain\Product\ProductRepository - pierwsza klasa w nowej architekturze Domain-Driven
|
||||
- NEW - Dependency Injection zamiast global variables
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?
|
||||
$current_ver = 238;
|
||||
$current_ver = 240;
|
||||
|
||||
for ($i = 1; $i <= $current_ver; $i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user