diff --git a/DATABASE_STRUCTURE.md b/DATABASE_STRUCTURE.md index ca26eb5..cd858d0 100644 --- a/DATABASE_STRUCTURE.md +++ b/DATABASE_STRUCTURE.md @@ -92,7 +92,7 @@ Artykuły. | id | PK | | status | -1 = archiwum, 0 = nieaktywny, 1 = aktywny | -**Używane w:** `admin\controls\ArticlesArchive`, `Domain\Article\ArticleRepository::find()` +**Używane w:** `admin\Controllers\ArticlesArchiveController`, `Domain\Article\ArticleRepository::find()`, `Domain\Article\ArticleRepository::listArchivedForAdmin()` ## pp_articles_pages Strony artykułów. @@ -287,3 +287,31 @@ Szablony tresci e-maili (uzytkownik + administracyjne/systemowe). **Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `admin\\Controllers\\NewsletterController`, `front\\factory\\Newsletter` **Aktualizacja 2026-02-12 (ver. 0.257):** modul `/admin/newsletter` korzysta z `Domain\\Newsletter\\NewsletterRepository` (DI kontroler + fasada legacy). + +## pp_scontainers +Kontenery statyczne (modul /admin/scontainers). + +| Kolumna | Opis | +|---------|------| +| id | PK | +| status | 1 = aktywny, 0 = nieaktywny | +| show_title | 1 = pokaz tytul, 0 = ukryj tytul | + +**Uzywane w:** `Domain\Scontainers\ScontainersRepository`, `admin\Controllers\ScontainersController`, `front\factory\Scontainers` + +## pp_scontainers_langs +Tlumaczenia kontenerow statycznych (per jezyk). + +| Kolumna | Opis | +|---------|------| +| id | PK | +| container_id | FK do pp_scontainers | +| lang_id | ID jezyka (np. pl, en) | +| title | Tytul kontenera | +| text | Tresc HTML kontenera | + +**Uzywane w:** `Domain\Scontainers\ScontainersRepository`, `front\factory\Scontainers` + +**Aktualizacja 2026-02-12 (ver. 0.259):** modul `/admin/scontainers` korzysta z `Domain\Scontainers\ScontainersRepository` (DI kontroler + fasada legacy). + +**Aktualizacja 2026-02-12 (ver. 0.260):** modul `/admin/articles_archive` korzysta z `Domain\Article\ArticleRepository` (`listArchivedForAdmin`, `restore`, `deletePermanently`) przez `admin\Controllers\ArticlesArchiveController`. diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 1f9a596..738c505 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -437,3 +437,21 @@ Aktualnie w suite są też testy modułów `Dictionaries`, `Articles` i `Users` - UPDATE: modul `/admin/newsletter/` - tymczasowo wylaczono liste `email_templates_user` (Szablony uzytkownika). - UPDATE: lista i edycja szablonow newslettera w panelu ograniczona do szablonow administracyjnych (`is_admin = 1`). - CLEANUP: usuniete nieuzywane widoki: `admin/templates/newsletter/prepare.php`, `admin/templates/newsletter/preview.php`, `admin/templates/newsletter/email-templates-user.php`. + +## Aktualizacja 2026-02-12 (ver. 0.259) +- NOWE: `Domain\Scontainers\ScontainersRepository` (listForAdmin/find/save/delete/detailsForLanguage + czyszczenie cache frontu). +- NOWE: `admin\Controllers\ScontainersController` (DI) dla akcji `list/view_list`, `container_edit`, `container_save`, `container_delete`. +- UPDATE: `/admin/scontainers/*` przepiete z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`. +- UPDATE: `admin\Site` ma fabryke DI dla modulu `Scontainers` oraz mapowanie akcji `container_*` -> `edit/save/delete`. +- UPDATE: `admin\factory\Scontainers` dziala jako fasada do `Domain\Scontainers\ScontainersRepository`. +- UPDATE: `front\factory\Scontainers` korzysta z `Domain\Scontainers\ScontainersRepository`. +- CLEANUP: usuniete legacy klasy `autoload/admin/controls/class.Scontainers.php`, `autoload/admin/view/class.Scontainers.php`. +- Testy: 158 tests, 397 assertions. + +## Aktualizacja 2026-02-12 (ver. 0.260) +- NOWE: `Domain\Article\ArticleRepository` rozszerzone o `listArchivedForAdmin()`, `restore()`, `deletePermanently()`. +- NOWE: `admin\Controllers\ArticlesArchiveController` (DI) dla akcji `list/view_list`, `article_restore`, `article_delete`. +- UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ArticlesArchive` oraz mapowanie akcji `article_restore -> restore`. +- UPDATE: `/admin/articles_archive/view_list/` przepiete z legacy `grid` na `components/table-list`. +- CLEANUP: usuniete legacy klasy `autoload/admin/controls/class.ArticlesArchive.php`, `autoload/admin/factory/class.ArticlesArchive.php`, `autoload/admin/view/class.ArticlesArchive.php`. +- Testy: 165 tests, 424 assertions. diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md index 4d0de26..8e48b33 100644 --- a/REFACTORING_PLAN.md +++ b/REFACTORING_PLAN.md @@ -634,3 +634,23 @@ Gdy `persist = true`: - UPDATE: tymczasowo wylaczono modul `Szablony uzytkownika`. - UPDATE: aktywna obsluga tylko szablonow administracyjnych (`is_admin = 1`). - CLEANUP: usuniete nieuzywane widoki `prepare.php`, `preview.php`, `email-templates-user.php`. + +## Aktualizacja 2026-02-12 (ver. 0.259) +- **Scontainers** - **ZMIGROWANE** (2026-02-12) + - NOWE: `Domain\Scontainers\ScontainersRepository` (listForAdmin, find, save, delete, detailsForLanguage) + - NOWE: `admin\Controllers\ScontainersController` (DI) + - UPDATE: `/admin/scontainers/view_list/` migrowane na `components/table-list` + - UPDATE: `/admin/scontainers/container_edit/` migrowane na `components/form-edit` + - UPDATE: `admin\factory\Scontainers` jako fasada do repozytorium + - UPDATE: `front\factory\Scontainers` korzysta z repozytorium domenowego + - CLEANUP: usuniete `autoload/admin/controls/class.Scontainers.php` i `autoload/admin/view/class.Scontainers.php` +- Testy po zmianie: **158 tests, 397 assertions** + +## Aktualizacja 2026-02-12 (ver. 0.260) +- **ArticlesArchive** - **ZMIGROWANE** (2026-02-12) + - NOWE: `admin\Controllers\ArticlesArchiveController` (DI) + - UPDATE: `Domain\Article\ArticleRepository` rozszerzone o `listArchivedForAdmin()`, `restore()`, `deletePermanently()` + - UPDATE: `/admin/articles_archive/view_list/` migrowane na `components/table-list` + - UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ArticlesArchive` + mapowanie `article_restore -> restore` + - CLEANUP: usuniete `autoload/admin/controls/class.ArticlesArchive.php`, `autoload/admin/factory/class.ArticlesArchive.php`, `autoload/admin/view/class.ArticlesArchive.php` +- Testy po zmianie: **165 tests, 424 assertions** diff --git a/TESTING.md b/TESTING.md index 23e7e41..a5c83dd 100644 --- a/TESTING.md +++ b/TESTING.md @@ -224,3 +224,25 @@ Ostatnio zweryfikowano: 2026-02-12 ```text OK (150 tests, 372 assertions) ``` + +## Aktualizacja suite (release 0.259) +Ostatnio zweryfikowano: 2026-02-12 + +```text +OK (158 tests, 397 assertions) +``` + +Nowe testy dodane 2026-02-12: +- `tests/Unit/Domain/Scontainers/ScontainersRepositoryTest.php` +- `tests/Unit/admin/Controllers/ScontainersControllerTest.php` + +## Aktualizacja suite (release 0.260) +Ostatnio zweryfikowano: 2026-02-12 + +```text +OK (165 tests, 424 assertions) +``` + +Nowe testy dodane 2026-02-12: +- `tests/Unit/Domain/Article/ArticleRepositoryTest.php` (rozszerzenie o testy `restore`, `deletePermanently`, `listArchivedForAdmin`) +- `tests/Unit/admin/Controllers/ArticlesArchiveControllerTest.php` diff --git a/admin/templates/articles/articles-archive-list.php b/admin/templates/articles/articles-archive-list.php index 2cd5df4..3e70c9a 100644 --- a/admin/templates/articles/articles-archive-list.php +++ b/admin/templates/articles/articles-archive-list.php @@ -1,67 +1,5 @@ - $this->viewModel]); ?> -$grid = new \grid( 'pp_articles' ); -$grid -> gdb_opt = $gdb; -$grid -> sql = 'SELECT *' - . 'FROM ( ' - . 'SELECT ' - . 'id, date_add, date_modify, status, ' - . '( SELECT title FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = pa.id AND title != \'\' ORDER BY o ASC LIMIT 1 ) AS title ' - . 'FROM ' - . 'pp_articles AS pa WHERE status = -1 ' - . ') AS q1 ' - . 'WHERE ' - . '1=1 [where] ' - . 'ORDER BY ' - . '[order_p1] [order_p2]'; -$grid -> sql_count = 'SELECT ' - . 'COUNT(0) FROM ( ' - . 'SELECT ' - . 'id, date_add, date_modify, status, ' - . '( SELECT title FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = pa.id AND title != \'\' ORDER BY o ASC LIMIT 1 ) AS title ' - . 'FROM ' - . 'pp_articles AS pa WHERE status = -1 ' - . ') AS q1 ' - . 'WHERE ' - . '1=1 [where] '; -$grid -> debug = true; -$grid -> order = [ 'column' => 'date_add', 'type' => 'DESC' ]; -$grid -> columns_view = [ - [ - 'name' => 'Lp.', - 'th' => [ 'class' => 'g-lp' ], - 'td' => [ 'class' => 'g-center' ], - 'autoincrement' => true - ], - [ - 'name' => 'Tytuł', - 'db' => 'id', - 'replace' => [ 'sql' => "SELECT title FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = [id] AND title != '' ORDER BY o ASC LIMIT 1" ] - ], - [ - 'name' => 'Data dodania', - 'td' => [ 'class' => 'g-center' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 140px;' ], - 'php' => 'echo date( "Y-m-d H:i", strtotime( "[date_add]" ) );' - ], - [ - 'name' => 'Data modyfikacji', - 'td' => [ 'class' => 'g-center' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 140px;' ], - 'php' => 'echo date( "Y-m-d H:i", strtotime( "[date_modify]" ) );' - ], - [ - 'name' => 'Akcja', - 'th' => [ 'class' => 'g-center' ], - 'td' => [ 'class' => 'g-center', 'style' => 'width: 50px;' ], - 'content' => 'przywróć' - ], - [ - 'name' => 'Akcja', - 'action' => [ 'type' => 'delete', 'url' => '/admin/articles_archive/article_delete/id=[id]' ], - 'th' => [ 'class' => 'g-center' ], - 'td' => [ 'class' => 'g-center', 'style' => 'width: 50px;' ] - ] - ]; -echo $grid -> draw(); \ No newline at end of file +viewModel->customScriptView)): ?> + viewModel->customScriptView, ['list' => $this->viewModel]); ?> + diff --git a/autoload/Domain/Article/ArticleRepository.php b/autoload/Domain/Article/ArticleRepository.php index 2a76031..429578c 100644 --- a/autoload/Domain/Article/ArticleRepository.php +++ b/autoload/Domain/Article/ArticleRepository.php @@ -331,6 +331,32 @@ class ArticleRepository return (bool)$result; } + /** + * Przywraca artykul z archiwum (status = 0). + */ + public function restore(int $articleId): bool + { + $result = $this->db->update('pp_articles', ['status' => 0], ['id' => $articleId]); + return (bool)$result; + } + + /** + * Trwale usuwa artykul wraz z relacjami i plikami z dysku. + */ + public function deletePermanently(int $articleId): bool + { + $this->db->delete('pp_articles_pages', ['article_id' => $articleId]); + $this->db->delete('pp_articles_langs', ['article_id' => $articleId]); + $this->db->delete('pp_articles_images', ['article_id' => $articleId]); + $this->db->delete('pp_articles_files', ['article_id' => $articleId]); + $this->db->delete('pp_articles', ['id' => $articleId]); + + \S::delete_dir('../upload/article_images/article_' . $articleId . '/'); + \S::delete_dir('../upload/article_files/article_' . $articleId . '/'); + + return true; + } + /** * Zwraca liste artykulow do panelu admin z filtrowaniem, sortowaniem i paginacja. * @@ -431,6 +457,93 @@ class ArticleRepository ]; } + /** + * Zwraca liste artykulow z archiwum do panelu admin z filtrowaniem, sortowaniem i paginacja. + * + * @return array{items: array>, total: int} + */ + public function listArchivedForAdmin( + array $filters, + string $sortColumn = 'date_add', + string $sortDir = 'DESC', + int $page = 1, + int $perPage = 15 + ): array { + $sortColumn = trim($sortColumn); + $sortDir = strtoupper(trim($sortDir)); + + $allowedSortColumns = [ + 'title' => 'title', + 'date_add' => 'pa.date_add', + 'date_modify' => 'pa.date_modify', + ]; + + $sortSql = $allowedSortColumns[$sortColumn] ?? 'pa.date_add'; + $sortDir = $sortDir === 'ASC' ? 'ASC' : 'DESC'; + $page = max(1, $page); + $perPage = min(self::MAX_PER_PAGE, max(1, $perPage)); + $offset = ($page - 1) * $perPage; + + $where = ['pa.status = -1']; + $params = []; + + $title = trim((string)($filters['title'] ?? '')); + if (strlen($title) > 255) { + $title = substr($title, 0, 255); + } + if ($title !== '') { + $where[] = "( + SELECT title + FROM pp_articles_langs AS pal, pp_langs AS pl + WHERE lang_id = pl.id AND article_id = pa.id AND title != '' + ORDER BY o ASC + LIMIT 1 + ) LIKE :title"; + $params[':title'] = '%' . $title . '%'; + } + + $this->appendDateRangeFilter($where, $params, 'pa.date_add', 'date_add_from', 'date_add_to', $filters); + $this->appendDateRangeFilter($where, $params, 'pa.date_modify', 'date_modify_from', 'date_modify_to', $filters); + + $whereSql = implode(' AND ', $where); + + $sqlCount = " + SELECT COUNT(0) + FROM pp_articles AS pa + WHERE {$whereSql} + "; + + $stmtCount = $this->db->query($sqlCount, $params); + $countRows = $stmtCount ? $stmtCount->fetchAll() : []; + $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; + + $sql = " + SELECT + pa.id, + pa.date_add, + pa.date_modify, + ( + SELECT title + FROM pp_articles_langs AS pal, pp_langs AS pl + WHERE lang_id = pl.id AND article_id = pa.id AND title != '' + ORDER BY o ASC + LIMIT 1 + ) AS title + FROM pp_articles AS pa + WHERE {$whereSql} + ORDER BY {$sortSql} {$sortDir}, pa.id {$sortDir} + LIMIT {$perPage} OFFSET {$offset} + "; + + $stmt = $this->db->query($sql, $params); + $items = $stmt ? $stmt->fetchAll() : []; + + return [ + 'items' => is_array($items) ? $items : [], + 'total' => $total, + ]; + } + /** * Zapisuje kolejnosc zdjec galerii artykulu. */ diff --git a/autoload/admin/Controllers/ArticlesArchiveController.php b/autoload/admin/Controllers/ArticlesArchiveController.php new file mode 100644 index 0000000..cfd0a12 --- /dev/null +++ b/autoload/admin/Controllers/ArticlesArchiveController.php @@ -0,0 +1,147 @@ +repository = $repository; + } + + public function list(): string + { + $sortableColumns = ['title', 'date_add', 'date_modify']; + + $filterDefinitions = [ + [ + 'key' => 'title', + 'label' => 'Tytul', + 'type' => 'text', + ], + ]; + + $listRequest = \admin\Support\TableListRequestFactory::fromRequest( + $filterDefinitions, + $sortableColumns, + 'date_add' + ); + + $result = $this->repository->listArchivedForAdmin( + $listRequest['filters'], + $listRequest['sortColumn'], + $listRequest['sortDir'], + $listRequest['page'], + $listRequest['perPage'] + ); + + $rows = []; + $lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1; + + foreach ($result['items'] as $item) { + $id = (int)($item['id'] ?? 0); + $title = trim((string)($item['title'] ?? '')); + + $rows[] = [ + 'lp' => $lp++ . '.', + 'title' => '' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '', + 'date_add' => !empty($item['date_add']) ? date('Y-m-d H:i', strtotime((string)$item['date_add'])) : '-', + 'date_modify' => !empty($item['date_modify']) ? date('Y-m-d H:i', strtotime((string)$item['date_modify'])) : '-', + '_actions' => [ + [ + 'label' => 'Przywroc', + 'url' => '/admin/articles_archive/article_restore/id=' . $id, + 'class' => 'btn btn-xs btn-success', + 'confirm' => 'Na pewno chcesz przywrocic wybrany artykul?', + 'confirm_ok' => 'Przywroc', + 'confirm_cancel' => 'Anuluj', + ], + [ + 'label' => 'Usun', + 'url' => '/admin/articles_archive/article_delete/id=' . $id, + 'class' => 'btn btn-xs btn-danger', + 'confirm' => 'Na pewno chcesz trwale usunac wybrany artykul?', + 'confirm_ok' => 'Usun', + 'confirm_cancel' => 'Anuluj', + ], + ], + ]; + } + + $total = (int)$result['total']; + $totalPages = max(1, (int)ceil($total / $listRequest['perPage'])); + + $viewModel = new PaginatedTableViewModel( + [ + ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false], + ['key' => 'title', 'sort_key' => 'title', 'label' => 'Tytul', 'sortable' => true, 'raw' => true], + ['key' => 'date_add', 'sort_key' => 'date_add', 'label' => 'Data dodania', 'class' => 'text-center', 'sortable' => true], + ['key' => 'date_modify', 'sort_key' => 'date_modify', 'label' => 'Data modyfikacji', 'class' => 'text-center', 'sortable' => true], + ], + $rows, + $listRequest['viewFilters'], + [ + 'column' => $listRequest['sortColumn'], + 'dir' => $listRequest['sortDir'], + ], + [ + 'page' => $listRequest['page'], + 'per_page' => $listRequest['perPage'], + 'total' => $total, + 'total_pages' => $totalPages, + ], + array_merge($listRequest['queryFilters'], [ + 'sort' => $listRequest['sortColumn'], + 'dir' => $listRequest['sortDir'], + 'per_page' => $listRequest['perPage'], + ]), + $listRequest['perPageOptions'], + $sortableColumns, + '/admin/articles_archive/view_list/', + 'Brak danych w tabeli.' + ); + + return \Tpl::view('articles/articles-archive-list', [ + 'viewModel' => $viewModel, + ]); + } + + public function view_list(): string + { + return $this->list(); + } + + public function restore(): void + { + if ($this->repository->restore((int)\S::get('id'))) { + \S::alert('Artykul zostal przywrocony.'); + } + + header('Location: /admin/articles_archive/view_list/'); + exit; + } + + public function article_restore(): void + { + $this->restore(); + } + + public function delete(): void + { + if ($this->repository->deletePermanently((int)\S::get('id'))) { + \S::alert('Artykul zostal trwale usuniety.'); + } + + header('Location: /admin/articles_archive/view_list/'); + exit; + } + + public function article_delete(): void + { + $this->delete(); + } +} diff --git a/autoload/admin/class.Site.php b/autoload/admin/class.Site.php index e6c453d..f519909 100644 --- a/autoload/admin/class.Site.php +++ b/autoload/admin/class.Site.php @@ -211,6 +211,13 @@ class Site new \Domain\Layouts\LayoutsRepository( $mdb ) ); }, + 'ArticlesArchive' => function() { + global $mdb; + + return new \admin\Controllers\ArticlesArchiveController( + new \Domain\Article\ArticleRepository( $mdb ) + ); + }, 'Banners' => function() { global $mdb; @@ -286,6 +293,14 @@ class Site new \Domain\Newsletter\NewsletterPreviewRenderer() ); }, + 'Scontainers' => function() { + global $mdb; + + return new \admin\Controllers\ScontainersController( + new \Domain\Scontainers\ScontainersRepository( $mdb ), + new \Domain\Languages\LanguagesRepository( $mdb ) + ); + }, ]; return self::$newControllers; @@ -319,6 +334,7 @@ class Site 'article_edit' => 'edit', 'article_save' => 'save', 'article_delete' => 'delete', + 'article_restore' => 'restore', 'banner_edit' => 'edit', 'banner_save' => 'save', 'banner_delete' => 'delete', @@ -332,6 +348,9 @@ class Site 'layout_edit' => 'edit', 'layout_save' => 'save', 'layout_delete' => 'delete', + 'container_edit' => 'edit', + 'container_save' => 'save', + 'container_delete' => 'delete', ]; public static function route() diff --git a/autoload/admin/controls/class.ArticlesArchive.php b/autoload/admin/controls/class.ArticlesArchive.php deleted file mode 100644 index 853607d..0000000 --- a/autoload/admin/controls/class.ArticlesArchive.php +++ /dev/null @@ -1,26 +0,0 @@ - update( 'pp_articles', [ 'status' => 0 ], [ 'id' => (int)$article_id ] ); - } - - public static function article_delete( $article_id ) - { - global $mdb; - - $mdb -> delete( 'pp_articles_pages', [ 'article_id' => (int)$article_id ] ); - $mdb -> delete( 'pp_articles_langs', [ 'article_id' => (int)$article_id ] ); - $mdb -> delete( 'pp_articles_images', [ 'article_id' => (int)$article_id ] ); - $mdb -> delete( 'pp_articles_files', [ 'article_id' => (int)$article_id ] ); - $mdb -> delete( 'pp_articles', [ 'id' => (int)$article_id ] ); - - \S::delete_dir( '../upload/article_images/article_' . (int)$article_id . '/' ); - \S::delete_dir( '../upload/article_files/article_' . (int)$article_id . '/' ); - - return true; - } -} diff --git a/autoload/admin/view/class.ArticlesArchive.php b/autoload/admin/view/class.ArticlesArchive.php deleted file mode 100644 index 766aa2b..0000000 --- a/autoload/admin/view/class.ArticlesArchive.php +++ /dev/null @@ -1,11 +0,0 @@ - render( 'articles/articles-archive-list' ); - } -} diff --git a/tests/Unit/Domain/Article/ArticleRepositoryTest.php b/tests/Unit/Domain/Article/ArticleRepositoryTest.php index abf4781..06aea6a 100644 --- a/tests/Unit/Domain/Article/ArticleRepositoryTest.php +++ b/tests/Unit/Domain/Article/ArticleRepositoryTest.php @@ -443,6 +443,95 @@ class ArticleRepositoryTest extends TestCase $this->assertFalse($result); } + public function testRestoreSetsStatusToZero(): void + { + $mockDb = $this->createMock(\medoo::class); + + $mockDb->expects($this->once()) + ->method('update') + ->with('pp_articles', ['status' => 0], ['id' => 25]) + ->willReturn(true); + + $repository = new ArticleRepository($mockDb); + $result = $repository->restore(25); + + $this->assertTrue($result); + } + + public function testDeletePermanentlyRemovesArticleAndRelations(): void + { + $mockDb = $this->createMock(\medoo::class); + $deleteCalls = []; + + $mockDb->expects($this->exactly(5)) + ->method('delete') + ->willReturnCallback(function ($table, $where) use (&$deleteCalls) { + $deleteCalls[] = ['table' => $table, 'where' => $where]; + return true; + }); + + $repository = new ArticleRepository($mockDb); + $result = $repository->deletePermanently(77); + + $this->assertTrue($result); + $this->assertCount(5, $deleteCalls); + $this->assertSame('pp_articles_pages', $deleteCalls[0]['table']); + $this->assertSame('pp_articles_langs', $deleteCalls[1]['table']); + $this->assertSame('pp_articles_images', $deleteCalls[2]['table']); + $this->assertSame('pp_articles_files', $deleteCalls[3]['table']); + $this->assertSame('pp_articles', $deleteCalls[4]['table']); + } + + public function testListArchivedForAdminWhitelistsSortAndDirection(): void + { + $mockDb = $this->createMock(\medoo::class); + $queries = []; + + $mockDb->method('query') + ->willReturnCallback(function ($sql, $params = []) use (&$queries) { + $queries[] = ['sql' => $sql, 'params' => $params]; + + if (strpos($sql, 'COUNT(0)') !== false) { + return new class { + public function fetchAll() + { + return [[1]]; + } + }; + } + + return new class { + public function fetchAll() + { + return [[ + 'id' => 1, + 'date_add' => '2020-01-01 00:00:00', + 'date_modify' => '2020-01-01 00:00:00', + 'title' => 'A', + ]]; + } + }; + }); + + $repository = new ArticleRepository($mockDb); + $repository->listArchivedForAdmin( + [], + 'date_add DESC; DROP TABLE pp_articles; --', + 'DESC; DELETE FROM pp_users; --', + 1, + 100000 + ); + + $this->assertCount(2, $queries); + $dataSql = $queries[1]['sql']; + + $this->assertMatchesRegularExpression('/ORDER BY\s+pa\.date_add\s+DESC,\s+pa\.id\s+DESC/i', $dataSql); + $this->assertStringNotContainsString('DROP TABLE', $dataSql); + $this->assertStringNotContainsString('DELETE FROM pp_users', $dataSql); + $this->assertMatchesRegularExpression('/LIMIT\s+100\s+OFFSET\s+0/i', $dataSql); + $this->assertStringContainsString('pa.status = -1', $dataSql); + } + public function testListForAdminWhitelistsSortAndDirection(): void { $mockDb = $this->createMock(\medoo::class); diff --git a/tests/Unit/admin/Controllers/ArticlesArchiveControllerTest.php b/tests/Unit/admin/Controllers/ArticlesArchiveControllerTest.php new file mode 100644 index 0000000..5241a8d --- /dev/null +++ b/tests/Unit/admin/Controllers/ArticlesArchiveControllerTest.php @@ -0,0 +1,52 @@ +repository = $this->createMock(ArticleRepository::class); + $this->controller = new ArticlesArchiveController($this->repository); + } + + public function testConstructorAcceptsRepository(): void + { + $controller = new ArticlesArchiveController($this->repository); + $this->assertInstanceOf(ArticlesArchiveController::class, $controller); + } + + public function testHasMainActionMethods(): void + { + $this->assertTrue(method_exists($this->controller, 'list')); + $this->assertTrue(method_exists($this->controller, 'view_list')); + $this->assertTrue(method_exists($this->controller, 'restore')); + $this->assertTrue(method_exists($this->controller, 'delete')); + } + + public function testActionMethodReturnTypes(): void + { + $reflection = new \ReflectionClass($this->controller); + + $this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('view_list')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('restore')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('delete')->getReturnType()); + } + + public function testConstructorRequiresArticleRepository(): void + { + $reflection = new \ReflectionClass(ArticlesArchiveController::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.260.zip b/updates/0.20/ver_0.260.zip new file mode 100644 index 0000000..da7014b Binary files /dev/null and b/updates/0.20/ver_0.260.zip differ diff --git a/updates/0.20/ver_0.260_files.txt b/updates/0.20/ver_0.260_files.txt new file mode 100644 index 0000000..056c527 --- /dev/null +++ b/updates/0.20/ver_0.260_files.txt @@ -0,0 +1,3 @@ +F: ../autoload/admin/controls/class.ArticlesArchive.php +F: ../autoload/admin/factory/class.ArticlesArchive.php +F: ../autoload/admin/view/class.ArticlesArchive.php diff --git a/updates/changelog.php b/updates/changelog.php index 98bf05a..7e1aba0 100644 --- a/updates/changelog.php +++ b/updates/changelog.php @@ -1,4 +1,19 @@ -ver. 0.258 - 12.02.2026
+ver. 0.260 - 12.02.2026
+- NEW - migracja modulu `ArticlesArchive` do architektury Domain + DI (`admin\\Controllers\\ArticlesArchiveController`) +- UPDATE - `Domain\\Article\\ArticleRepository` rozszerzone o metody `listArchivedForAdmin`, `restore`, `deletePermanently` +- UPDATE - widok `/admin/articles_archive/view_list/` przepiety z legacy `grid` na `components/table-list` +- UPDATE - routing DI (`admin\\Site`) rozszerzony o modul `ArticlesArchive` + mapowanie akcji `article_restore -> restore` +- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.ArticlesArchive.php`, `autoload/admin/factory/class.ArticlesArchive.php`, `autoload/admin/view/class.ArticlesArchive.php` +- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.260_files.txt` +
ver. 0.259 - 12.02.2026
+- NEW - migracja modulu `Scontainers` do architektury Domain + DI (`Domain\\Scontainers\\ScontainersRepository`, `admin\\Controllers\\ScontainersController`) +- UPDATE - widoki `/admin/scontainers/*` przepiete z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit` +- UPDATE - routing DI (`admin\\Site`) rozszerzony o modul `Scontainers` + mapowanie akcji `container_edit/container_save/container_delete` +- UPDATE - `admin\\factory\\Scontainers` dziala jako fasada do repozytorium (backward compatibility) +- UPDATE - `front\\factory\\Scontainers` korzysta z `Domain\\Scontainers\\ScontainersRepository` +- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.Scontainers.php`, `autoload/admin/view/class.Scontainers.php` +- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.259_files.txt` +
ver. 0.258 - 12.02.2026
- UPDATE - modul `Newsletter`: funkcjonalnosc `Wysylka - przygotowanie` zostala tymczasowo wylaczona (menu + akcje `prepare/send/preview`) - UPDATE - modul `Newsletter`: lista `Szablony uzytkownika` zostala tymczasowo wylaczona (menu + akcja `email_templates_user`) - UPDATE - `NewsletterController`: lista szablonow ograniczona do szablonow administracyjnych (`is_admin = 1`) @@ -8,7 +23,7 @@
ver. 0.257 - 12.02.2026
- NEW - migracja modulu `Newsletter` do architektury Domain + DI (`Domain\\Newsletter\\NewsletterRepository`, `Domain\\Newsletter\\NewsletterPreviewRenderer`, `admin\\Controllers\\NewsletterController`) - UPDATE - widoki `/admin/newsletter/*` przepiete z legacy `grid/gridEdit` na nowe komponenty (`components/table-list`, `components/form-edit`) + nowy endpoint `/admin/newsletter/preview/` -- UPDATE - routing DI (`admin\\Site`) rozszerzony o moduł `Newsletter` +- UPDATE - routing DI (`admin\\Site`) rozszerzony o moduĹ‚ `Newsletter` - UPDATE - `admin\\factory\\Newsletter` dziala jako fasada do nowego repozytorium (backward compatibility) - UPDATE - `front\\factory\\Newsletter` nie korzysta juz z `admin\\view\\Newsletter` - CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.Newsletter.php`, `autoload/admin/view/class.Newsletter.php` @@ -29,7 +44,7 @@ - UPDATE - migracja widokow languages (`languages-list`, `language-edit`, `translations-list`, `translation-edit`) na `components/table-list` i `components/form-edit` - UPDATE - routing DI dla `Languages` w `admin\\Site` oraz kompatybilna fasada `admin\\factory\\Languages` delegujaca do repozytorium - UPDATE - naprawiono zapis edycji jezyka (ID jezyka pobierane z URL przy edycji) -- UPDATE - globalne poprawki UX filtrów w `components/table-list` (kompaktowe kolumny `Aktywny`/`Domyslny`, spacing i pelna szerokosc selecta) +- UPDATE - globalne poprawki UX filtrĂłw w `components/table-list` (kompaktowe kolumny `Aktywny`/`Domyslny`, spacing i pelna szerokosc selecta) - CLEANUP - usuniete legacy klasy: `autoload/admin/controls/class.Languages.php`, `autoload/admin/view/class.Languages.php`
ver. 0.253 - 12.02.2026
@@ -92,74 +107,74 @@ - UPDATE - refaktoryzacja: article_save przeniesiony do Domain\Article\ArticleRepository::save() z prywatnymi helperami - UPDATE - refaktoryzacja: article_delete przeniesiony do Domain\Article\ArticleRepository::archive() - UPDATE - ArticlesController: nowe akcje save() i delete() z DI -- UPDATE - admin\factory\Articles::article_save() i articles_set_archive() delegują do repozytorium (kompatybilność) +- UPDATE - admin\factory\Articles::article_save() i articles_set_archive() delegujÄ… do repozytorium (kompatybilność)
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ść) +- 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ść)
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 przejęte przez nowe kontrolery oznaczone jako @deprecated w legacy kontrolerach admin\controls +- UPDATE - admin\factory\Articles::article_details() deleguje do nowego repozytorium (kompatybilność zachowana) +- UPDATE - metody przejÄ™te przez nowe kontrolery oznaczone jako @deprecated w legacy kontrolerach admin\controls
ver. 0.241
-- NEW - refaktoryzacja: admin\Controllers\ProductArchiveController - archiwum produktów z DI +- NEW - refaktoryzacja: admin\Controllers\ProductArchiveController - archiwum produktĂłw z DI - NEW - ProductRepository::archive(), unarchive() - operacje archiwizacji w repozytorium -- FIX - naprawiono SQL w liście archiwum (puste wyszukiwanie filtrowało wszystkie wyniki) -- FIX - naprawiono brakujący filtr archive = 1 w zapytaniu bez wyszukiwania -- UPDATE - wyczyszczono szablony archiwum (usunięto zbędne funkcje: apilo, baselinker, duplikowanie) +- FIX - naprawiono SQL w liĹ›cie archiwum (puste wyszukiwanie filtrowaĹ‚o wszystkie wyniki) +- FIX - naprawiono brakujÄ…cy filtr archive = 1 w zapytaniu bez wyszukiwania +- UPDATE - wyczyszczono szablony archiwum (usuniÄ™to zbÄ™dne funkcje: apilo, baselinker, duplikowanie)
ver. 0.240
- 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 +- 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)
ver. 0.239
-- NEW - refaktoryzacja: Domain\Banner\BannerRepository + admin\Controllers\BannerController (pełna migracja kontrolera) +- 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) +- 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)
ver. 0.238
- NEW - refaktoryzacja: Domain\Product\ProductRepository - pierwsza klasa w nowej architekturze Domain-Driven - NEW - Dependency Injection zamiast global variables -- UPDATE - shop\Product::get_product_quantity() używa teraz nowego repozytorium (kompatybilność zachowana) +- UPDATE - shop\Product::get_product_quantity() uĹĽywa teraz nowego repozytorium (kompatybilność zachowana)
ver. 0.237
- NEW - automatyczne czyszczenie cache produktu po aktualizacji przez CRON (Sellasist, Apilo, Baselinker) -- UPDATE - przycisk "Wyczyść cache" w panelu administratora z obsługą AJAX i komunikatami o postępie +- UPDATE - przycisk "Wyczyść cache" w panelu administratora z obsĹ‚ugÄ… AJAX i komunikatami o postÄ™pie
ver. 0.236
-- FIX - zabezpieczenie przed duplikatami zamówień w Apilo - automatyczne pobieranie ID zamówienia przy błędzie "idExternal już wykorzystywany" +- FIX - zabezpieczenie przed duplikatami zamĂłwieĹ„ w Apilo - automatyczne pobieranie ID zamĂłwienia przy błędzie "idExternal juĹĽ wykorzystywany"
ver. 0.235
- FIX - poprawka funkcji aktualizacji
ver. 0.234
-- NEW - przycisk zaznaczania zamówienia jako wysłane do trustmate.io +- NEW - przycisk zaznaczania zamĂłwienia jako wysĹ‚ane do trustmate.io
ver. 0.232
- NEW - opcje GPSR
ver. 0.231
-- FIX - poprawki bezpieczeństwa + dwuetapowa weryfikacja logowania +- FIX - poprawki bezpieczeĹ„stwa + dwuetapowa weryfikacja logowania
ver. 0.230
-- FIX - poprawki bezpieczeństwa +- FIX - poprawki bezpieczeĹ„stwa
ver. 0.229
-- NEW - pola dodatkowe z opcją wymagane/niewymagane +- NEW - pola dodatkowe z opcjÄ… wymagane/niewymagane
ver. 0.228
-- NEW - cron do wysyłania zamówień do trustmate.io +- NEW - cron do wysyĹ‚ania zamĂłwieĹ„ do trustmate.io
ver. 0.227
-- NEW - historia kodów rabatowych +- NEW - historia kodĂłw rabatowych
ver. 0.226
-- NEW - dodanie opcji faktury do zamówienia +- NEW - dodanie opcji faktury do zamĂłwienia
ver. 0.225
- NEW - przycisk czyszczenia cache
-- NEW - ponowne wysyłanie zamówienia do apilo +- NEW - ponowne wysyĹ‚anie zamĂłwienia do apilo
ver. 0.224
- NEW - sortowanie form dostawy @@ -171,20 +186,20 @@ - NEW - integracja z Orlen Paczka
ver. 0.221
-- NEW - Automatyczne przekierowania adresów URL produktów, zmiany w pliku htaccess +- NEW - Automatyczne przekierowania adresĂłw URL produktĂłw, zmiany w pliku htaccess
ver. 0.220
-- NEW - Dodanie możliwości wyświetlenia na strone ostatnio dodane produkty [PRODUKTY_NEW] lub [PRODUKTY_NEW:10].
-- NEW - Dodanie możliwości wyświetlenia na strone popularnych produktów [PRODUKTY_TOP] lub [PRODUKTY_TOP:10]. +- NEW - Dodanie moĹĽliwoĹ›ci wyĹ›wietlenia na strone ostatnio dodane produkty [PRODUKTY_NEW] lub [PRODUKTY_NEW:10].
+- NEW - Dodanie możliwości wyświetlenia na strone popularnych produktów [PRODUKTY_TOP] lub [PRODUKTY_TOP:10].
ver. 0.219
-- NEW - Dodanie możliwości zmiany daty w artykułach +- NEW - Dodanie moĹĽliwoĹ›ci zmiany daty w artykuĹ‚ach
ver. 0.218
- NEW - indywidualny kod GTM
ver. 0.217
-- NEW - zwiększenie obsługi REDIS +- NEW - zwiÄ™kszenie obsĹ‚ugi REDIS
ver. 0.216
- NEW - aktualizacja api i cron (apilo) @@ -199,43 +214,43 @@ - FIX - wyliczenie darmowej dostawy
ver. 0.212
-- NEW - zmiany w zapisywaniu zamówienia do apilo +- NEW - zmiany w zapisywaniu zamĂłwienia do apilo
ver. 0.211
-- NEW - Debugowanie apilo + wyświetlanie podkategorii +- NEW - Debugowanie apilo + wyĹ›wietlanie podkategorii
ver. 0.210
-- NEW - dodatkowe pola w widoku produktów +- NEW - dodatkowe pola w widoku produktĂłw
ver. 0.209
-- NEW - zmiany w widoku produktów (panel administratora) +- NEW - zmiany w widoku produktĂłw (panel administratora)
ver. 0.208
-- NEW - zmiany w wyszukiwarce produktów +- NEW - zmiany w wyszukiwarce produktĂłw
ver. 0.204-0.207
- NEW - htaccess update
ver. 0.204-0.206
-- NEW - wysyłanie produktów do apilo +- NEW - wysyĹ‚anie produktĂłw do apilo
ver. 0.203
- NEW - zmiana sposobu wyliczania cen produkty z dodatkami
ver. 0.202
-- NEW - dodano "główne zdjęcie" w edycji artykułu +- NEW - dodano "główne zdjÄ™cie" w edycji artykuĹ‚u
ver. 0.201
-- FIX - aktualizacja statusów na podstawie baselinkera +- FIX - aktualizacja statusĂłw na podstawie baselinkera
ver. 0.200
-- NEW - wysyłanie produktów do baselinker +- NEW - wysyĹ‚anie produktĂłw do baselinker
ver. 0.199
- NEW - usprawnienie edycji danych do XML
ver. 0.198
-- NEW - automatyczne generowanie kodów SKU +- NEW - automatyczne generowanie kodĂłw SKU
ver. 0.197
- FIX - poprawki w Dashboard @@ -244,7 +259,7 @@ - FIX - integracja z apilo.com
ver. 0.195
-- FIX - aktualizacja statusów +- FIX - aktualizacja statusĂłw
ver. 0.194
- UPDATE - integracja apilo @@ -253,46 +268,46 @@ - UPDATE - aktualizacja synchronizacji z baselinker
ver. 0.192
-- NEW - pobieranie statusów z sellasist +- NEW - pobieranie statusĂłw z sellasist
ver. 0.191
- NEW - integracja z selasist
ver. 0.190
-- FIX - produkty powiązane +- FIX - produkty powiÄ…zane
ver. 0.189
-- FIX - ceny promocyjne produktów z dodatkiem +- FIX - ceny promocyjne produktĂłw z dodatkiem
ver. 0.188
-- NEW - widok listy produktów +- NEW - widok listy produktĂłw
ver. 0.187
- FIX - pobieranie cen z APILO
ver. 0.186
-- FIX - dodawanie do koszyka tych samych produktów ale z różną personalizacją +- FIX - dodawanie do koszyka tych samych produktĂłw ale z różnÄ… personalizacjÄ…
ver. 0.185
-- FIX - masowa edycja produktów +- FIX - masowa edycja produktĂłw
ver. 0.184
-- NEW - druga część integracji z apilo, masowa edycja produktów +- NEW - druga część integracji z apilo, masowa edycja produktĂłw
ver. 0.183
-- NEW - pierwsza część integracji z apilo +- NEW - pierwsza część integracji z apilo
ver. 0.182
- FIX - layout
ver. 0.181
-- NEW - infinitescroll - opcja włączy/wyłącz +- NEW - infinitescroll - opcja włączy/wyłącz
ver. 0.180
- NEW - aktualizacja dashboard
ver. 0.179
-- NEW - obsługa EAN +- NEW - obsĹ‚uga EAN
ver. 0.177, 0.178
- FIX - custom_label @@ -308,7 +323,7 @@
ver. 0.173
- NEW - duplikowanie produktu wraz z kombinacjami -- NEW - dodanie przechodzenia pomiędzy zamówienia (poprzednie/następne zamówienie) +- NEW - dodanie przechodzenia pomiÄ™dzy zamĂłwienia (poprzednie/nastÄ™pne zamĂłwienie)
ver. 0.172
- FIX - poprawki w Cache @@ -320,22 +335,22 @@ - NEW - usuwanie cache produktu przy zapisie
ver. 0.169
-- FIX - poprawki w liście produktów +- FIX - poprawki w liĹ›cie produktĂłw
ver. 0.168
-- NEW - archiwum produktów +- NEW - archiwum produktĂłw
ver. 0.167
-- NEW - dodanie obsługi cen i stanów magazynowych kombinacji produktów +- NEW - dodanie obsĹ‚ugi cen i stanĂłw magazynowych kombinacji produktĂłw
ver. 0.166
-- NEW - współpraca z GTM +- NEW - współpraca z GTM
ver. 0.164/5
-- FIX - ukrywanie produktów nieaktywnych +- FIX - ukrywanie produktĂłw nieaktywnych
ver. 0.163
-- NEW - automatyczne podpowiadanie produktów do zestawu na podstawie wcześniejszych zakupów klientów +- NEW - automatyczne podpowiadanie produktĂłw do zestawu na podstawie wczeĹ›niejszych zakupĂłw klientĂłw
ver. 0.162
- NEW - GA4 @@ -351,7 +366,7 @@ - FIX - cron Baselinker
ver. 0.158
-- UPDATE - poprawa kolorystyki przycisków +- UPDATE - poprawa kolorystyki przyciskĂłw
ver. 0.157
- NEW - szybka zmiana statusu produktu @@ -363,10 +378,10 @@ - NEW - infinite scroll w widoku kategorii
ver. 0.154
-- FIX - atrybuty produktów +- FIX - atrybuty produktĂłw
ver. 0.153
-- FIX - atrybuty produktów +- FIX - atrybuty produktĂłw
ver. 0.152
- FIX - tematy maili @@ -375,7 +390,7 @@ - FIX - tematy maili
ver. 0.150
-- NEW - domyślna forma transportu +- NEW - domyĹ›lna forma transportu
ver. 0.149
- NEW - tematy maili @@ -393,13 +408,14 @@ - NEW - omnibus ready
ver. 0.144
-- FIX - usunięcie adresu marianek.pl z kodu +- FIX - usuniÄ™cie adresu marianek.pl z kodu
ver. 0.143
-- FIX - poprawa generowania plików WEBP +- FIX - poprawa generowania plikĂłw WEBP
ver. 0.142
-- FIX - poprawa adresu strony głównej +- FIX - poprawa adresu strony głównej +