diff --git a/.phpunit.result.cache b/.phpunit.result.cache
index 32f3e1b..7917679 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.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
+{"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,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedFilesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedImagesDeletesDbRows":0}}
\ No newline at end of file
diff --git a/DATABASE_STRUCTURE.md b/DATABASE_STRUCTURE.md
index dbfeded..462452c 100644
--- a/DATABASE_STRUCTURE.md
+++ b/DATABASE_STRUCTURE.md
@@ -103,7 +103,7 @@ Strony artykułów.
| page_id | FK do strony (pp_pages) |
| o | Kolejność |
-**Używane w:** `Domain\Article\ArticleRepository::find()`
+**Używane w:** `Domain\Article\ArticleRepository::find()`, `Domain\Article\ArticleRepository::deleteNonassignedImages()`
## pp_articles_langs
Tłumaczenia artykułów.
@@ -115,7 +115,7 @@ Tłumaczenia artykułów.
| title | Tytuł artykułu |
| seo_link | Link SEO artykułu |
-**Używane w:** `Domain\Article\ArticleRepository::find()`
+**Używane w:** `Domain\Article\ArticleRepository::find()`, `Domain\Article\ArticleRepository::deleteNonassignedFiles()`
## pp_articles_images
Zdjęcia artykułów.
diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md
index 5d36a51..b0132ee 100644
--- a/PROJECT_STRUCTURE.md
+++ b/PROJECT_STRUCTURE.md
@@ -246,10 +246,16 @@ tests/
│ └── ProductArchiveControllerTest.php # 6 testów
└── Integration/
```
-**Łącznie: 48 testw, 91 asercji**
+**Łącznie: 50 tests, 95 assertions**
## Ostatnie modyfikacje
+### 2026-02-06: Articles cleanup moved to repository (ver. 0.243)
+- **UPDATE:** `Domain\Article\ArticleRepository` - added `deleteNonassignedImages()` and `deleteNonassignedFiles()`
+- **UPDATE:** `admin\Controllers\ArticlesController::edit()` uses repository cleanup methods
+- **UPDATE:** `admin\factory\Articles::delete_nonassigned_images()` and `delete_nonassigned_files()` delegate to repository (backward compatibility)
+- Testy: 50 tests, 95 assertions
+
### 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
@@ -266,7 +272,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: 48 testw, 91 asercji (+10 nowych)
+- Testy: 50 tests, 95 assertions (+10 nowych)
### 2026-02-05: Migracja Settings + Cache (ver. 0.240)
- **NOWE:** `Domain\Settings\SettingsRepository` - repozytorium ustawień (fasada → factory)
diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md
index d177f31..80fdf23 100644
--- a/REFACTORING_PLAN.md
+++ b/REFACTORING_PLAN.md
@@ -181,8 +181,8 @@ grep -r "Product::getQuantity" .
- 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
+ - Testy repozytorium rozszerzone o czyszczenie nieprzypisanych plikw/zdj
+ - Aktualizacja: ver. 0.243
- **Settings** (migracja kontrolera - krok pośredni)
- ✅ SettingsRepository - **ZMIGROWANE** (2026-02-05) 🎉
@@ -315,4 +315,4 @@ vendor/bin/phpstan analyse autoload/Domain
---
*Rozpoczęto: 2025-02-05*
-*Ostatnia aktualizacja: 2026-02-06*
+*Ostatnia aktualizacja: 2026-02-06*
\ No newline at end of file
diff --git a/UPDATE_INSTRUCTIONS.md b/UPDATE_INSTRUCTIONS.md
index cfff524..790d973 100644
--- a/UPDATE_INSTRUCTIONS.md
+++ b/UPDATE_INSTRUCTIONS.md
@@ -13,6 +13,7 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią
### Zasada pakowania plików
- Do paczek aktualizacji **nie dodajemy plików `*.md`** (dokumentacja jest tylko wewnętrzna/deweloperska).
+- Do paczek aktualizacji **nie dodajemy `updates/changelog.php`** (to plik serwisowy po stronie repozytorium aktualizacji, nie runtime klienta).
## Procedura tworzenia nowej aktualizacji
diff --git a/autoload/Domain/Article/ArticleRepository.php b/autoload/Domain/Article/ArticleRepository.php
index 9a986d5..de36849 100644
--- a/autoload/Domain/Article/ArticleRepository.php
+++ b/autoload/Domain/Article/ArticleRepository.php
@@ -40,4 +40,38 @@ class ArticleRepository
return $article;
}
+
+ /**
+ * Usuwa nieprzypisane pliki artykulow (article_id = null) wraz z plikami z dysku.
+ */
+ public function deleteNonassignedFiles(): void
+ {
+ $results = $this->db->select('pp_articles_files', '*', ['article_id' => null]);
+ if (is_array($results)) {
+ foreach ($results as $row) {
+ if (file_exists('../' . $row['src'])) {
+ unlink('../' . $row['src']);
+ }
+ }
+ }
+
+ $this->db->delete('pp_articles_files', ['article_id' => null]);
+ }
+
+ /**
+ * Usuwa nieprzypisane zdjecia artykulow (article_id = null) wraz z plikami z dysku.
+ */
+ public function deleteNonassignedImages(): void
+ {
+ $results = $this->db->select('pp_articles_images', '*', ['article_id' => null]);
+ if (is_array($results)) {
+ foreach ($results as $row) {
+ if (file_exists('../' . $row['src'])) {
+ unlink('../' . $row['src']);
+ }
+ }
+ }
+
+ $this->db->delete('pp_articles_images', ['article_id' => null]);
+ }
}
diff --git a/autoload/admin/Controllers/ArticlesController.php b/autoload/admin/Controllers/ArticlesController.php
index 8bf6eb5..affee8a 100644
--- a/autoload/admin/Controllers/ArticlesController.php
+++ b/autoload/admin/Controllers/ArticlesController.php
@@ -32,8 +32,8 @@ class ArticlesController
exit;
}
- \admin\factory\Articles::delete_nonassigned_images();
- \admin\factory\Articles::delete_nonassigned_files();
+ $this->repository->deleteNonassignedImages();
+ $this->repository->deleteNonassignedFiles();
return \Tpl::view('articles/article-edit', [
'article' => $this->repository->find((int)\S::get('id')),
diff --git a/autoload/admin/factory/class.Articles.php b/autoload/admin/factory/class.Articles.php
index ee82309..605c53b 100644
--- a/autoload/admin/factory/class.Articles.php
+++ b/autoload/admin/factory/class.Articles.php
@@ -433,29 +433,15 @@ class Articles
public static function delete_nonassigned_files()
{
global $mdb;
-
- $results = $mdb -> select( 'pp_articles_files', '*', [ 'article_id' => null ] );
- if ( is_array( $results ) ) foreach ( $results as $row )
- {
- if ( file_exists( '../' . $row['src'] ) )
- unlink( '../' . $row['src'] );
- }
-
- $mdb -> delete( 'pp_articles_files', [ 'article_id' => null ] );
+ $repository = new \Domain\Article\ArticleRepository( $mdb );
+ $repository->deleteNonassignedFiles();
}
public static function delete_nonassigned_images()
{
global $mdb;
-
- $results = $mdb -> select( 'pp_articles_images', '*', [ 'article_id' => null ] );
- if ( is_array( $results ) ) foreach ( $results as $row )
- {
- if ( file_exists( '../' . $row['src'] ) )
- unlink( '../' . $row['src'] );
- }
-
- $mdb -> delete( 'pp_articles_images', [ 'article_id' => null ] );
+ $repository = new \Domain\Article\ArticleRepository( $mdb );
+ $repository->deleteNonassignedImages();
}
}
?>
diff --git a/tests/Unit/Domain/Article/ArticleRepositoryTest.php b/tests/Unit/Domain/Article/ArticleRepositoryTest.php
index 749f09a..b040292 100644
--- a/tests/Unit/Domain/Article/ArticleRepositoryTest.php
+++ b/tests/Unit/Domain/Article/ArticleRepositoryTest.php
@@ -57,4 +57,46 @@ class ArticleRepositoryTest extends TestCase
$this->assertNull($article);
}
+
+ public function testDeleteNonassignedFilesDeletesDbRows(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+
+ $mockDb->expects($this->once())
+ ->method('select')
+ ->with('pp_articles_files', '*', ['article_id' => null])
+ ->willReturn([
+ ['id' => 1, 'src' => '/this/path/does/not/exist-file.tmp']
+ ]);
+
+ $mockDb->expects($this->once())
+ ->method('delete')
+ ->with('pp_articles_files', ['article_id' => null]);
+
+ $repository = new ArticleRepository($mockDb);
+ $repository->deleteNonassignedFiles();
+
+ $this->assertTrue(true);
+ }
+
+ public function testDeleteNonassignedImagesDeletesDbRows(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+
+ $mockDb->expects($this->once())
+ ->method('select')
+ ->with('pp_articles_images', '*', ['article_id' => null])
+ ->willReturn([
+ ['id' => 1, 'src' => '/this/path/does/not/exist-image.tmp']
+ ]);
+
+ $mockDb->expects($this->once())
+ ->method('delete')
+ ->with('pp_articles_images', ['article_id' => null]);
+
+ $repository = new ArticleRepository($mockDb);
+ $repository->deleteNonassignedImages();
+
+ $this->assertTrue(true);
+ }
}
diff --git a/updates/0.20/ver_0.243.zip b/updates/0.20/ver_0.243.zip
new file mode 100644
index 0000000..6557f56
Binary files /dev/null and b/updates/0.20/ver_0.243.zip differ
diff --git a/updates/changelog.php b/updates/changelog.php
index 3b46194..c752bac 100644
--- a/updates/changelog.php
+++ b/updates/changelog.php
@@ -1,7 +1,10 @@
-ver. 0.242
+ver. 0.243
+- UPDATE - refaktoryzacja: cleanup nieprzypisanych plików/zdjęć artykułów przeniesiony do Domain\Article\ArticleRepository
+- UPDATE - ArticlesController::edit() używa repozytorium do cleanupu, a admin\factory\Articles zachowuje delegowanie (kompatybilność)
+