diff --git a/.phpunit.result.cache b/.phpunit.result.cache
index 270bb78..32f3e1b 100644
--- a/.phpunit.result.cache
+++ b/.phpunit.result.cache
@@ -1 +1 @@
-{"version":1,"defects":[],"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.001,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsPromoPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularWhenPromoIsHigher":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsProductName":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveReturnsBool":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveReturnsBool":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasUnarchiveMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testUnarchiveMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorRequiresProductRepository":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsBannerWithTranslations":0.004,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testDeleteReturnsTrue":0.002,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveInsertsNewBanner":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithRedis":0.001,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheRedisUnavailable":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithoutRedis":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheReturnStructure":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testCanBeInstantiated":0.001,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasSaveSettingsMethod":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasGetSettingsMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheAjaxMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasViewMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testIsNotAbstract":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testActionMethodReturnTypes":0}}
\ No newline at end of file
+{"version":1,"defects":[],"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.001,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsPromoPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularWhenPromoIsHigher":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsProductName":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveReturnsBool":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveReturnsBool":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasUnarchiveMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testUnarchiveMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorRequiresProductRepository":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsBannerWithTranslations":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testDeleteReturnsTrue":0.002,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveInsertsNewBanner":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithRedis":0.001,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheRedisUnavailable":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithoutRedis":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheReturnStructure":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testCanBeInstantiated":0.001,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasSaveSettingsMethod":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasGetSettingsMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheAjaxMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasViewMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testIsNotAbstract":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testCanCreateController":0.001,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testEditMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsArticleWithRelations":0.003,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsNullWhenArticleDoesNotExist":0}}
\ No newline at end of file
diff --git a/DATABASE_STRUCTURE.md b/DATABASE_STRUCTURE.md
index e76aa50..dbfeded 100644
--- a/DATABASE_STRUCTURE.md
+++ b/DATABASE_STRUCTURE.md
@@ -92,16 +92,49 @@ Artykuły.
| id | PK |
| status | -1 = archiwum, 0 = nieaktywny, 1 = aktywny |
-**Używane w:** `admin\controls\ArticlesArchive`
+**Używane w:** `admin\controls\ArticlesArchive`, `Domain\Article\ArticleRepository::find()`
## pp_articles_pages
Strony artykułów.
+| Kolumna | Opis |
+|---------|------|
+| article_id | FK do pp_articles |
+| page_id | FK do strony (pp_pages) |
+| o | Kolejność |
+
+**Używane w:** `Domain\Article\ArticleRepository::find()`
+
## pp_articles_langs
Tłumaczenia artykułów.
+| Kolumna | Opis |
+|---------|------|
+| article_id | FK do pp_articles |
+| lang_id | ID języka (np. 'pl') |
+| title | Tytuł artykułu |
+| seo_link | Link SEO artykułu |
+
+**Używane w:** `Domain\Article\ArticleRepository::find()`
+
## pp_articles_images
Zdjęcia artykułów.
+| Kolumna | Opis |
+|---------|------|
+| article_id | FK do pp_articles |
+| src | Ścieżka do pliku |
+| o | Kolejność |
+| id | PK (używane też do sortowania DESC) |
+
+**Używane w:** `Domain\Article\ArticleRepository::find()`
+
## pp_articles_files
Pliki artykułów.
+
+| Kolumna | Opis |
+|---------|------|
+| article_id | FK do pp_articles |
+| src | Ścieżka do pliku |
+
+**Używane w:** `Domain\Article\ArticleRepository::find()`
diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md
index 4729cb1..5d36a51 100644
--- a/PROJECT_STRUCTURE.md
+++ b/PROJECT_STRUCTURE.md
@@ -246,10 +246,18 @@ tests/
│ └── ProductArchiveControllerTest.php # 6 testów
└── Integration/
```
-**Łącznie: 39 testów, 73 asercji**
+**Łącznie: 48 testw, 91 asercji**
## Ostatnie modyfikacje
+### 2026-02-06: Migracja Articles::article_edit do DI (ver. 0.242)
+- **NOWE:** `Domain\Article\ArticleRepository` - repozytorium artykułów (`find()`)
+- **UPDATE:** `admin\Controllers\ArticlesController` - konstruktor DI + `edit()` używa repozytorium
+- **UPDATE:** Router `admin\Site` - factory dla `ArticlesController` z `ArticleRepository`
+- **UPDATE:** `admin\factory\Articles::article_details()` deleguje do `Domain\Article\ArticleRepository`
+- **UPDATE:** Stare kontrolery `admin\controls\Articles|Banners|Settings` - metody przejęte przez nowe kontrolery oznaczone `@deprecated`
+- Testy: 48 testów, 91 asercji
+
### 2026-02-06: Migracja ProductArchive (ver. 0.241)
- **NOWE:** `admin\Controllers\ProductArchiveController` - kontroler archiwum produktów z DI
- **NOWE:** `ProductRepository::archive()`, `unarchive()` - operacje archiwizacji w repozytorium
@@ -258,7 +266,7 @@ tests/
- **FIX:** Brakujący `archive = 1` w branchu bez wyszukiwania
- **CLEANUP:** Usunięto zbędny JS z szablonu archiwum (apilo, baselinker, duplikowanie, edycja cen)
- Stary kontroler `admin\controls\Archive` zachowany jako fallback
-- Testy: 39 testów, 73 asercji (+10 nowych)
+- Testy: 48 testw, 91 asercji (+10 nowych)
### 2026-02-05: Migracja Settings + Cache (ver. 0.240)
- **NOWE:** `Domain\Settings\SettingsRepository` - repozytorium ustawień (fasada → factory)
@@ -293,4 +301,5 @@ tests/
- Metoda `clear_product_cache()` w klasie S
---
-*Dokument aktualizowany: 2026-02-05*
+*Dokument aktualizowany: 2026-02-06*
+
diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md
index 13e5167..d177f31 100644
--- a/REFACTORING_PLAN.md
+++ b/REFACTORING_PLAN.md
@@ -174,6 +174,16 @@ grep -r "Product::getQuantity" .
- Stara factory `admin\factory\Banners` zachowana bez zmian (fallback)
- Aktualizacja: ver. 0.239
+- **Articles** (migracja kontrolera - etap edit/details)
+ - ✅ ArticleRepository::find() - **ZMIGROWANE** (2026-02-06) 🎉
+ - Nowa klasa: `Domain\Article\ArticleRepository` (find: artykul + relacje)
+ - Nowy kontroler: `admin\Controllers\ArticlesController` (DI, instancyjny)
+ - Zmigrowana akcja: `article_edit` -> `edit` (mapowanie w `admin\Site::$actionMap`)
+ - Kompatybilność: `admin\factory\Articles::article_details()` deleguje do nowego repozytorium
+ - Legacy cleanup: metody przejęte przez nowe kontrolery oznaczone `@deprecated` w `admin\controls\Articles|Banners|Settings`
+ - Testy: `tests/Unit/Domain/Article/ArticleRepositoryTest.php`, `tests/Unit/admin/Controllers/ArticlesControllerTest.php`
+ - Aktualizacja: ver. 0.242
+
- **Settings** (migracja kontrolera - krok pośredni)
- ✅ SettingsRepository - **ZMIGROWANE** (2026-02-05) 🎉
- Nowa klasa: `Domain\Settings\SettingsRepository` (saveSettings, getSettings)
@@ -217,7 +227,7 @@ tests/
│ └── ProductArchiveControllerTest.php # 6 testów
└── Integration/
```
-**Łącznie: 39 testów, 73 asercji**
+**Łącznie: 48 testów, 91 asercji**
### Przykład testu
```php
@@ -305,4 +315,4 @@ vendor/bin/phpstan analyse autoload/Domain
---
*Rozpoczęto: 2025-02-05*
-*Ostatnia aktualizacja: 2026-02-05*
+*Ostatnia aktualizacja: 2026-02-06*
diff --git a/UPDATE_INSTRUCTIONS.md b/UPDATE_INSTRUCTIONS.md
index 70af940..cfff524 100644
--- a/UPDATE_INSTRUCTIONS.md
+++ b/UPDATE_INSTRUCTIONS.md
@@ -11,6 +11,9 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią
- `changelog.php` - historia zmian
- `versions.php` - konfiguracja wersji (zmienna `$current_ver`)
+### Zasada pakowania plików
+- Do paczek aktualizacji **nie dodajemy plików `*.md`** (dokumentacja jest tylko wewnętrzna/deweloperska).
+
## Procedura tworzenia nowej aktualizacji
### 1. Określ numer wersji
diff --git a/autoload/Domain/Article/ArticleRepository.php b/autoload/Domain/Article/ArticleRepository.php
new file mode 100644
index 0000000..9a986d5
--- /dev/null
+++ b/autoload/Domain/Article/ArticleRepository.php
@@ -0,0 +1,43 @@
+db = $db;
+ }
+
+ /**
+ * Pobiera artykul po ID wraz z tlumaczeniami, obrazami, plikami i powiazanymi stronami
+ */
+ public function find(int $articleId): ?array
+ {
+ $article = $this->db->get('pp_articles', '*', ['id' => $articleId]);
+
+ if (!$article) {
+ return null;
+ }
+
+ $results = $this->db->select('pp_articles_langs', '*', ['article_id' => $articleId]);
+ if (is_array($results)) {
+ foreach ($results as $row) {
+ $article['languages'][$row['lang_id']] = $row;
+ }
+ }
+
+ $article['images'] = $this->db->select('pp_articles_images', '*', [
+ 'article_id' => $articleId,
+ 'ORDER' => ['o' => 'ASC', 'id' => 'DESC']
+ ]);
+ $article['files'] = $this->db->select('pp_articles_files', '*', ['article_id' => $articleId]);
+ $article['pages'] = $this->db->select('pp_articles_pages', 'page_id', ['article_id' => $articleId]);
+
+ return $article;
+ }
+}
diff --git a/autoload/admin/Controllers/ArticlesController.php b/autoload/admin/Controllers/ArticlesController.php
new file mode 100644
index 0000000..8bf6eb5
--- /dev/null
+++ b/autoload/admin/Controllers/ArticlesController.php
@@ -0,0 +1,46 @@
+repository = $repository;
+ }
+
+ /**
+ * Lista artykulow
+ */
+ public function list(): string
+ {
+ return \admin\view\Articles::articles_list();
+ }
+
+ /**
+ * Edycja artykulu
+ */
+ public function edit(): string
+ {
+ global $user;
+
+ if (!$user) {
+ header('Location: /admin/');
+ exit;
+ }
+
+ \admin\factory\Articles::delete_nonassigned_images();
+ \admin\factory\Articles::delete_nonassigned_files();
+
+ return \Tpl::view('articles/article-edit', [
+ 'article' => $this->repository->find((int)\S::get('id')),
+ 'menus' => \admin\factory\Pages::menus_list(),
+ 'languages' => \admin\factory\Languages::languages_list(),
+ 'layouts' => \admin\factory\Layouts::layouts_list(),
+ 'user' => $user
+ ]);
+ }
+}
diff --git a/autoload/admin/class.Site.php b/autoload/admin/class.Site.php
index a3a8aa5..1c2b48a 100644
--- a/autoload/admin/class.Site.php
+++ b/autoload/admin/class.Site.php
@@ -199,6 +199,13 @@ class Site
return self::$newControllers;
self::$newControllers = [
+ 'Articles' => function() {
+ global $mdb;
+
+ return new \admin\Controllers\ArticlesController(
+ new \Domain\Article\ArticleRepository( $mdb )
+ );
+ },
'Banners' => function() {
global $mdb;
@@ -247,6 +254,7 @@ class Site
*/
private static $actionMap = [
'view_list' => 'list',
+ 'article_edit' => 'edit',
'banner_edit' => 'edit',
'banner_save' => 'save',
'banner_delete' => 'delete',
diff --git a/autoload/admin/controls/class.Articles.php b/autoload/admin/controls/class.Articles.php
index a7e32b3..b2fddb7 100644
--- a/autoload/admin/controls/class.Articles.php
+++ b/autoload/admin/controls/class.Articles.php
@@ -39,6 +39,10 @@ class Articles
exit;
}
+ /**
+ * @deprecated Routing kieruje do admin\Controllers\ArticlesController::edit().
+ * Ta metoda pozostaje tylko jako fallback dla starej architektury.
+ */
public static function article_edit() {
global $user;
@@ -59,9 +63,13 @@ class Articles
] );
}
+ /**
+ * @deprecated Routing kieruje do admin\Controllers\ArticlesController::list().
+ * Ta metoda pozostaje tylko jako fallback dla starej architektury.
+ */
public static function view_list()
{
return \admin\view\Articles::articles_list();
}
}
-?>
\ No newline at end of file
+?>
diff --git a/autoload/admin/controls/class.Banners.php b/autoload/admin/controls/class.Banners.php
index ccce70a..9166a66 100644
--- a/autoload/admin/controls/class.Banners.php
+++ b/autoload/admin/controls/class.Banners.php
@@ -9,6 +9,10 @@ namespace admin\controls;
*/
class Banners
{
+ /**
+ * @deprecated Routing kieruje do admin\Controllers\BannerController::delete().
+ * Ta metoda pozostaje tylko jako fallback dla starej architektury.
+ */
public static function banner_delete()
{
if ( \admin\factory\Banners::banner_delete( \S::get( 'id' ) ) )
@@ -17,6 +21,10 @@ class Banners
exit;
}
+ /**
+ * @deprecated Routing kieruje do admin\Controllers\BannerController::save().
+ * Ta metoda pozostaje tylko jako fallback dla starej architektury.
+ */
public static function banner_save()
{
$response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania baneru wystąpił błąd. Proszę spróbować ponownie.' ];
@@ -30,6 +38,10 @@ class Banners
exit;
}
+ /**
+ * @deprecated Routing kieruje do admin\Controllers\BannerController::edit().
+ * Ta metoda pozostaje tylko jako fallback dla starej architektury.
+ */
public static function banner_edit()
{
return \admin\view\Banners::banner_edit(
@@ -40,6 +52,10 @@ class Banners
);
}
+ /**
+ * @deprecated Routing kieruje do admin\Controllers\BannerController::list().
+ * Ta metoda pozostaje tylko jako fallback dla starej architektury.
+ */
public static function view_list()
{
return \admin\view\Banners::banners_list();
diff --git a/autoload/admin/controls/class.Settings.php b/autoload/admin/controls/class.Settings.php
index f11b666..63d71dd 100644
--- a/autoload/admin/controls/class.Settings.php
+++ b/autoload/admin/controls/class.Settings.php
@@ -3,6 +3,10 @@ namespace admin\controls;
class Settings
{
+ /**
+ * @deprecated Routing kieruje do admin\Controllers\SettingsController::clearCache().
+ * Ta metoda pozostaje tylko jako fallback dla starej architektury.
+ */
static public function clear_cache()
{
\S::delete_dir( '../temp/' );
@@ -17,6 +21,10 @@ class Settings
exit;
}
+ /**
+ * @deprecated Routing kieruje do admin\Controllers\SettingsController::clearCacheAjax().
+ * Ta metoda pozostaje tylko jako fallback dla starej architektury.
+ */
static public function clear_cache_ajax()
{
try
@@ -41,6 +49,10 @@ class Settings
exit;
}
+ /**
+ * @deprecated Routing kieruje do admin\Controllers\SettingsController::save().
+ * Ta metoda pozostaje tylko jako fallback dla starej architektury.
+ */
public static function settings_save()
{
$values = json_decode( \S::get( 'values' ), true );
@@ -88,6 +100,10 @@ class Settings
exit;
}
+ /**
+ * @deprecated Routing kieruje do admin\Controllers\SettingsController::view().
+ * Ta metoda pozostaje tylko jako fallback dla starej architektury.
+ */
public static function view()
{
return \Tpl::view( 'settings/settings', [
@@ -96,4 +112,4 @@ class Settings
] );
}
}
-?>
\ No newline at end of file
+?>
diff --git a/autoload/admin/factory/class.Articles.php b/autoload/admin/factory/class.Articles.php
index 7f748ed..ee82309 100644
--- a/autoload/admin/factory/class.Articles.php
+++ b/autoload/admin/factory/class.Articles.php
@@ -123,19 +123,8 @@ class Articles
public static function article_details( $article_id )
{
global $mdb;
-
- if ( $article = $mdb -> get( 'pp_articles', '*', [ 'id' => (int)$article_id ] ) )
- {
- $results = $mdb -> select( 'pp_articles_langs', '*', [ 'article_id' => (int)$article_id ] );
- if ( is_array( $results ) ) foreach ( $results as $row )
- $article['languages'][ $row['lang_id'] ] = $row;
-
- $article['images'] = $mdb -> select( 'pp_articles_images', '*', [ 'article_id' => (int)$article_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'DESC' ] ] );
- $article['files'] = $mdb -> select( 'pp_articles_files', '*', [ 'article_id' => (int)$article_id ] );
- $article['pages'] = $mdb -> select( 'pp_articles_pages', 'page_id', [ 'article_id' => (int)$article_id ] );
- }
-
- return $article;
+ $repository = new \Domain\Article\ArticleRepository( $mdb );
+ return $repository->find( (int)$article_id );
}
public static function max_order()
@@ -469,4 +458,4 @@ class Articles
$mdb -> delete( 'pp_articles_images', [ 'article_id' => null ] );
}
}
-?>
\ No newline at end of file
+?>
diff --git a/tests/Unit/Domain/Article/ArticleRepositoryTest.php b/tests/Unit/Domain/Article/ArticleRepositoryTest.php
new file mode 100644
index 0000000..749f09a
--- /dev/null
+++ b/tests/Unit/Domain/Article/ArticleRepositoryTest.php
@@ -0,0 +1,60 @@
+createMock(\medoo::class);
+
+ $mockDb->expects($this->once())
+ ->method('get')
+ ->with('pp_articles', '*', ['id' => 7])
+ ->willReturn(['id' => 7, 'status' => 1]);
+
+ $mockDb->expects($this->exactly(4))
+ ->method('select')
+ ->willReturnOnConsecutiveCalls(
+ [
+ ['lang_id' => 'pl', 'title' => 'Artykul'],
+ ['lang_id' => 'en', 'title' => 'Article'],
+ ],
+ [
+ ['id' => 10, 'src' => '/img/a.jpg']
+ ],
+ [
+ ['id' => 20, 'src' => '/files/a.pdf']
+ ],
+ [1, 2]
+ );
+
+ $repository = new ArticleRepository($mockDb);
+ $article = $repository->find(7);
+
+ $this->assertIsArray($article);
+ $this->assertEquals(7, $article['id']);
+ $this->assertArrayHasKey('languages', $article);
+ $this->assertEquals('Artykul', $article['languages']['pl']['title']);
+ $this->assertCount(1, $article['images']);
+ $this->assertCount(1, $article['files']);
+ $this->assertEquals([1, 2], $article['pages']);
+ }
+
+ public function testFindReturnsNullWhenArticleDoesNotExist(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->once())
+ ->method('get')
+ ->with('pp_articles', '*', ['id' => 999])
+ ->willReturn(false);
+ $mockDb->expects($this->never())->method('select');
+
+ $repository = new ArticleRepository($mockDb);
+ $article = $repository->find(999);
+
+ $this->assertNull($article);
+ }
+}
diff --git a/tests/Unit/admin/Controllers/ArticlesControllerTest.php b/tests/Unit/admin/Controllers/ArticlesControllerTest.php
new file mode 100644
index 0000000..4350cfb
--- /dev/null
+++ b/tests/Unit/admin/Controllers/ArticlesControllerTest.php
@@ -0,0 +1,61 @@
+mockRepository = $this->createMock(ArticleRepository::class);
+ $this->controller = new ArticlesController($this->mockRepository);
+ }
+
+ public function testCanCreateController(): void
+ {
+ $this->assertInstanceOf(ArticlesController::class, $this->controller);
+ }
+
+ public function testConstructorAcceptsRepository(): void
+ {
+ $controller = new ArticlesController($this->mockRepository);
+ $this->assertInstanceOf(ArticlesController::class, $controller);
+ }
+
+ public function testHasListMethod(): void
+ {
+ $this->assertTrue(method_exists($this->controller, 'list'));
+ }
+
+ public function testHasEditMethod(): void
+ {
+ $this->assertTrue(method_exists($this->controller, 'edit'));
+ }
+
+ public function testListMethodReturnType(): void
+ {
+ $reflection = new \ReflectionClass($this->controller);
+ $this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType());
+ }
+
+ public function testEditMethodReturnType(): void
+ {
+ $reflection = new \ReflectionClass($this->controller);
+ $this->assertEquals('string', (string)$reflection->getMethod('edit')->getReturnType());
+ }
+
+ public function testConstructorRequiresArticleRepository(): void
+ {
+ $reflection = new \ReflectionClass(ArticlesController::class);
+ $constructor = $reflection->getConstructor();
+ $params = $constructor->getParameters();
+
+ $this->assertCount(1, $params);
+ $this->assertEquals('Domain\Article\ArticleRepository', $params[0]->getType()->getName());
+ }
+}
diff --git a/updates/0.20/ver_0.242.zip b/updates/0.20/ver_0.242.zip
new file mode 100644
index 0000000..0defbd7
Binary files /dev/null and b/updates/0.20/ver_0.242.zip differ
diff --git a/updates/changelog.php b/updates/changelog.php
index 2dd1c0a..3b46194 100644
--- a/updates/changelog.php
+++ b/updates/changelog.php
@@ -1,4 +1,8 @@
-ver. 0.241
+ver. 0.242
+- NEW - refaktoryzacja: Domain\Article\ArticleRepository + migracja article_edit do admin\Controllers\ArticlesController (DI)
+- UPDATE - admin\factory\Articles::article_details() deleguje do nowego repozytorium (kompatybilno zachowana)
+- UPDATE - metody przejte przez nowe kontrolery oznaczone jako @deprecated w legacy kontrolerach admin\controls
+