diff --git a/DATABASE_STRUCTURE.md b/DATABASE_STRUCTURE.md index 323bfc7..ca26eb5 100644 --- a/DATABASE_STRUCTURE.md +++ b/DATABASE_STRUCTURE.md @@ -249,3 +249,41 @@ Przypisanie layoutow do kategorii sklepu. **Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `front\\factory\\Layouts` **Aktualizacja 2026-02-12 (ver. 0.256):** modul `/admin/layouts` korzysta z `Domain\\Layouts\\LayoutsRepository` (DI kontroler + fasada legacy). + +## pp_newsletter +Adresy e-mail zapisane do newslettera. + +| Kolumna | Opis | +|---------|------| +| id | PK | +| email | Adres e-mail subskrybenta | +| hash | Hash potwierdzenia/wypisu | +| status | 1 = potwierdzony, 0 = oczekujacy | + +**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `front\\factory\\Newsletter` + +## pp_newsletter_send +Kolejka wysylki newslettera. + +| Kolumna | Opis | +|---------|------| +| id | PK | +| email | Adres docelowy | +| dates | Zakres dat artykulow (tekst) | +| id_template | FK do `pp_newsletter_templates` (NULL gdy brak szablonu) | + +**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `front\\factory\\Newsletter::newsletter_send()` + +## pp_newsletter_templates +Szablony tresci e-maili (uzytkownik + administracyjne/systemowe). + +| Kolumna | Opis | +|---------|------| +| id | PK | +| name | Nazwa/klucz szablonu | +| text | Tresc HTML szablonu | +| is_admin | 1 = szablon administracyjny/systemowy, 0 = szablon uzytkownika | + +**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). diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 8a54078..1f9a596 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -419,3 +419,21 @@ Aktualnie w suite są też testy modułów `Dictionaries`, `Articles` i `Users` - CLEANUP: usuniete legacy klasy `autoload/admin/controls/class.Layouts.php`, `autoload/admin/view/class.Layouts.php`; `admin/factory/class.Layouts.php` dziala jako fasada do `Domain\\Layouts\\LayoutsRepository`. - UPDATE: `admin\\Controllers\\ArticlesController` pobiera layouty przez `Domain\\Layouts\\LayoutsRepository` (DI). - Testy: 141 tests, 336 assertions + +## Aktualizacja 2026-02-12 (ver. 0.257) +- NOWE: `Domain\\Newsletter\\NewsletterRepository` (subskrybenci, szablony, ustawienia, kolejka wysylki). +- NOWE: `Domain\\Newsletter\\NewsletterPreviewRenderer` (render podgladu newslettera). +- NOWE: `admin\\Controllers\\NewsletterController` (DI) dla akcji `emails_list`, `prepare`, `send`, `preview`, `settings*`, `email_template*`. +- UPDATE: `/admin/newsletter/*` przepiete z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`. +- UPDATE: nowy endpoint podgladu `/admin/newsletter/preview/` (bez `admin/ajax.php`). +- UPDATE: `admin\\Site` ma fabryke DI dla modulu `Newsletter`. +- UPDATE: `admin\\factory\\Newsletter` dziala jako fasada do `Domain\\Newsletter\\NewsletterRepository`. +- UPDATE: `front\\factory\\Newsletter` nie korzysta z `admin\\view\\Newsletter`. +- CLEANUP: usuniete legacy klasy `autoload/admin/controls/class.Newsletter.php`, `autoload/admin/view/class.Newsletter.php`. +- Testy: 150 tests, 372 assertions + +## Aktualizacja 2026-02-12 (ver. 0.258) +- UPDATE: modul `/admin/newsletter/` - tymczasowo wylaczono akcje `prepare/send/preview` (Wysylka - przygotowanie). +- 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`. diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md index 6cbc7b8..4d0de26 100644 --- a/REFACTORING_PLAN.md +++ b/REFACTORING_PLAN.md @@ -614,3 +614,23 @@ Gdy `persist = true`: - UPDATE: `admin\\Controllers\\ArticlesController` korzysta z `Domain\\Layouts\\LayoutsRepository` (DI) dla listy layoutow - Testy po zmianie: **141 tests, 336 assertions** + +## Aktualizacja 2026-02-12 (ver. 0.257) +- **Newsletter** - **ZMIGROWANE** (2026-02-12) + - NOWE: `Domain\\Newsletter\\NewsletterRepository` (listy admin, szablony, ustawienia, kolejka wysylki) + - NOWE: `Domain\\Newsletter\\NewsletterPreviewRenderer` (wspolny render podgladu) + - NOWE: `admin\\Controllers\\NewsletterController` (DI) + - UPDATE: routing DI (`admin\\Site`) rozszerzony o modul `Newsletter` + - UPDATE: widoki `/admin/newsletter/*` migrowane na `components/table-list` i `components/form-edit` + - UPDATE: `admin\\factory\\Newsletter` jako fasada do repozytorium + - UPDATE: `front\\factory\\Newsletter` bez zaleznosci od `admin\\view\\Newsletter` + - CLEANUP: usuniete `autoload/admin/controls/class.Newsletter.php`, `autoload/admin/view/class.Newsletter.php` + +- Testy po zmianie: **150 tests, 372 assertions** + +## Aktualizacja 2026-02-12 (ver. 0.258) +- **Newsletter** + - UPDATE: tymczasowo wylaczono flow `prepare/send/preview` (wymaga przebudowy). + - 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`. diff --git a/TESTING.md b/TESTING.md index 14598bd..23e7e41 100644 --- a/TESTING.md +++ b/TESTING.md @@ -206,3 +206,21 @@ Nowe testy dodane 2026-02-12: Zaktualizowane testy 2026-02-12: - `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php` (defaultLanguageId) - `tests/Unit/admin/Controllers/ArticlesControllerTest.php` (konstruktor + LayoutsRepository) + +## Aktualizacja suite (release 0.257) +Ostatnio zweryfikowano: 2026-02-12 + +```text +OK (150 tests, 372 assertions) +``` + +Nowe testy dodane 2026-02-12: +- `tests/Unit/Domain/Newsletter/NewsletterRepositoryTest.php` +- `tests/Unit/admin/Controllers/NewsletterControllerTest.php` + +## Aktualizacja suite (release 0.258) +Ostatnio zweryfikowano: 2026-02-12 + +```text +OK (150 tests, 372 assertions) +``` diff --git a/admin/templates/newsletter/email-template-edit.php b/admin/templates/newsletter/email-template-edit.php index 7c6aa1d..3615024 100644 --- a/admin/templates/newsletter/email-template-edit.php +++ b/admin/templates/newsletter/email-template-edit.php @@ -1,67 +1 @@ - - - - 'Nazwa', - 'name' => 'name', - 'id' => 'name', - 'value' => $this -> email_template['name'], - 'inline' => true, - 'readonly' => $this -> email_template['is_admin'] ? true : false - ) - );?> - 'Treść', - 'name' => 'text', - 'id' => 'text', - 'value' => $this ->email_template['text'], - 'inline' => true - ) - );?> - - id = 'email-templates-edit'; -$grid -> gdb_opt = $gdb; -$grid -> include_plugins = true; -$grid -> title = 'Edycja szablonu newslettera'; -$grid -> fields = [ - [ - 'db' => 'id', - 'type' => 'hidden', - 'value' => $this -> email_template['id'] - ] - ]; -$grid -> external_code = $out; -$grid -> actions = [ - 'save' => [ - 'url' => '/admin/newsletter/template_save/', - 'back_url' => $this -> email_template['is_admin'] ? '/admin/newsletter/email_templates_admin/' : '/admin/newsletter/email_templates_user/' - ], - 'cancel' => [ - 'url' => $this -> email_template['is_admin'] ? '/admin/newsletter/email_templates_admin/' : '/admin/newsletter/email_templates_user/' - ] - ]; -$grid -> persist_edit = true; -$grid -> id_param = 'id'; - -echo $grid -> draw(); -?> - \ No newline at end of file + $this->form]); ?> \ No newline at end of file diff --git a/admin/templates/newsletter/email-templates-admin.php b/admin/templates/newsletter/email-templates-admin.php index be182b7..17cbbb8 100644 --- a/admin/templates/newsletter/email-templates-admin.php +++ b/admin/templates/newsletter/email-templates-admin.php @@ -1,28 +1 @@ - gdb_opt = $gdb; -$grid -> order = [ 'column' => 'name', 'type' => 'ASC' ]; -$grid -> where = [ 'is_admin' => 1 ]; -$grid -> columns_view = [ - [ - 'name' => 'Lp.', - 'th' => [ 'class' => 'g-lp' ], - 'td' => [ 'class' => 'g-center' ], - 'autoincrement' => true - ], - [ - 'name' => 'Nazwa', - 'db' => 'name', - 'php' => 'echo "[name]";', - 'sort' => true - ], - [ - 'name' => 'Edytuj', - 'action' => [ 'type' => 'edit', 'url' => '/admin/newsletter/email_template_edit/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ] - ]; -echo $grid -> draw(); \ No newline at end of file + $this->viewModel]); ?> \ No newline at end of file diff --git a/admin/templates/newsletter/email-templates-user.php b/admin/templates/newsletter/email-templates-user.php deleted file mode 100644 index 6275e03..0000000 --- a/admin/templates/newsletter/email-templates-user.php +++ /dev/null @@ -1,42 +0,0 @@ - gdb_opt = $gdb; -$grid -> order = [ 'column' => 'name', 'type' => 'ASC' ]; -$grid -> where = [ 'is_admin' => 0 ]; -$grid -> columns_view = [ - [ - 'name' => 'Lp.', - 'th' => [ 'class' => 'g-lp' ], - 'td' => [ 'class' => 'g-center' ], - 'autoincrement' => true - ], - [ - 'name' => 'Nazwa', - 'db' => 'name', - 'php' => 'echo "[name]";', - 'sort' => true - ], - [ - 'name' => 'Edytuj', - 'action' => [ 'type' => 'edit', 'url' => '/admin/newsletter/email_template_edit/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ], - [ - 'name' => 'Usuń', - 'action' => [ 'type' => 'delete', 'url' => '/admin/newsletter/email_template_delete/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ] - ]; -$grid -> buttons = [ - [ - 'label' => 'Dodaj szablon', - 'url' => '/admin/newsletter/email_template_edit/', - 'icon' => 'fa-plus-circle', - 'class' => 'btn-success' - ] - ]; -echo $grid -> draw(); \ No newline at end of file diff --git a/admin/templates/newsletter/emails-list.php b/admin/templates/newsletter/emails-list.php index 19f1827..0f89c5b 100644 --- a/admin/templates/newsletter/emails-list.php +++ b/admin/templates/newsletter/emails-list.php @@ -1,30 +1 @@ - gdb_opt = $gdb; -$grid -> order = [ 'column' => 'email', 'type' => 'ASC' ]; -$grid -> search = [ - [ 'name' => 'Email', 'db' => 'email', 'type' => 'text' ] - ]; -$grid -> columns_view = [ - [ - 'name' => 'Lp.', - 'th' => [ 'class' => 'g-lp' ], - 'td' => [ 'class' => 'g-center' ], - 'autoincrement' => true - ], - [ - 'name' => 'Email', - 'db' => 'email', - 'sort' => true - ], - [ - 'name' => 'Potwierdzony', - 'db' => 'status', - 'sort' => true, - 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ] - ] - ]; -$grid -> actions = [ 'delete' => true ]; -echo $grid -> draw(); \ No newline at end of file + $this->viewModel]); ?> diff --git a/admin/templates/newsletter/prepare.php b/admin/templates/newsletter/prepare.php deleted file mode 100644 index 565dd21..0000000 --- a/admin/templates/newsletter/prepare.php +++ /dev/null @@ -1,131 +0,0 @@ - -
- -
-
- - - - -
-
-
-
- -
- templates ) ): foreach ( $this -> templates as $template ): - $templates[ $template['id'] ] = $template['name']; - endforeach; endif; - ?> - 'Szablon', - 'name' => 'template', - 'id' => 'template', - 'values' => $templates, - 'value' => $this -> templates['id'] - ));?> - -
- -
-
-
-
- id = 'newsletter-prepare'; -$grid -> gdb_opt = $gdb; -$grid -> include_plugins = true; -$grid -> title = 'Wysyłka newslettera - przygotowanie'; -$grid -> default_buttons = false; -$grid -> external_code = $out; -$grid -> buttons = [ - [ - 'label' => 'Wyślij newsletter', - 'class' => 'btn-success', - 'icon' => 'fa-send', - 'js' => 'send_newsletter();' - ] - ]; -echo $grid -> draw(); -?> - \ No newline at end of file diff --git a/admin/templates/newsletter/preview.php b/admin/templates/newsletter/preview.php deleted file mode 100644 index 642c6d5..0000000 --- a/admin/templates/newsletter/preview.php +++ /dev/null @@ -1,41 +0,0 @@ -
- settings['newsletter_header'] ? $this -> settings['newsletter_header'] : '

--- brak zdefiniowanego nagłówka ---

';?> -
-
- template ) ):?> -
- template['text']?> -
- - articles ) ):?> - articles as $article ):?> - -
- - - - -
- -
-
-
- - - dates ):?> -
- --- brak artykułów w danym okresie --- -
- - -
-
- settings['newsletter_footer'] ? $this -> settings['newsletter_footer'] : '

--- brak zdefiniowanej stopki ---

';?> -
\ No newline at end of file diff --git a/admin/templates/newsletter/settings.php b/admin/templates/newsletter/settings.php index 2da1c9c..3615024 100644 --- a/admin/templates/newsletter/settings.php +++ b/admin/templates/newsletter/settings.php @@ -1,51 +1 @@ - - - - 'Nagłówek', - 'name' => 'newsletter_header', - 'id' => 'newsletter_header', - 'value' => $this -> settings['newsletter_header'], - 'inline' => true - ) - ); - echo \Html::textarea( - array( - 'label' => 'Stopka', - 'name' => 'newsletter_footer', - 'id' => 'newsletter_footer', - 'value' => $this -> settings['newsletter_footer'], - 'inline' => true - ) - ); - -$out = ob_get_clean(); - -$grid = new \gridEdit; -$grid -> id = 'settings-edit'; -$grid -> gdb_opt = $gdb; -$grid -> include_plugins = true; -$grid -> title = 'Edycja ustawień'; -$grid -> actions = [ - 'save' => [ 'url' => '/admin/newsletter/settings_save/', 'back_url' => '/admin/newsletter/settings/' ], - ]; -$grid -> external_code = $out; -echo $grid -> draw(); -?> - - \ No newline at end of file + $this->form]); ?> \ No newline at end of file diff --git a/admin/templates/site/main-layout.php b/admin/templates/site/main-layout.php index 0bf4e71..0d5baf7 100644 --- a/admin/templates/site/main-layout.php +++ b/admin/templates/site/main-layout.php @@ -89,9 +89,7 @@ Newsletter @@ -274,4 +272,4 @@ }); - \ No newline at end of file + diff --git a/autoload/Domain/Newsletter/NewsletterPreviewRenderer.php b/autoload/Domain/Newsletter/NewsletterPreviewRenderer.php new file mode 100644 index 0000000..a14f2e7 --- /dev/null +++ b/autoload/Domain/Newsletter/NewsletterPreviewRenderer.php @@ -0,0 +1,59 @@ +'; + $out .= !empty($settings['newsletter_header']) + ? (string)$settings['newsletter_header'] + : '

--- brak zdefiniowanego naglowka ---

'; + $out .= ''; + + $out .= '
'; + + if (is_array($template) && !empty($template)) { + $out .= '
'; + $out .= (string)($template['text'] ?? ''); + $out .= '
'; + } + + if (is_array($articles) && !empty($articles)) { + foreach ($articles as $article) { + $articleId = (int)($article['id'] ?? 0); + $title = (string)($article['language']['title'] ?? ''); + $seoLink = trim((string)($article['language']['seo_link'] ?? '')); + $url = $seoLink !== '' ? $seoLink : ('a-' . $articleId . '-' . \S::seo($title)); + $entry = !empty($article['language']['entry']) + ? (string)$article['language']['entry'] + : (string)($article['language']['text'] ?? ''); + + $out .= '
'; + $out .= '' + . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') + . ''; + $out .= '
' . $entry . '
'; + $out .= '
'; + $out .= '
'; + } + } elseif (trim($dates) !== '') { + $out .= '
'; + $out .= '--- brak artykulow w danym okresie ---'; + $out .= '
'; + } + + $out .= '
'; + + $out .= '
'; + $out .= !empty($settings['newsletter_footer']) + ? (string)$settings['newsletter_footer'] + : '

--- brak zdefiniowanej stopki ---

'; + $out .= '
'; + + return $out; + } +} diff --git a/autoload/Domain/Newsletter/NewsletterRepository.php b/autoload/Domain/Newsletter/NewsletterRepository.php new file mode 100644 index 0000000..7f7e63e --- /dev/null +++ b/autoload/Domain/Newsletter/NewsletterRepository.php @@ -0,0 +1,292 @@ +db = $db; + $this->settingsRepository = $settingsRepository ?? new SettingsRepository($db); + } + + public function getSettings(): array + { + return $this->settingsRepository->getSettings(); + } + + public function saveSettings(array $values): bool + { + $this->settingsRepository->updateSetting('newsletter_footer', (string)($values['newsletter_footer'] ?? '')); + $this->settingsRepository->updateSetting('newsletter_header', (string)($values['newsletter_header'] ?? '')); + + return true; + } + + public function queueSend(string $dates = '', int $templateId = 0): bool + { + $subscribers = $this->db->select('pp_newsletter', 'email', ['status' => 1]); + if (!is_array($subscribers) || empty($subscribers)) { + return true; + } + + $cleanDates = trim($dates); + $templateId = $templateId > 0 ? $templateId : 0; + + foreach ($subscribers as $subscriber) { + $email = is_array($subscriber) ? (string)($subscriber['email'] ?? '') : (string)$subscriber; + if ($email === '') { + continue; + } + + $this->db->insert('pp_newsletter_send', [ + 'email' => $email, + 'dates' => $cleanDates, + 'id_template' => $templateId > 0 ? $templateId : null, + ]); + } + + return true; + } + + public function templateByName(string $templateName): string + { + return (string)$this->db->get('pp_newsletter_templates', 'text', ['name' => $templateName]); + } + + public function templateDetails(int $templateId): ?array + { + if ($templateId <= 0) { + return null; + } + + $row = $this->db->get('pp_newsletter_templates', '*', ['id' => $templateId]); + if (!is_array($row)) { + return null; + } + + return $row; + } + + public function isAdminTemplate(int $templateId): bool + { + $isAdmin = $this->db->get('pp_newsletter_templates', 'is_admin', ['id' => $templateId]); + return (int)$isAdmin === 1; + } + + public function deleteTemplate(int $templateId): bool + { + if ($templateId <= 0 || $this->isAdminTemplate($templateId)) { + return false; + } + + return (bool)$this->db->delete('pp_newsletter_templates', ['id' => $templateId]); + } + + public function saveTemplate(int $templateId, string $name, string $text): ?int + { + $templateId = max(0, $templateId); + $name = trim($name); + + if ($templateId <= 0) { + if ($name === '') { + return null; + } + + $ok = $this->db->insert('pp_newsletter_templates', [ + 'name' => $name, + 'text' => $text, + 'is_admin' => 0, + ]); + if (!$ok) { + return null; + } + + \S::delete_dir('../temp/'); + return (int)$this->db->id(); + } + + $current = $this->templateDetails($templateId); + if (!is_array($current)) { + return null; + } + + if ((int)($current['is_admin'] ?? 0) === 1) { + $name = (string)($current['name'] ?? ''); + } + + $this->db->update('pp_newsletter_templates', [ + 'name' => $name, + 'text' => $text, + ], [ + 'id' => $templateId, + ]); + + \S::delete_dir('../temp/'); + return $templateId; + } + + public function listTemplatesSimple(bool $adminTemplates = false): array + { + $rows = $this->db->select('pp_newsletter_templates', '*', [ + 'is_admin' => $adminTemplates ? 1 : 0, + 'ORDER' => ['name' => 'ASC'], + ]); + + return is_array($rows) ? $rows : []; + } + + public function deleteSubscriber(int $subscriberId): bool + { + if ($subscriberId <= 0) { + return false; + } + + return (bool)$this->db->delete('pp_newsletter', ['id' => $subscriberId]); + } + + /** + * @return array{items: array>, total: int} + */ + public function listSubscribersForAdmin( + array $filters, + string $sortColumn = 'email', + string $sortDir = 'ASC', + int $page = 1, + int $perPage = 15 + ): array { + $allowedSortColumns = [ + 'id' => 'pn.id', + 'email' => 'pn.email', + 'status' => 'pn.status', + ]; + + $sortSql = $allowedSortColumns[$sortColumn] ?? 'pn.email'; + $sortDir = strtoupper(trim($sortDir)) === 'DESC' ? 'DESC' : 'ASC'; + $page = max(1, $page); + $perPage = min(self::MAX_PER_PAGE, max(1, $perPage)); + $offset = ($page - 1) * $perPage; + + $where = ['1 = 1']; + $params = []; + + $email = trim((string)($filters['email'] ?? '')); + if ($email !== '') { + if (strlen($email) > 255) { + $email = substr($email, 0, 255); + } + $where[] = 'pn.email LIKE :email'; + $params[':email'] = '%' . $email . '%'; + } + + $status = trim((string)($filters['status'] ?? '')); + if ($status === '0' || $status === '1') { + $where[] = 'pn.status = :status'; + $params[':status'] = (int)$status; + } + + $whereSql = implode(' AND ', $where); + + $sqlCount = " + SELECT COUNT(0) + FROM pp_newsletter AS pn + WHERE {$whereSql} + "; + + $stmtCount = $this->db->query($sqlCount, $params); + $countRows = $stmtCount ? $stmtCount->fetchAll() : []; + $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; + + $sql = " + SELECT + pn.id, + pn.email, + pn.status + FROM pp_newsletter AS pn + WHERE {$whereSql} + ORDER BY {$sortSql} {$sortDir}, pn.id ASC + LIMIT {$perPage} OFFSET {$offset} + "; + + $stmt = $this->db->query($sql, $params); + $items = $stmt ? $stmt->fetchAll() : []; + + return [ + 'items' => is_array($items) ? $items : [], + 'total' => $total, + ]; + } + + /** + * @return array{items: array>, total: int} + */ + public function listTemplatesForAdmin( + bool $adminTemplates, + array $filters, + string $sortColumn = 'name', + string $sortDir = 'ASC', + int $page = 1, + int $perPage = 15 + ): array { + $allowedSortColumns = [ + 'id' => 'pnt.id', + 'name' => 'pnt.name', + ]; + + $sortSql = $allowedSortColumns[$sortColumn] ?? 'pnt.name'; + $sortDir = strtoupper(trim($sortDir)) === 'DESC' ? 'DESC' : 'ASC'; + $page = max(1, $page); + $perPage = min(self::MAX_PER_PAGE, max(1, $perPage)); + $offset = ($page - 1) * $perPage; + + $where = ['pnt.is_admin = :is_admin']; + $params = [':is_admin' => $adminTemplates ? 1 : 0]; + + $name = trim((string)($filters['name'] ?? '')); + if ($name !== '') { + if (strlen($name) > 255) { + $name = substr($name, 0, 255); + } + $where[] = 'pnt.name LIKE :name'; + $params[':name'] = '%' . $name . '%'; + } + + $whereSql = implode(' AND ', $where); + + $sqlCount = " + SELECT COUNT(0) + FROM pp_newsletter_templates AS pnt + WHERE {$whereSql} + "; + + $stmtCount = $this->db->query($sqlCount, $params); + $countRows = $stmtCount ? $stmtCount->fetchAll() : []; + $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; + + $sql = " + SELECT + pnt.id, + pnt.name, + pnt.is_admin + FROM pp_newsletter_templates AS pnt + WHERE {$whereSql} + ORDER BY {$sortSql} {$sortDir}, pnt.id ASC + LIMIT {$perPage} OFFSET {$offset} + "; + + $stmt = $this->db->query($sql, $params); + $items = $stmt ? $stmt->fetchAll() : []; + + return [ + 'items' => is_array($items) ? $items : [], + 'total' => $total, + ]; + } +} + diff --git a/autoload/admin/Controllers/NewsletterController.php b/autoload/admin/Controllers/NewsletterController.php new file mode 100644 index 0000000..ceef967 --- /dev/null +++ b/autoload/admin/Controllers/NewsletterController.php @@ -0,0 +1,514 @@ +repository = $repository; + $this->previewRenderer = $previewRenderer; + $this->formHandler = new FormRequestHandler(); + } + + public function list(): string + { + return $this->emails_list(); + } + + public function view_list(): string + { + return $this->list(); + } + + public function emails_list(): string + { + $sortableColumns = ['email', 'status']; + $filterDefinitions = [ + [ + 'key' => 'email', + 'label' => 'Email', + 'type' => 'text', + ], + [ + 'key' => 'status', + 'label' => 'Potwierdzony', + 'type' => 'select', + 'options' => [ + '' => '- potwierdzony -', + '1' => 'tak', + '0' => 'nie', + ], + ], + ]; + + $listRequest = \admin\Support\TableListRequestFactory::fromRequest( + $filterDefinitions, + $sortableColumns, + 'email' + ); + + $sortDir = $listRequest['sortDir']; + if (trim((string)\S::get('sort')) === '') { + $sortDir = 'ASC'; + } + + $result = $this->repository->listSubscribersForAdmin( + $listRequest['filters'], + $listRequest['sortColumn'], + $sortDir, + $listRequest['page'], + $listRequest['perPage'] + ); + + $rows = []; + $lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1; + foreach ($result['items'] as $item) { + $id = (int)($item['id'] ?? 0); + $email = trim((string)($item['email'] ?? '')); + $status = (int)($item['status'] ?? 0); + + $rows[] = [ + 'lp' => $lp++ . '.', + 'email' => htmlspecialchars($email, ENT_QUOTES, 'UTF-8'), + 'status' => $status === 1 ? 'tak' : 'nie', + '_actions' => [ + [ + 'label' => 'Usun', + 'url' => '/admin/newsletter/email_delete/id=' . $id, + 'class' => 'btn btn-xs btn-danger', + 'confirm' => 'Na pewno chcesz usunac wybrany adres email?', + ], + ], + ]; + } + + $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' => 'email', 'sort_key' => 'email', 'label' => 'Email', 'sortable' => true], + ['key' => 'status', 'sort_key' => 'status', 'label' => 'Potwierdzony', 'class' => 'text-center', 'sortable' => true, 'raw' => true], + ], + $rows, + $listRequest['viewFilters'], + [ + 'column' => $listRequest['sortColumn'], + 'dir' => $sortDir, + ], + [ + 'page' => $listRequest['page'], + 'per_page' => $listRequest['perPage'], + 'total' => $total, + 'total_pages' => $totalPages, + ], + array_merge($listRequest['queryFilters'], [ + 'sort' => $listRequest['sortColumn'], + 'dir' => $sortDir, + 'per_page' => $listRequest['perPage'], + ]), + $listRequest['perPageOptions'], + $sortableColumns, + '/admin/newsletter/emails_list/', + 'Brak danych w tabeli.' + ); + + return \Tpl::view('newsletter/emails-list', [ + 'viewModel' => $viewModel, + ]); + } + + public function email_delete(): void + { + if ($this->repository->deleteSubscriber((int)\S::get('id'))) { + \S::alert('Adres email zostal usuniety.'); + } + + header('Location: /admin/newsletter/emails_list/'); + exit; + } + + public function delete(): void + { + $this->email_delete(); + } + + public function prepare(): string + { + \S::alert('Funkcjonalnosc "Wysylka - przygotowanie" jest tymczasowo wylaczona.'); + header('Location: /admin/newsletter/emails_list/'); + exit; + } + + public function preview(): string + { + return ''; + } + + public function send(): void + { + \S::alert('Funkcjonalnosc "Wysylka - przygotowanie" jest tymczasowo wylaczona.'); + header('Location: /admin/newsletter/emails_list/'); + exit; + } + + public function settings(): string + { + $settings = $this->repository->getSettings(); + $validationErrors = $_SESSION['form_errors'][$this->settingsFormId()] ?? null; + if ($validationErrors) { + unset($_SESSION['form_errors'][$this->settingsFormId()]); + } + + return \Tpl::view('newsletter/settings', [ + 'form' => $this->buildSettingsFormViewModel($settings, $validationErrors), + ]); + } + + public function settings_save(): void + { + $legacyValues = \S::get('values'); + if ($legacyValues) { + $values = json_decode((string)$legacyValues, true); + if (!is_array($values)) { + echo json_encode(['status' => 'error', 'msg' => 'Nieprawidlowe dane formularza.']); + exit; + } + + $this->repository->saveSettings($values); + \S::alert('Ustawienia zostaly zapisane.'); + + echo json_encode(['status' => 'ok', 'msg' => 'Ustawienia zostaly zapisane.']); + exit; + } + + $viewModel = $this->buildSettingsFormViewModel($this->repository->getSettings()); + $result = $this->formHandler->handleSubmit($viewModel, $_POST); + if (!$result['success']) { + $_SESSION['form_errors'][$this->settingsFormId()] = $result['errors']; + echo json_encode(['success' => false, 'errors' => $result['errors']]); + exit; + } + + $this->repository->saveSettings($result['data']); + \S::alert('Ustawienia zostaly zapisane.'); + + echo json_encode([ + 'success' => true, + 'message' => 'Ustawienia zostaly zapisane.', + ]); + exit; + } + + public function email_templates_user(): string + { + \S::alert('Funkcjonalnosc "Szablony uzytkownika" jest tymczasowo wylaczona.'); + header('Location: /admin/newsletter/email_templates_admin/'); + exit; + } + + public function email_templates_admin(): string + { + $viewModel = $this->templatesListViewModel(); + return \Tpl::view('newsletter/email-templates-admin', [ + 'viewModel' => $viewModel, + ]); + } + + public function email_template_edit(): string + { + $template = $this->repository->templateDetails((int)\S::get('id')); + if (!is_array($template) || (int)($template['is_admin'] ?? 0) !== 1) { + \S::alert('Dostepne sa tylko szablony administracyjne.'); + header('Location: /admin/newsletter/email_templates_admin/'); + exit; + } + + $formId = $this->templateFormId((int)$template['id']); + $validationErrors = $_SESSION['form_errors'][$formId] ?? null; + if ($validationErrors) { + unset($_SESSION['form_errors'][$formId]); + } + + return \Tpl::view('newsletter/email-template-edit', [ + 'form' => $this->buildTemplateFormViewModel($template, $validationErrors), + ]); + } + + public function template_save(): void + { + $legacyValues = \S::get('values'); + if ($legacyValues) { + $values = json_decode((string)$legacyValues, true); + $response = ['status' => 'error', 'msg' => 'Podczas zapisywania wystapil blad.']; + + if (is_array($values)) { + $templateId = (int)($values['id'] ?? 0); + $template = $this->repository->templateDetails($templateId); + + if (is_array($template) && (int)($template['is_admin'] ?? 0) === 1) { + $id = $this->repository->saveTemplate( + $templateId, + (string)($values['name'] ?? ''), + (string)($values['text'] ?? '') + ); + if ($id) { + $response = ['status' => 'ok', 'msg' => 'Zmiany zostaly zapisane.', 'id' => $id]; + } + } + } + + echo json_encode($response); + exit; + } + + $template = $this->repository->templateDetails((int)\S::get('id')); + if (!is_array($template) || (int)($template['is_admin'] ?? 0) !== 1) { + echo json_encode([ + 'success' => false, + 'errors' => ['general' => 'Dostepne sa tylko szablony administracyjne.'], + ]); + exit; + } + + $form = $this->buildTemplateFormViewModel($template); + $result = $this->formHandler->handleSubmit($form, $_POST); + if (!$result['success']) { + $_SESSION['form_errors'][$this->templateFormId((int)$template['id'])] = $result['errors']; + echo json_encode(['success' => false, 'errors' => $result['errors']]); + exit; + } + + $data = $result['data']; + $id = $this->repository->saveTemplate( + (int)($template['id'] ?? 0), + (string)($data['name'] ?? ''), + (string)($data['text'] ?? '') + ); + + if ($id) { + echo json_encode([ + 'success' => true, + 'id' => $id, + 'message' => 'Zmiany zostaly zapisane.', + ]); + exit; + } + + echo json_encode([ + 'success' => false, + 'errors' => ['general' => 'Podczas zapisywania wystapil blad.'], + ]); + exit; + } + + public function email_template_delete(): void + { + \S::alert('Usuwanie szablonow uzytkownika jest tymczasowo wylaczone.'); + header('Location: /admin/newsletter/email_templates_admin/'); + exit; + } + + private function buildSettingsFormViewModel(array $settings, ?array $errors = null): FormEditViewModel + { + $data = [ + 'newsletter_header' => (string)($settings['newsletter_header'] ?? ''), + 'newsletter_footer' => (string)($settings['newsletter_footer'] ?? ''), + ]; + + $fields = [ + FormField::editor('newsletter_header', [ + 'label' => 'Naglowek', + 'height' => 150, + ]), + FormField::editor('newsletter_footer', [ + 'label' => 'Stopka', + 'height' => 150, + ]), + ]; + + $actionUrl = '/admin/newsletter/settings_save/'; + $actions = [ + FormAction::save($actionUrl, '/admin/newsletter/settings/'), + ]; + + return new FormEditViewModel( + $this->settingsFormId(), + 'Edycja ustawien newslettera', + $data, + $fields, + [], + $actions, + 'POST', + $actionUrl, + '/admin/newsletter/settings/', + true, + [], + null, + $errors + ); + } + + private function buildTemplateFormViewModel(array $template, ?array $errors = null): FormEditViewModel + { + $templateId = (int)($template['id'] ?? 0); + $isAdminTemplate = (int)($template['is_admin'] ?? 0) === 1; + $isNew = $templateId <= 0; + + $data = [ + 'id' => $templateId, + 'name' => (string)($template['name'] ?? ''), + 'text' => (string)($template['text'] ?? ''), + ]; + + $nameAttrs = []; + if ($isAdminTemplate) { + $nameAttrs['readonly'] = 'readonly'; + } + + $fields = [ + FormField::text('name', [ + 'label' => 'Nazwa', + 'required' => true, + 'attributes' => $nameAttrs, + ]), + FormField::editor('text', [ + 'label' => 'Tresc', + 'required' => true, + 'height' => 350, + ]), + ]; + + $backUrl = '/admin/newsletter/email_templates_admin/'; + $actionUrl = '/admin/newsletter/template_save/' . ($isNew ? '' : ('id=' . $templateId)); + $actions = [ + FormAction::save($actionUrl, $backUrl), + FormAction::cancel($backUrl), + ]; + + return new FormEditViewModel( + $this->templateFormId($templateId), + 'Edycja szablonu newslettera', + $data, + $fields, + [], + $actions, + 'POST', + $actionUrl, + $backUrl, + true, + ['id' => $templateId], + null, + $errors + ); + } + + private function templatesListViewModel(): PaginatedTableViewModel + { + $sortableColumns = ['name']; + $filterDefinitions = [ + [ + 'key' => 'name', + 'label' => 'Nazwa', + 'type' => 'text', + ], + ]; + + $basePath = '/admin/newsletter/email_templates_admin/'; + + $listRequest = \admin\Support\TableListRequestFactory::fromRequest( + $filterDefinitions, + $sortableColumns, + 'name' + ); + + $sortDir = $listRequest['sortDir']; + if (trim((string)\S::get('sort')) === '') { + $sortDir = 'ASC'; + } + + $result = $this->repository->listTemplatesForAdmin( + true, + $listRequest['filters'], + $listRequest['sortColumn'], + $sortDir, + $listRequest['page'], + $listRequest['perPage'] + ); + + $rows = []; + $lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1; + foreach ($result['items'] as $item) { + $id = (int)($item['id'] ?? 0); + $name = trim((string)($item['name'] ?? '')); + + $actions = [ + [ + 'label' => 'Edytuj', + 'url' => '/admin/newsletter/email_template_edit/id=' . $id, + 'class' => 'btn btn-xs btn-primary', + ], + ]; + + $rows[] = [ + 'lp' => $lp++ . '.', + 'name' => '' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '', + '_actions' => $actions, + ]; + } + + $total = (int)$result['total']; + $totalPages = max(1, (int)ceil($total / $listRequest['perPage'])); + + return new PaginatedTableViewModel( + [ + ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false], + ['key' => 'name', 'sort_key' => 'name', 'label' => 'Nazwa', 'sortable' => true, 'raw' => true], + ], + $rows, + $listRequest['viewFilters'], + [ + 'column' => $listRequest['sortColumn'], + 'dir' => $sortDir, + ], + [ + 'page' => $listRequest['page'], + 'per_page' => $listRequest['perPage'], + 'total' => $total, + 'total_pages' => $totalPages, + ], + array_merge($listRequest['queryFilters'], [ + 'sort' => $listRequest['sortColumn'], + 'dir' => $sortDir, + 'per_page' => $listRequest['perPage'], + ]), + $listRequest['perPageOptions'], + $sortableColumns, + $basePath, + 'Brak danych w tabeli.' + ); + } + + private function settingsFormId(): string + { + return 'newsletter-settings-edit'; + } + + private function templateFormId(int $templateId): string + { + return 'newsletter-template-edit-' . $templateId; + } +} diff --git a/autoload/admin/class.Site.php b/autoload/admin/class.Site.php index 12f12e8..e6c453d 100644 --- a/autoload/admin/class.Site.php +++ b/autoload/admin/class.Site.php @@ -275,6 +275,17 @@ class Site new \Domain\Languages\LanguagesRepository( $mdb ) ); }, + 'Newsletter' => function() { + global $mdb; + + return new \admin\Controllers\NewsletterController( + new \Domain\Newsletter\NewsletterRepository( + $mdb, + new \Domain\Settings\SettingsRepository( $mdb ) + ), + new \Domain\Newsletter\NewsletterPreviewRenderer() + ); + }, ]; return self::$newControllers; diff --git a/autoload/admin/controls/class.Newsletter.php b/autoload/admin/controls/class.Newsletter.php deleted file mode 100644 index 97e2090..0000000 --- a/autoload/admin/controls/class.Newsletter.php +++ /dev/null @@ -1,94 +0,0 @@ - updateSetting( 'newsletter_footer', $values['newsletter_footer'] ?? '' ); - $settingsRepository -> updateSetting( 'newsletter_header', $values['newsletter_header'] ?? '' ); - - \S::alert( 'Ustawienia zostały zapisane.' ); - - echo json_encode( [ 'status' => 'ok', 'msg' => 'Ustawienia zostały zapisane.' ] ); - exit; - } - - public static function settings() - { - $settingsRepository = new \Domain\Settings\SettingsRepository(); - - return \admin\view\Newsletter::settings( - $settingsRepository -> getSettings() - ); - } - - public static function email_templates_user() - { - return \admin\view\Newsletter::email_templates_user(); - } - - public static function email_templates_admin() - { - return \admin\view\Newsletter::email_templates_admin(); - } - - public static function email_template_delete() - { - $is_admin = \admin\factory\Newsletter::is_admin_template( \S::get( 'id' ) ); - - if ( !$is_admin and \admin\factory\Newsletter::newsletter_template_delete( \S::get( 'id' ) ) ) - \S::alert( 'Szablon newslettera został usunięty.' ); - - if ( $is_admin ) - header( 'Location: /admin/newsletter/email_templates_admin/' ); - else - header( 'Location: /admin/newsletter/email_templates_user/' ); - exit; - } - - public static function email_template_edit() - { - return \admin\view\Newsletter::email_template_edit( - \admin\factory\Newsletter::email_template_detalis( - \S::get( 'id' ) - ) - ); - } - - public static function template_save() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania wystąpił błąd. Proszę spróbować ponownie.' ]; - $values = json_decode( \S::get( 'values' ), true ); - - if ( $id = \admin\factory\Newsletter::template_save( $values['id'], $values['name'], $values['text'] ) ) - $response = [ 'status' => 'ok', 'msg' => 'Zmiany zostały zapisane.', 'id' => $id ]; - - echo json_encode( $response ); - exit; - } -} diff --git a/autoload/admin/factory/class.Newsletter.php b/autoload/admin/factory/class.Newsletter.php index 21e8d11..ddd14f7 100644 --- a/autoload/admin/factory/class.Newsletter.php +++ b/autoload/admin/factory/class.Newsletter.php @@ -1,76 +1,48 @@ get( 'pp_newsletter_templates', 'is_admin', [ 'id' => (int)$template_id ] ); + return self::repository() -> isAdminTemplate( (int)$template_id ); } - + public static function newsletter_template_delete( $template_id ) { - global $mdb; - return $mdb -> delete( 'pp_newsletter_templates', [ 'id' => (int)$template_id ] ); + return self::repository() -> deleteTemplate( (int)$template_id ); } - + public static function send( $dates, $template ) { - global $mdb; - - $results = $mdb -> select( 'pp_newsletter', 'email', [ 'status' => 1 ] ); - if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row ) - { - $mdb -> insert( 'pp_newsletter_send', [ - 'email' => $row, - 'dates' => $dates, - 'id_template' => $template ? $template : null - ] ); - } - return true; + return self::repository() -> queueSend( (string)$dates, (int)$template ); } - - public static function email_template_detalis ($id_template) + + public static function email_template_detalis( $id_template ) { - global $mdb; - - $result = $mdb -> get ('pp_newsletter_templates', '*', [ 'id' => (int)$id_template ] ); - return $result; + return self::repository() -> templateDetails( (int)$id_template ); } - + public static function template_save( $id, $name, $text ) { - global $mdb; - - if ( !$id ) - { - if ( $mdb -> insert( 'pp_newsletter_templates', [ - 'name' => $name, - 'text' => $text - ] ) ) - { - \S::delete_dir( '../temp/' ); - return $mdb -> id(); - } - } - else - { - $mdb -> update( 'pp_newsletter_templates', [ - 'name' => $name, - 'text' => $text - ], [ - 'id' => (int)$id - ] ); - - \S::delete_dir( '../temp/' ); - return $id; - } + return self::repository() -> saveTemplate( (int)$id, (string)$name, (string)$text ); } - + public static function templates_list() { - global $mdb; - return $mdb -> select( 'pp_newsletter_templates', '*', [ 'is_admin' => 0, 'ORDER' => [ 'name' => 'ASC' ] ] ); + return self::repository() -> listTemplatesSimple( false ); } -} + + private static function repository(): NewsletterRepository + { + global $mdb; + + return new NewsletterRepository( + $mdb, + new SettingsRepository($mdb) + ); + } +} \ No newline at end of file diff --git a/autoload/admin/view/class.Newsletter.php b/autoload/admin/view/class.Newsletter.php deleted file mode 100644 index f40fb74..0000000 --- a/autoload/admin/view/class.Newsletter.php +++ /dev/null @@ -1,55 +0,0 @@ - render( 'newsletter/emails-list' ); - } - - public static function preview( $articles, $settings, $template, $dates = '' ) - { - $tpl = new \Tpl; - $tpl -> articles = $articles; - $tpl -> settings = $settings; - $tpl -> template = $template; - $tpl -> dates = $dates; - return $tpl -> render( 'newsletter/preview' ); - } - - public static function prepare( $templates ) - { - $tpl = new \Tpl; - $tpl -> templates = $templates; - return $tpl -> render( 'newsletter/prepare' ); - } - - public static function settings( $settings ) - { - $tpl = new \Tpl; - $tpl -> settings = $settings; - return $tpl -> render( 'newsletter/settings' ); - } - - public static function email_templates_user() - { - $tpl = new \Tpl; - return $tpl -> render( 'newsletter/email-templates-user' ); - } - - public static function email_templates_admin() - { - $tpl = new \Tpl; - return $tpl -> render( 'newsletter/email-templates-admin' ); - } - - public static function email_template_edit($template) - { - $tpl = new \Tpl; - $tpl -> email_template = $template; - return $tpl -> render( 'newsletter/email-template-edit' ); - } -} - \ No newline at end of file diff --git a/autoload/front/factory/class.Newsletter.php b/autoload/front/factory/class.Newsletter.php index 7521364..ee8da07 100644 --- a/autoload/front/factory/class.Newsletter.php +++ b/autoload/front/factory/class.Newsletter.php @@ -27,6 +27,8 @@ class Newsletter { global $mdb, $settings, $lang; $settingsRepository = new \Domain\Settings\SettingsRepository( $mdb ); + $newsletterRepository = new \Domain\Newsletter\NewsletterRepository( $mdb, $settingsRepository ); + $previewRenderer = new \Domain\Newsletter\NewsletterPreviewRenderer(); $settingsDetails = $settingsRepository -> getSettings(); $results = $mdb -> query( 'SELECT * FROM pp_newsletter_send ORDER BY id ASC LIMIT ' . $limit ) -> fetchAll(); @@ -36,10 +38,15 @@ class Newsletter { $dates = explode( ' - ', $row['dates'] ); - $text = \admin\view\Newsletter::preview( - \admin\factory\Articles::articles_by_date_add( $dates[0], $dates[1] ), + $articles = []; + if ( isset( $dates[0], $dates[1] ) ) + $articles = \admin\factory\Articles::articles_by_date_add( $dates[0], $dates[1] ); + + $text = $previewRenderer -> render( + is_array( $articles ) ? $articles : [], $settingsDetails, - \admin\factory\Newsletter::email_template_detalis($row['id_template']) + $newsletterRepository -> templateDetails( (int)$row['id_template'] ), + (string)$row['dates'] ); if ( $settings['ssl'] ) $base = 'https'; else $base = 'http'; @@ -110,7 +117,8 @@ class Newsletter public static function get_template( $template_name ) { global $mdb; - return $mdb -> get( 'pp_newsletter_templates', 'text', [ 'name' => $template_name ] ); + $repository = new \Domain\Newsletter\NewsletterRepository( $mdb ); + return $repository -> templateByName( (string)$template_name ); } public static function newsletter_signout( $email ) diff --git a/tests/Unit/Domain/Newsletter/NewsletterRepositoryTest.php b/tests/Unit/Domain/Newsletter/NewsletterRepositoryTest.php new file mode 100644 index 0000000..406562c --- /dev/null +++ b/tests/Unit/Domain/Newsletter/NewsletterRepositoryTest.php @@ -0,0 +1,78 @@ +createMock(\medoo::class); + $repository = new NewsletterRepository($mockDb, $this->createMock(SettingsRepository::class)); + + $this->assertNull($repository->templateDetails(0)); + } + + public function testTemplateDetailsReturnsArray(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_newsletter_templates', '*', ['id' => 7]) + ->willReturn(['id' => 7, 'name' => 'Tpl']); + + $repository = new NewsletterRepository($mockDb, $this->createMock(SettingsRepository::class)); + $details = $repository->templateDetails(7); + + $this->assertIsArray($details); + $this->assertSame(7, $details['id']); + } + + public function testSaveSettingsUpdatesHeaderAndFooter(): void + { + $mockDb = $this->createMock(\medoo::class); + $settingsRepository = $this->createMock(SettingsRepository::class); + + $settingsRepository->expects($this->exactly(2)) + ->method('updateSetting') + ->withConsecutive( + ['newsletter_footer', 'Footer'], + ['newsletter_header', 'Header'] + ); + + $repository = new NewsletterRepository($mockDb, $settingsRepository); + + $this->assertTrue($repository->saveSettings([ + 'newsletter_header' => 'Header', + 'newsletter_footer' => 'Footer', + ])); + } + + public function testDeleteTemplateReturnsFalseForAdminTemplate(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_newsletter_templates', 'is_admin', ['id' => 5]) + ->willReturn(1); + + $repository = new NewsletterRepository($mockDb, $this->createMock(SettingsRepository::class)); + + $this->assertFalse($repository->deleteTemplate(5)); + } + + public function testTemplateByNameReturnsText(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_newsletter_templates', 'text', ['name' => '#abc']) + ->willReturn('Template text'); + + $repository = new NewsletterRepository($mockDb, $this->createMock(SettingsRepository::class)); + + $this->assertSame('Template text', $repository->templateByName('#abc')); + } +} \ No newline at end of file diff --git a/tests/Unit/admin/Controllers/NewsletterControllerTest.php b/tests/Unit/admin/Controllers/NewsletterControllerTest.php new file mode 100644 index 0000000..24ddb74 --- /dev/null +++ b/tests/Unit/admin/Controllers/NewsletterControllerTest.php @@ -0,0 +1,70 @@ +repository = $this->createMock(NewsletterRepository::class); + $this->previewRenderer = $this->createMock(NewsletterPreviewRenderer::class); + $this->controller = new NewsletterController($this->repository, $this->previewRenderer); + } + + public function testConstructorAcceptsDependencies(): void + { + $controller = new NewsletterController($this->repository, $this->previewRenderer); + $this->assertInstanceOf(NewsletterController::class, $controller); + } + + public function testHasMainActionMethods(): void + { + $this->assertTrue(method_exists($this->controller, 'emails_list')); + $this->assertTrue(method_exists($this->controller, 'prepare')); + $this->assertTrue(method_exists($this->controller, 'send')); + $this->assertTrue(method_exists($this->controller, 'preview')); + $this->assertTrue(method_exists($this->controller, 'settings')); + $this->assertTrue(method_exists($this->controller, 'settings_save')); + $this->assertTrue(method_exists($this->controller, 'email_templates_user')); + $this->assertTrue(method_exists($this->controller, 'email_templates_admin')); + $this->assertTrue(method_exists($this->controller, 'email_template_edit')); + $this->assertTrue(method_exists($this->controller, 'template_save')); + $this->assertTrue(method_exists($this->controller, 'email_template_delete')); + } + + public function testActionMethodReturnTypes(): void + { + $reflection = new \ReflectionClass($this->controller); + + $this->assertEquals('string', (string)$reflection->getMethod('emails_list')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('prepare')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('send')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('preview')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('settings')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('settings_save')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('email_templates_user')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('email_templates_admin')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('email_template_edit')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('template_save')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('email_template_delete')->getReturnType()); + } + + public function testConstructorRequiresRepositoryAndRenderer(): void + { + $reflection = new \ReflectionClass(NewsletterController::class); + $constructor = $reflection->getConstructor(); + $params = $constructor->getParameters(); + + $this->assertCount(2, $params); + $this->assertEquals('Domain\Newsletter\NewsletterRepository', $params[0]->getType()->getName()); + $this->assertEquals('Domain\Newsletter\NewsletterPreviewRenderer', $params[1]->getType()->getName()); + } +} \ No newline at end of file diff --git a/updates/0.20/ver_0.258.zip b/updates/0.20/ver_0.258.zip new file mode 100644 index 0000000..be4886c Binary files /dev/null and b/updates/0.20/ver_0.258.zip differ diff --git a/updates/0.20/ver_0.258_files.txt b/updates/0.20/ver_0.258_files.txt new file mode 100644 index 0000000..63fcf25 --- /dev/null +++ b/updates/0.20/ver_0.258_files.txt @@ -0,0 +1,5 @@ +F: ../autoload/admin/controls/class.Newsletter.php +F: ../autoload/admin/view/class.Newsletter.php +F: ../admin/templates/newsletter/prepare.php +F: ../admin/templates/newsletter/preview.php +F: ../admin/templates/newsletter/email-templates-user.php \ No newline at end of file diff --git a/updates/changelog.php b/updates/changelog.php index b402229..98bf05a 100644 --- a/updates/changelog.php +++ b/updates/changelog.php @@ -1,4 +1,18 @@ -ver. 0.256 - 12.02.2026
+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`) +- UPDATE - `email_template_edit` i `template_save` obsluguja tylko szablony administracyjne +- CLEANUP - usuniete nieuzywane szablony newslettera: `admin/templates/newsletter/prepare.php`, `admin/templates/newsletter/preview.php`, `admin/templates/newsletter/email-templates-user.php` +- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.258_files.txt` +
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 - `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` +
ver. 0.256 - 12.02.2026
- NEW - migracja modulu `Layouts` do architektury Domain + DI (`Domain\\Layouts\\LayoutsRepository`, `admin\\Controllers\\LayoutsController`) - UPDATE - lista `/admin/layouts/view_list/` przepieta z legacy `grid` na `components/table-list` (filtry, sortowanie, paginacja) - UPDATE - `layouts/layout-edit` korzysta z danych z repozytorium (menus/categories), bez wywolan legacy factory w widoku diff --git a/updates/versions.php b/updates/versions.php index b64e3e9..59bbd79 100644 --- a/updates/versions.php +++ b/updates/versions.php @@ -1,5 +1,5 @@