From 68320090202b8b3fe859e2fcf63cdb2041130913 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Thu, 12 Feb 2026 23:53:05 +0100 Subject: [PATCH] refactor articles_archive to DI controller and table-list --- DATABASE_STRUCTURE.md | 30 +++- PROJECT_STRUCTURE.md | 18 +++ REFACTORING_PLAN.md | 20 +++ TESTING.md | 22 +++ .../articles/articles-archive-list.php | 70 +------- autoload/Domain/Article/ArticleRepository.php | 113 +++++++++++++ .../Controllers/ArticlesArchiveController.php | 147 +++++++++++++++++ autoload/admin/class.Site.php | 19 +++ .../admin/controls/class.ArticlesArchive.php | 26 --- .../admin/factory/class.ArticlesArchive.php | 27 ---- autoload/admin/view/class.ArticlesArchive.php | 11 -- .../Domain/Article/ArticleRepositoryTest.php | 89 ++++++++++ .../ArticlesArchiveControllerTest.php | 52 ++++++ updates/0.20/ver_0.260.zip | Bin 0 -> 15002 bytes updates/0.20/ver_0.260_files.txt | 3 + updates/changelog.php | 152 ++++++++++-------- 16 files changed, 600 insertions(+), 199 deletions(-) create mode 100644 autoload/admin/Controllers/ArticlesArchiveController.php delete mode 100644 autoload/admin/controls/class.ArticlesArchive.php delete mode 100644 autoload/admin/factory/class.ArticlesArchive.php delete mode 100644 autoload/admin/view/class.ArticlesArchive.php create mode 100644 tests/Unit/admin/Controllers/ArticlesArchiveControllerTest.php create mode 100644 updates/0.20/ver_0.260.zip create mode 100644 updates/0.20/ver_0.260_files.txt 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 0000000000000000000000000000000000000000..da7014b9095cb79485243aca0a4f7e17dedfc023 GIT binary patch literal 15002 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 WK^hGF?`6RL9P+>b02}AuPyYjmZ0`vG literal 0 HcmV?d00001 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 +