diff --git a/DATABASE_STRUCTURE.md b/DATABASE_STRUCTURE.md index 0253566..99353fe 100644 --- a/DATABASE_STRUCTURE.md +++ b/DATABASE_STRUCTURE.md @@ -183,3 +183,29 @@ Uzytkownicy panelu administratora. **Uzywane w:** `Domain\User\UserRepository`, `admin\Controllers\UsersController`, `admin\factory\Users` **Aktualizacja 2026-02-12:** uzycia `pp_users` sa prowadzone przez `Domain\\User\\UserRepository` (legacy `admin\\factory\\Users` usunieto). + +## pp_langs +Jezyki panelu i frontendu. + +| Kolumna | Opis | +|---------|------| +| id | PK (2-literowe ID jezyka, np. pl, en) | +| name | Nazwa jezyka | +| status | 1 = aktywny, 0 = nieaktywny | +| start | 1 = domyslny jezyk | +| o | Kolejnosc | + +**Uzywane w:** `Domain\\Languages\\LanguagesRepository`, `admin\\Controllers\\LanguagesController`, `admin\\factory\\Languages`, `front\\factory\\Languages` + +## pp_langs_translations +Slownik tlumaczen panelu/frontendu. + +| Kolumna | Opis | +|---------|------| +| id | PK | +| text | Klucz/tekst bazowy | +| | Kolumny dynamiczne per jezyk (np. pl, en) | + +**Uzywane w:** `Domain\\Languages\\LanguagesRepository`, `admin\\Controllers\\LanguagesController`, `front\\factory\\Languages` + +**Aktualizacja 2026-02-12:** modul jezykow i tlumaczen (`pp_langs`, `pp_langs_translations`) obslugiwany przez `Domain\\Languages\\LanguagesRepository`. diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index b6b27f7..aa1961c 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -391,3 +391,14 @@ Aktualnie w suite sÄ… też testy modułów `Dictionaries`, `Articles` i `Users` - Usuniêto legacy klasy: `autoload/admin/controls/class.Users.php`, `autoload/admin/factory/class.Users.php`, `autoload/admin/view/class.Users.php`. - Walidacja: przy w³¹czonym 2FA pole `twofa_email` jest wymagane. - Widoki users przeniesione na `components/table-list` i `components/form-edit`. +- **NOWE:** `Domain\\Languages\\LanguagesRepository` - repozytorium jezykow i tlumaczen (lista, zapis, usuwanie, max_order) +- **NOWE:** `admin\\Controllers\\LanguagesController` - kontroler DI (`list/view_list`, `language_*`, `translation_*`) +- **UPDATE:** modul Languages przepiety z `grid/gridEdit` na `components/table-list` i `components/form-edit` +- **CLEANUP:** usuniete legacy klasy `autoload/admin/controls/class.Languages.php`, `autoload/admin/view/class.Languages.php` +- Testy: 130 tests, 301 assertions + +## Aktualizacja 2026-02-12 (Languages final) +- Dodano `Domain\\Languages\\LanguagesRepository` oraz `admin\\Controllers\\LanguagesController`. +- Modul `/admin/languages/` (jezyki + tlumaczenia) dziala na nowym routingu DI. +- Widoki jezykow przepiete na `components/table-list` i `components/form-edit`. +- Usunieto legacy: `autoload/admin/controls/class.Languages.php`, `autoload/admin/view/class.Languages.php`. diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md index 63377ff..d1b0a81 100644 --- a/REFACTORING_PLAN.md +++ b/REFACTORING_PLAN.md @@ -574,3 +574,19 @@ Gdy `persist = true`: - `UsersController` obsluguje: `list/view_list`, `user_edit`, `user_save`, `user_delete`, `login_form`, `twofa`. - Dodano walidacje warunkowa: `twofa_email` wymagany gdy `twofa_enabled = 1`. - Widoki users migrowane z `grid/gridEdit` na `table-list` i `form-edit`. + +## Aktualizacja 2026-02-12 - Languages +- **NOWE:** `Domain\\Languages\\LanguagesRepository` (languages + translations CRUD/list) +- **NOWE:** `admin\\Controllers\\LanguagesController` (DI) +- **UPDATE:** `admin\\Site` - nowy kontroler DI dla modulu `Languages` +- **UPDATE:** `admin\\factory\\Languages` jako fasada delegujaca do repozytorium +- **UPDATE:** widoki `languages/*` migrowane na `components/table-list` i `components/form-edit` +- **CLEANUP:** usunieto legacy `admin\\controls\\Languages` i `admin\\view\\Languages` +- Testy po zmianie: 130 tests, 301 assertions + +## Aktualizacja 2026-02-12 (Languages final) +- **NOWE:** `Domain\\Languages\\LanguagesRepository` (list/save/delete dla jezykow i tlumaczen) +- **NOWE:** `admin\\Controllers\\LanguagesController` (DI) dla akcji `view_list/list`, `language_*`, `translation_*` +- **UPDATE:** `admin\\factory\\Languages` jako fasada delegujaca do repozytorium +- **CLEANUP:** usunieto legacy `admin\\controls\\Languages` oraz `admin\\view\\Languages` +- **UPDATE:** poprawki globalne `components/table-list` dla krotkich kolumn/filtrów diff --git a/TESTING.md b/TESTING.md index 0a94d8b..7028dfe 100644 --- a/TESTING.md +++ b/TESTING.md @@ -164,3 +164,23 @@ Ostatnio zweryfikowano: 2026-02-12 ```text OK (120 tests, 262 assertions) ``` + +Aktualizacja po migracji Languages (2026-02-12): +```text +OK (130 tests, 301 assertions) +``` + +Nowe testy dodane 2026-02-12: +- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php` +- `tests/Unit/admin/Controllers/LanguagesControllerTest.php` + +## Aktualizacja suite (release 0.254) +Ostatnio zweryfikowano: 2026-02-12 + +```text +OK (130 tests, 301 assertions) +``` + +Nowe testy dodane 2026-02-12: +- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php` +- `tests/Unit/admin/Controllers/LanguagesControllerTest.php` diff --git a/admin/templates/components/table-list.php b/admin/templates/components/table-list.php index ddb168b..c6a91d4 100644 --- a/admin/templates/components/table-list.php +++ b/admin/templates/components/table-list.php @@ -23,11 +23,11 @@ $isCompactColumn = function(array $column): bool { $key = strtolower(trim((string)($column['key'] ?? ''))); $label = strtolower(trim((string)($column['label'] ?? ''))); - if (in_array($key, ['status', 'active', 'enabled', 'is_active'], true)) { + if (in_array($key, ['status', 'active', 'enabled', 'is_active', 'start', 'default'], true)) { return true; } - if (in_array($label, ['status', 'aktywny', 'aktywnosc', 'active'], true)) { + if (in_array($label, ['status', 'aktywny', 'aktywnosc', 'active', 'domyslny', 'domyÅ›lny', 'default'], true)) { return true; } @@ -277,9 +277,9 @@ $isCompactColumn = function(array $column): bool { } .js-table-filters-form .js-filter-compact-select { - width: auto; - min-width: 110px; - max-width: 140px; + width: 100%; + min-width: 0; + max-width: none; } .table-list-table th.table-col-compact, diff --git a/admin/templates/languages/language-edit.php b/admin/templates/languages/language-edit.php index af603a8..be2362b 100644 --- a/admin/templates/languages/language-edit.php +++ b/admin/templates/languages/language-edit.php @@ -1,101 +1,2 @@ - $this->form]); ?> -ob_start(); -?> - language['id'] ) - { - echo \Html::input( - array( - 'type' => 'hidden', - 'name' => 'id', - 'value' => $this -> language['id'] - ) - ); - echo \Html::input( - array( - 'type' => 'hidden', - 'name' => 'o', - 'value' => $this -> language['o'] - ) - ); - echo \Html::input( - array( - 'label' => 'JÄ™zyk', - 'type' => 'text', - 'class' => 'require', - 'name' => 'name', - 'value' => $this -> language['name'] - ) - ); - } - else - { - echo \Html::input( - array( - 'type' => 'hidden', - 'name' => 'o', - 'value' => $this -> order + 1 - ) - ); - echo \Html::input( - array( - 'label' => 'JÄ™zyk', - 'type' => 'text', - 'class' => 'require', - 'name' => 'name' - ) - ); - echo \Html::input( - array( - 'label' => 'ID (2 znaki)', - 'class' => 'require', - 'type' => 'text', - 'name' => 'id' - ) - ); - } - ?> - 'Aktywny', - 'name' => 'status', - 'checked' => $this -> language['status'] == 1 ? true : false - ) - );?> - 'DomyÅ›lny', - 'name' => 'start', - 'checked' => $this -> language['start'] == 1 ? true : false - ) - );?> - id = 'language-edit'; -$grid -> gdb_opt = $gdb; -$grid -> include_plugins = true; -$grid -> title = 'Edycja jÄ™zyka'; -$grid -> external_code = $out; -$grid -> actions = [ - 'save' => [ 'url' => '/admin/languages/language_save/', 'back_url' => '/admin/languages/view_list/' ], - 'cancel' => [ 'url' => '/admin/languages/view_list/' ] - ]; -$grid -> persist_edit = true; -$grid -> id_param = 'id'; -echo $grid -> draw(); -?> - \ No newline at end of file diff --git a/admin/templates/languages/languages-list.php b/admin/templates/languages/languages-list.php index de4d85f..b336124 100644 --- a/admin/templates/languages/languages-list.php +++ b/admin/templates/languages/languages-list.php @@ -1,58 +1,2 @@ - $this->viewModel]); ?> -$grid = new \grid( 'pp_langs' ); -$grid -> gdb_opt = $gdb; -$grid -> order = [ 'column' => 'o', 'type' => 'ASC' ]; -$grid -> search = [ - [ 'name' => 'JÄ™zyk', 'db' => 'name', 'type' => 'text' ], - [ 'name' => 'Aktywny', 'db' => 'status', 'type' => 'select', 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ] ], - [ 'name' => 'DomyÅ›lny', 'db' => 'start', 'type' => 'select', 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ] ] - ]; -$grid -> columns_view = [ - [ - 'name' => 'Lp.', - 'th' => [ 'class' => 'g-lp' ], - 'td' => [ 'class' => 'g-center' ], - 'autoincrement' => true - ], - [ - 'name' => 'DomyÅ›lny', - 'db' => 'start', - 'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ], - 'td' => [ 'class' => 'g-center' ], - 'php' => 'if ( [start] ) echo "tak"; else echo "nie";' - ], - [ - 'name' => 'Aktywny', - 'db' => 'status', - 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ], - 'td' => [ 'class' => 'g-center' ] - ], - [ - 'name' => 'JÄ™zyk', - 'db' => 'name' - ], - [ - 'name' => 'Edytuj', - 'action' => [ 'type' => 'edit', 'url' => '/admin/languages/language_edit/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ], - [ - 'name' => 'UsuÅ„', - 'action' => [ 'type' => 'delete', 'url' => '/admin/languages/language_delete/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ] - ]; -$grid -> buttons = [ - [ - 'label' => 'Dodaj jÄ™zyk', - 'url' => '/admin/languages/language_edit/', - 'icon' => 'fa-plus-circle', - 'class' => 'btn-success' - ] - ]; -echo $grid -> draw(); \ No newline at end of file diff --git a/admin/templates/languages/translation-edit.php b/admin/templates/languages/translation-edit.php index 64895ed..be2362b 100644 --- a/admin/templates/languages/translation-edit.php +++ b/admin/templates/languages/translation-edit.php @@ -1,60 +1,2 @@ - $this->form]); ?> -ob_start(); -?> - 'text', - 'label' => 'Tekst', - 'name' => 'text', - 'class' => 'require', - 'value' => $this -> translation['text'] - ) -); - -if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $language ): - echo \Html::input( - array( - 'type' => 'text', - 'label' => $language['name'], - 'name' => $language['id'], - 'value' => $this -> translation[$language['id']] - ) ); - endforeach; -endif; -$out = ob_get_clean(); - -$grid = new \gridEdit; -$grid -> id = 'translation-edit'; -$grid -> gdb_opt = $gdb; -$grid -> include_plugins = true; -$grid -> title = 'Edycja tÅ‚umaczenia'; -$grid -> fields = [ - [ - 'db' => 'id', - 'type' => 'hidden', - 'value' => $this -> translation['id'] - ] -]; -$grid -> external_code = $out; -$grid -> actions = [ - 'save' => [ 'url' => '/admin/languages/translation_save/', 'back_url' => '/admin/languages/translation_list/' ], - 'cancel' => [ 'url' => '/admin/languages/translation_list/' ] -]; -$grid -> persist_edit = true; -$grid -> id_param = 'id'; -echo $grid -> draw(); -?> - \ No newline at end of file diff --git a/admin/templates/languages/translations-list.php b/admin/templates/languages/translations-list.php index a7e2d6d..b336124 100644 --- a/admin/templates/languages/translations-list.php +++ b/admin/templates/languages/translations-list.php @@ -1,44 +1,2 @@ - $this->viewModel]); ?> -$grid = new \grid( 'pp_langs_translations' ); -$grid -> gdb_opt = $gdb; -$grid -> order = [ 'column' => 'text', 'type' => 'ASC' ]; -$grid -> search = [ - [ 'name' => 'Tekst', 'db' => 'text', 'type' => 'text' ] - ]; -$grid -> columns_view = [ - [ - 'name' => 'Lp.', - 'th' => [ 'class' => 'g-lp' ], - 'td' => [ 'class' => 'g-center' ], - 'autoincrement' => true - ], - [ - 'name' => 'Tekst', - 'db' => 'text', - 'php' => 'echo "[text]";', - 'sort' => true - ], - [ - 'name' => 'Edytuj', - 'action' => [ 'type' => 'edit', 'url' => '/admin/languages/translation_edit/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ], - [ - 'name' => 'UsuÅ„', - 'action' => [ 'type' => 'delete', 'url' => '/admin/languages/translation_delete/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ] - ]; -$grid -> buttons = [ - [ - 'label' => 'Dodaj tÅ‚umaczenie', - 'url' => '/admin/languages/translation_edit/', - 'icon' => 'fa-plus-circle', - 'class' => 'btn-success' - ] - ]; -echo $grid -> draw(); \ No newline at end of file diff --git a/autoload/Domain/Languages/LanguagesRepository.php b/autoload/Domain/Languages/LanguagesRepository.php new file mode 100644 index 0000000..7957acc --- /dev/null +++ b/autoload/Domain/Languages/LanguagesRepository.php @@ -0,0 +1,330 @@ +db = $db; + } + + /** + * @return array{items: array>, total: int} + */ + public function listForAdmin( + array $filters, + string $sortColumn = 'o', + string $sortDir = 'ASC', + int $page = 1, + int $perPage = 15 + ): array { + $allowedSortColumns = [ + 'id' => 'pl.id', + 'name' => 'pl.name', + 'status' => 'pl.status', + 'start' => 'pl.start', + 'o' => 'pl.o', + ]; + + $sortSql = $allowedSortColumns[$sortColumn] ?? 'pl.o'; + $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 = []; + + $name = trim((string)($filters['name'] ?? '')); + if ($name !== '') { + if (strlen($name) > 255) { + $name = substr($name, 0, 255); + } + $where[] = 'pl.name LIKE :name'; + $params[':name'] = '%' . $name . '%'; + } + + $status = trim((string)($filters['status'] ?? '')); + if ($status === '0' || $status === '1') { + $where[] = 'pl.status = :status'; + $params[':status'] = (int)$status; + } + + $start = trim((string)($filters['start'] ?? '')); + if ($start === '0' || $start === '1') { + $where[] = 'pl.start = :start'; + $params[':start'] = (int)$start; + } + + $whereSql = implode(' AND ', $where); + + $sqlCount = " + SELECT COUNT(0) + FROM pp_langs AS pl + WHERE {$whereSql} + "; + + $stmtCount = $this->db->query($sqlCount, $params); + $countRows = $stmtCount ? $stmtCount->fetchAll() : []; + $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; + + $sql = " + SELECT + pl.id, + pl.name, + pl.status, + pl.start, + pl.o + FROM pp_langs AS pl + WHERE {$whereSql} + ORDER BY {$sortSql} {$sortDir}, pl.o ASC, pl.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 listTranslationsForAdmin( + array $filters, + string $sortColumn = 'text', + string $sortDir = 'ASC', + int $page = 1, + int $perPage = 15 + ): array { + $allowedSortColumns = [ + 'id' => 'plt.id', + 'text' => 'plt.text', + ]; + + $sortSql = $allowedSortColumns[$sortColumn] ?? 'plt.text'; + $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 = []; + + $text = trim((string)($filters['text'] ?? '')); + if ($text !== '') { + if (strlen($text) > 255) { + $text = substr($text, 0, 255); + } + $where[] = 'plt.text LIKE :text'; + $params[':text'] = '%' . $text . '%'; + } + + $whereSql = implode(' AND ', $where); + + $sqlCount = " + SELECT COUNT(0) + FROM pp_langs_translations AS plt + WHERE {$whereSql} + "; + + $stmtCount = $this->db->query($sqlCount, $params); + $countRows = $stmtCount ? $stmtCount->fetchAll() : []; + $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; + + $sql = " + SELECT + plt.id, + plt.text + FROM pp_langs_translations AS plt + WHERE {$whereSql} + ORDER BY {$sortSql} {$sortDir}, plt.id ASC + LIMIT {$perPage} OFFSET {$offset} + "; + + $stmt = $this->db->query($sql, $params); + $items = $stmt ? $stmt->fetchAll() : []; + + return [ + 'items' => is_array($items) ? $items : [], + 'total' => $total, + ]; + } + + public function languageDetails(string $languageId): ?array + { + $language = $this->db->get('pp_langs', '*', ['id' => $languageId]); + return $language ?: null; + } + + public function translationDetails(int $translationId): ?array + { + $translation = $this->db->get('pp_langs_translations', '*', ['id' => $translationId]); + return $translation ?: null; + } + + public function maxOrder(): int + { + $max = $this->db->max('pp_langs', 'o'); + return $max ? (int)$max : 0; + } + + public function languagesList(bool $onlyActive = false): array + { + $where = []; + if ($onlyActive) { + $where['status'] = 1; + } + + $rows = $this->db->select('pp_langs', '*', array_merge(['ORDER' => ['o' => 'ASC']], $where)); + return is_array($rows) ? $rows : []; + } + + public function deleteLanguage(string $languageId): bool + { + $languageId = $this->sanitizeLanguageId($languageId); + if ($languageId === null) { + return false; + } + + if ((int)$this->db->count('pp_langs') <= 1) { + return false; + } + + if (!$this->db->count('pp_langs', ['id' => $languageId])) { + return false; + } + + $dropResult = $this->db->query('ALTER TABLE pp_langs_translations DROP COLUMN `' . $languageId . '`'); + if (!$dropResult) { + return false; + } + + $deleteResult = $this->db->delete('pp_langs', ['id' => $languageId]); + if (!$deleteResult) { + return false; + } + + \S::htacces(); + \S::delete_dir('../temp/'); + return true; + } + + public function saveLanguage(string $languageId, string $name, $status, $start, int $order): ?string + { + $languageId = $this->sanitizeLanguageId($languageId); + if ($languageId === null) { + return null; + } + + $statusVal = $this->toSwitchValue($status); + $startVal = $this->toSwitchValue($start); + + $exists = (bool)$this->db->count('pp_langs', ['id' => $languageId]); + + if ($startVal === 1) { + $this->db->update('pp_langs', ['start' => 0], ['id[!]' => $languageId]); + } + + if ($exists) { + $this->db->update('pp_langs', [ + 'status' => $statusVal, + 'start' => $startVal, + 'name' => $name, + 'o' => $order, + ], [ + 'id' => $languageId, + ]); + } else { + $addResult = $this->db->query('ALTER TABLE pp_langs_translations ADD COLUMN `' . $languageId . '` TEXT NULL DEFAULT NULL'); + if (!$addResult) { + return null; + } + + $insertResult = $this->db->insert('pp_langs', [ + 'id' => $languageId, + 'name' => $name, + 'status' => $statusVal, + 'start' => $startVal, + 'o' => $order, + ]); + if (!$insertResult) { + return null; + } + } + + if (!(int)$this->db->count('pp_langs', ['start' => 1])) { + $idTmp = (string)$this->db->get('pp_langs', 'id', ['ORDER' => ['o' => 'ASC']]); + if ($idTmp !== '') { + $this->db->update('pp_langs', ['start' => 1], ['id' => $idTmp]); + } + } + + \S::htacces(); + \S::delete_dir('../temp/'); + return $languageId; + } + + public function deleteTranslation(int $translationId): bool + { + $result = $this->db->delete('pp_langs_translations', ['id' => $translationId]); + return (bool)$result; + } + + public function saveTranslation(int $translationId, string $text, array $translations): ?int + { + if ($translationId > 0) { + $this->db->update('pp_langs_translations', ['text' => $text], ['id' => $translationId]); + } else { + $insertResult = $this->db->insert('pp_langs_translations', ['text' => $text]); + if (!$insertResult) { + return null; + } + $translationId = (int)$this->db->id(); + } + + if ($translationId <= 0) { + return null; + } + + foreach ($translations as $languageId => $value) { + $safeLanguageId = $this->sanitizeLanguageId((string)$languageId); + if ($safeLanguageId === null) { + continue; + } + + $this->db->update('pp_langs_translations', [ + $safeLanguageId => (string)$value, + ], [ + 'id' => $translationId, + ]); + } + + \S::htacces(); + \S::delete_dir('../temp/'); + return $translationId; + } + + private function sanitizeLanguageId(string $languageId): ?string + { + $languageId = strtolower(trim($languageId)); + if (!preg_match('/^[a-z]{2}$/', $languageId)) { + return null; + } + + return $languageId; + } + + private function toSwitchValue($value): int + { + return ($value === 'on' || $value === 1 || $value === '1' || $value === true) ? 1 : 0; + } +} + diff --git a/autoload/admin/Controllers/LanguagesController.php b/autoload/admin/Controllers/LanguagesController.php new file mode 100644 index 0000000..2a13763 --- /dev/null +++ b/autoload/admin/Controllers/LanguagesController.php @@ -0,0 +1,560 @@ +repository = $repository; + $this->formHandler = new FormRequestHandler(); + } + + public function list(): string + { + $sortableColumns = ['o', 'name', 'status', 'start']; + + $filterDefinitions = [ + [ + 'key' => 'name', + 'label' => 'Jezyk', + 'type' => 'text', + ], + [ + 'key' => 'status', + 'label' => 'Aktywny', + 'type' => 'select', + 'options' => [ + '' => '- aktywny -', + '1' => 'tak', + '0' => 'nie', + ], + ], + [ + 'key' => 'start', + 'label' => 'Domyslny', + 'type' => 'select', + 'options' => [ + '' => '- domyslny -', + '1' => 'tak', + '0' => 'nie', + ], + ], + ]; + + $listRequest = \admin\Support\TableListRequestFactory::fromRequest( + $filterDefinitions, + $sortableColumns, + 'o' + ); + + $sortDir = $listRequest['sortDir']; + if (trim((string)\S::get('sort')) === '') { + $sortDir = 'ASC'; + } + + $result = $this->repository->listForAdmin( + $listRequest['filters'], + $listRequest['sortColumn'], + $sortDir, + $listRequest['page'], + $listRequest['perPage'] + ); + + $rows = []; + $lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1; + foreach ($result['items'] as $item) { + $id = (string)($item['id'] ?? ''); + $name = trim((string)($item['name'] ?? '')); + + $rows[] = [ + 'lp' => $lp++ . '.', + 'start' => ((int)($item['start'] ?? 0) === 1) ? 'tak' : 'nie', + 'status' => ((int)($item['status'] ?? 0) === 1) ? 'tak' : 'nie', + 'name' => '' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '', + '_actions' => [ + [ + 'label' => 'Edytuj', + 'url' => '/admin/languages/language_edit/id=' . $id, + 'class' => 'btn btn-xs btn-primary', + ], + [ + 'label' => 'Usun', + 'url' => '/admin/languages/language_delete/id=' . $id, + 'class' => 'btn btn-xs btn-danger', + 'confirm' => 'Na pewno chcesz usunac wybrany jezyk?', + ], + ], + ]; + } + + $total = (int)$result['total']; + $totalPages = max(1, (int)ceil($total / $listRequest['perPage'])); + + $viewModel = new \admin\ViewModels\Common\PaginatedTableViewModel( + [ + ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false], + ['key' => 'start', 'sort_key' => 'start', 'label' => 'Domyslny', 'class' => 'text-center', 'sortable' => true, 'raw' => true], + ['key' => 'status', 'sort_key' => 'status', 'label' => 'Aktywny', 'class' => 'text-center', 'sortable' => true, 'raw' => true], + ['key' => 'name', 'sort_key' => 'name', 'label' => 'Jezyk', '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/languages/view_list/', + 'Brak danych w tabeli.', + '/admin/languages/language_edit/', + 'Dodaj jezyk' + ); + + return \Tpl::view('languages/languages-list', [ + 'viewModel' => $viewModel, + ]); + } + + public function view_list(): string + { + return $this->list(); + } + + public function language_edit(): string + { + $languageId = trim((string)\S::get('id')); + $language = $this->repository->languageDetails($languageId) ?? []; + $validationErrors = $_SESSION['form_errors'][$this->getLanguageFormId()] ?? null; + if ($validationErrors) { + unset($_SESSION['form_errors'][$this->getLanguageFormId()]); + } + + return \Tpl::view('languages/language-edit', [ + 'form' => $this->buildLanguageFormViewModel($language, $this->repository->maxOrder(), $validationErrors), + ]); + } + + public function language_save(): void + { + $legacyValues = \S::get('values'); + if ($legacyValues) { + $values = json_decode((string)$legacyValues, true); + $response = ['status' => 'error', 'msg' => 'Podczas zapisywania jezyka wystapil blad.']; + + if (is_array($values)) { + $savedId = $this->repository->saveLanguage( + (string)($values['id'] ?? ''), + (string)($values['name'] ?? ''), + $values['status'] ?? 0, + $values['start'] ?? 0, + (int)($values['o'] ?? 0) + ); + if ($savedId) { + $response = ['status' => 'ok', 'msg' => 'Jezyk zostal zapisany.', 'id' => $savedId]; + } + } + + echo json_encode($response); + exit; + } + + $languageId = trim((string)\S::get('id')); + $language = $this->repository->languageDetails($languageId) ?? []; + $viewModel = $this->buildLanguageFormViewModel($language, $this->repository->maxOrder()); + + $result = $this->formHandler->handleSubmit($viewModel, $_POST); + if (!$result['success']) { + $_SESSION['form_errors'][$this->getLanguageFormId()] = $result['errors']; + echo json_encode(['success' => false, 'errors' => $result['errors']]); + exit; + } + + $data = $result['data']; + $requestId = strtolower(trim((string)\S::get('id'))); + $idFromData = strtolower(trim((string)($data['id'] ?? ''))); + $id = $idFromData !== '' ? $idFromData : $requestId; + if (!preg_match('/^[a-z]{2}$/', $id)) { + echo json_encode([ + 'success' => false, + 'errors' => ['id' => 'ID jezyka musi miec 2 litery (np. pl, en).'], + ]); + exit; + } + + $savedId = $this->repository->saveLanguage( + $id, + trim((string)($data['name'] ?? '')), + $data['status'] ?? 0, + $data['start'] ?? 0, + (int)($data['o'] ?? 0) + ); + + if ($savedId) { + echo json_encode([ + 'success' => true, + 'id' => $savedId, + 'message' => 'Jezyk zostal zapisany.', + ]); + exit; + } + + echo json_encode([ + 'success' => false, + 'errors' => ['general' => 'Podczas zapisywania jezyka wystapil blad.'], + ]); + exit; + } + + public function language_delete(): void + { + if ($this->repository->deleteLanguage((string)\S::get('id'))) { + \S::alert('Jezyk zostal usuniety.'); + } + + header('Location: /admin/languages/view_list/'); + exit; + } + + public function translation_list(): string + { + $sortableColumns = ['text', 'id']; + $filterDefinitions = [ + [ + 'key' => 'text', + 'label' => 'Tekst', + 'type' => 'text', + ], + ]; + + $listRequest = \admin\Support\TableListRequestFactory::fromRequest( + $filterDefinitions, + $sortableColumns, + 'text' + ); + + $sortDir = $listRequest['sortDir']; + if (trim((string)\S::get('sort')) === '') { + $sortDir = 'ASC'; + } + + $result = $this->repository->listTranslationsForAdmin( + $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); + $text = trim((string)($item['text'] ?? '')); + + $rows[] = [ + 'lp' => $lp++ . '.', + 'text' => '' . htmlspecialchars($text, ENT_QUOTES, 'UTF-8') . '', + '_actions' => [ + [ + 'label' => 'Edytuj', + 'url' => '/admin/languages/translation_edit/id=' . $id, + 'class' => 'btn btn-xs btn-primary', + ], + [ + 'label' => 'Usun', + 'url' => '/admin/languages/translation_delete/id=' . $id, + 'class' => 'btn btn-xs btn-danger', + 'confirm' => 'Na pewno chcesz usunac wybrane tlumaczenie?', + ], + ], + ]; + } + + $total = (int)$result['total']; + $totalPages = max(1, (int)ceil($total / $listRequest['perPage'])); + + $viewModel = new \admin\ViewModels\Common\PaginatedTableViewModel( + [ + ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false], + ['key' => 'text', 'sort_key' => 'text', 'label' => 'Tekst', '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/languages/translation_list/', + 'Brak danych w tabeli.', + '/admin/languages/translation_edit/', + 'Dodaj tlumaczenie' + ); + + return \Tpl::view('languages/translations-list', [ + 'viewModel' => $viewModel, + ]); + } + + public function translation_edit(): string + { + $translationId = (int)\S::get('id'); + $translation = $this->repository->translationDetails($translationId) ?? []; + $languages = $this->repository->languagesList(); + $validationErrors = $_SESSION['form_errors'][$this->getTranslationFormId()] ?? null; + if ($validationErrors) { + unset($_SESSION['form_errors'][$this->getTranslationFormId()]); + } + + return \Tpl::view('languages/translation-edit', [ + 'form' => $this->buildTranslationFormViewModel($translation, $languages, $validationErrors), + ]); + } + + public function translation_save(): void + { + $legacyValues = \S::get('values'); + if ($legacyValues) { + $values = json_decode((string)$legacyValues, true); + $response = ['status' => 'error', 'msg' => 'Podczas zapisywania tlumaczenia wystapil blad.']; + + if (is_array($values)) { + $languagesMap = $this->extractLegacyTranslations($values, $this->repository->languagesList()); + $savedId = $this->repository->saveTranslation( + (int)($values['id'] ?? 0), + (string)($values['text'] ?? ''), + $languagesMap + ); + if ($savedId) { + $this->clearLanguageSessions($this->repository->languagesList()); + $response = ['status' => 'ok', 'msg' => 'Tlumaczenie zostalo zapisane.', 'id' => $savedId]; + } + } + + echo json_encode($response); + exit; + } + + $translationId = (int)\S::get('id'); + $translation = $this->repository->translationDetails($translationId) ?? []; + $languages = $this->repository->languagesList(); + $viewModel = $this->buildTranslationFormViewModel($translation, $languages); + + $result = $this->formHandler->handleSubmit($viewModel, $_POST); + if (!$result['success']) { + $_SESSION['form_errors'][$this->getTranslationFormId()] = $result['errors']; + echo json_encode(['success' => false, 'errors' => $result['errors']]); + exit; + } + + $data = $result['data']; + $languagesMap = []; + foreach ($languages as $language) { + $langId = (string)($language['id'] ?? ''); + $key = 'lang_' . $langId; + $languagesMap[$langId] = (string)($data[$key] ?? ''); + } + + $savedId = $this->repository->saveTranslation( + $translationId, + (string)($data['text'] ?? ''), + $languagesMap + ); + + if ($savedId) { + $this->clearLanguageSessions($languages); + echo json_encode([ + 'success' => true, + 'id' => $savedId, + 'message' => 'Tlumaczenie zostalo zapisane.', + ]); + exit; + } + + echo json_encode([ + 'success' => false, + 'errors' => ['general' => 'Podczas zapisywania tlumaczenia wystapil blad.'], + ]); + exit; + } + + public function translation_delete(): void + { + if ($this->repository->deleteTranslation((int)\S::get('id'))) { + \S::alert('Tlumaczenie zostalo usuniete.'); + } + + header('Location: /admin/languages/translation_list/'); + exit; + } + + private function buildLanguageFormViewModel(array $language, int $maxOrder, ?array $errors = null): FormEditViewModel + { + $languageId = strtolower(trim((string)($language['id'] ?? ''))); + $isNew = $languageId === ''; + + $data = [ + 'id' => $languageId, + 'name' => (string)($language['name'] ?? ''), + 'status' => (int)($language['status'] ?? 0), + 'start' => (int)($language['start'] ?? 0), + 'o' => (int)($language['o'] ?? ($maxOrder + 1)), + ]; + + $fields = []; + if ($isNew) { + $fields[] = FormField::text('id', [ + 'label' => 'ID (2 znaki)', + 'required' => true, + 'attributes' => ['maxlength' => 2], + ]); + } + + $fields[] = FormField::text('name', [ + 'label' => 'Jezyk', + 'required' => true, + ]); + $fields[] = FormField::switch('status', [ + 'label' => 'Aktywny', + ]); + $fields[] = FormField::switch('start', [ + 'label' => 'Domyslny', + ]); + $fields[] = FormField::hidden('o', $data['o']); + + $actionUrl = '/admin/languages/language_save/' . ($isNew ? '' : ('id=' . $languageId)); + $actions = [ + FormAction::save($actionUrl, '/admin/languages/view_list/'), + FormAction::cancel('/admin/languages/view_list/'), + ]; + + return new FormEditViewModel( + $this->getLanguageFormId(), + $isNew ? 'Nowy jezyk' : 'Edycja jezyka', + $data, + $fields, + [], + $actions, + 'POST', + $actionUrl, + '/admin/languages/view_list/', + true, + $isNew ? [] : ['id' => $languageId], + null, + $errors + ); + } + + private function buildTranslationFormViewModel(array $translation, array $languages, ?array $errors = null): FormEditViewModel + { + $translationId = (int)($translation['id'] ?? 0); + $isNew = $translationId <= 0; + + $data = [ + 'id' => $translationId, + 'text' => (string)($translation['text'] ?? ''), + ]; + + $fields = [ + FormField::text('text', [ + 'label' => 'Tekst', + 'required' => true, + ]), + ]; + + foreach ($languages as $language) { + $langId = (string)($language['id'] ?? ''); + $fieldName = 'lang_' . $langId; + $data[$fieldName] = (string)($translation[$langId] ?? ''); + $fields[] = FormField::text($fieldName, [ + 'label' => (string)($language['name'] ?? $langId), + ]); + } + + $actionUrl = '/admin/languages/translation_save/' . ($isNew ? '' : ('id=' . $translationId)); + $actions = [ + FormAction::save($actionUrl, '/admin/languages/translation_list/'), + FormAction::cancel('/admin/languages/translation_list/'), + ]; + + return new FormEditViewModel( + $this->getTranslationFormId(), + $isNew ? 'Nowe tlumaczenie' : 'Edycja tlumaczenia', + $data, + $fields, + [], + $actions, + 'POST', + $actionUrl, + '/admin/languages/translation_list/', + true, + $isNew ? [] : ['id' => $translationId], + null, + $errors + ); + } + + private function extractLegacyTranslations(array $values, array $languages): array + { + $result = []; + foreach ($languages as $language) { + $langId = (string)($language['id'] ?? ''); + $result[$langId] = (string)($values[$langId] ?? ''); + } + + return $result; + } + + private function clearLanguageSessions(array $languages): void + { + foreach ($languages as $language) { + if (!isset($language['id'])) { + continue; + } + \S::delete_session('lang-' . (string)$language['id']); + } + } + + private function getLanguageFormId(): string + { + return 'languages-language-edit'; + } + + private function getTranslationFormId(): string + { + return 'languages-translation-edit'; + } +} diff --git a/autoload/admin/class.Site.php b/autoload/admin/class.Site.php index 30daac6..015134a 100644 --- a/autoload/admin/class.Site.php +++ b/autoload/admin/class.Site.php @@ -255,6 +255,13 @@ class Site new \Domain\User\UserRepository( $mdb ) ); }, + 'Languages' => function() { + global $mdb; + + return new \admin\Controllers\LanguagesController( + new \Domain\Languages\LanguagesRepository( $mdb ) + ); + }, ]; return self::$newControllers; diff --git a/autoload/admin/controls/class.Languages.php b/autoload/admin/controls/class.Languages.php deleted file mode 100644 index e282212..0000000 --- a/autoload/admin/controls/class.Languages.php +++ /dev/null @@ -1,82 +0,0 @@ - 'error', 'msg' => 'Podczas zapisywania jÄ™zyka wystÄ…piÅ‚ błąd. ProszÄ™ spróbować ponownie.' ]; - $values = json_decode( \S::get( 'values' ), true ); - - if ( \admin\factory\Languages::language_save( - $values['id'], $values['name'], $values['status'], - $values['start'], $values['o'] ) ) - $response = [ 'status' => 'ok', 'msg' => 'JÄ™zyk zostaÅ‚ zapisany.', 'id' => $id ]; - - echo json_encode( $response ); - exit; - } - - public static function language_edit() - { - return \admin\view\Languages::language_edit( - \admin\factory\Languages::language_details( - \S::get( 'id' ) - ), \admin\factory\Languages::max_order() - ); - } - - public static function view_list() - { - return \admin\view\Languages::languages_list(); - } - - public static function translation_delete() - { - if ( \admin\factory\Languages::translation_delete( \S::get( 'id' ) ) ) - \S::alert( 'TÅ‚umaczenie zostaÅ‚o usuniÄ™te.' ); - header( 'Location: /admin/languages/translation_list/' ); - exit; - } - - public static function translation_save() - { - $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania tÅ‚umaczenia wystÄ…piÅ‚ błąd. ProszÄ™ spróbować ponownie.' ]; - $values = json_decode( \S::get( 'values' ), true ); - - $languages_list = \admin\factory\Languages::languages_list(); - if ( is_array( $languages_list ) and !empty( $languages_list ) ) foreach ( $languages_list as $language ) - { - \S::delete_session( 'lang-' . $language['id'] ); - $languages[ $language['id'] ] = $values[ $language['id'] ]; - } - - if ( $id = \admin\factory\Languages::translation_save( $values['id'], $values['text'], $languages ) ) - $response = [ 'status' => 'ok', 'msg' => 'TÅ‚umaczenie zostaÅ‚o zapisane.', 'id' => $id ]; - - echo json_encode( $response ); - exit; - } - - public static function translation_edit() - { - return \admin\view\Languages::translation_edit( - \admin\factory\Languages::translation_details( \S::get( 'id' ) ), - \admin\factory\Languages::languages_list() - ); - } - - public static function translation_list() - { - return \admin\view\Languages::translations_list(); - } -} -?> \ No newline at end of file diff --git a/autoload/admin/factory/class.Languages.php b/autoload/admin/factory/class.Languages.php index 97c8006..f77d6bf 100644 --- a/autoload/admin/factory/class.Languages.php +++ b/autoload/admin/factory/class.Languages.php @@ -1,141 +1,53 @@ delete( 'pp_langs_translations', [ 'id' => $translation_id ] ); + return new \Domain\Languages\LanguagesRepository($mdb); } - public static function translation_save( $translation_id, $text, $languages ) + public static function translation_delete($translation_id) { - global $mdb; - - if ( $translation_id ) - { - $mdb -> update( 'pp_langs_translations', [ 'text' => $text ], [ 'id' => $translation_id ] ); - if ( is_array( $languages ) and !empty( $languages ) ): foreach ( $languages as $key => $val ): - $mdb -> update( 'pp_langs_translations', [ $key => $val ], [ 'id' => $translation_id ] ); - endforeach; endif; - \S::htacces(); - \S::delete_dir( '../temp/' ); - return $translation_id; - } - else - { - $mdb -> insert( 'pp_langs_translations', [ 'text' => $text ] ); - if ( $translation_id = $mdb -> id() ) - { - if ( is_array( $languages ) and !empty( $languages ) ): foreach ( $languages as $key => $val ): - $mdb -> update( 'pp_langs_translations', [ $key => $val ], [ 'id' => $translation_id ] ); - endforeach; endif; - } - \S::htacces(); - \S::delete_dir( '../temp/' ); - return $translation_id; - } + return self::repository()->deleteTranslation((int)$translation_id); } - public static function translation_details( $translation_id ) + public static function translation_save($translation_id, $text, $languages) { - global $mdb; - return $mdb -> get( 'pp_langs_translations', '*', [ 'id' => $translation_id ] ); + return self::repository()->saveTranslation((int)$translation_id, (string)$text, is_array($languages) ? $languages : []); } - public static function language_delete( $language_id ) + public static function translation_details($translation_id) { - global $mdb; + return self::repository()->translationDetails((int)$translation_id); + } - if ( $mdb -> count( 'pp_langs' ) > 1 ) - { - if ( $mdb -> query( 'ALTER TABLE pp_langs_translations DROP ' . $language_id ) - and - $mdb -> delete( 'pp_langs', [ 'id' => $language_id ] ) - ) - return true; - } - return false; + public static function language_delete($language_id) + { + return self::repository()->deleteLanguage((string)$language_id); } public static function max_order() { - global $mdb; - return $mdb -> max( 'pp_langs', 'o' ); + return self::repository()->maxOrder(); } - public static function language_save( $language_id, $name, $status, $start, $o ) + public static function language_save($language_id, $name, $status, $start, $o) { - global $mdb; - - if ( $start == 'on' ) - $mdb -> update( 'pp_langs', [ - 'start' => 0 - ], [ - 'id[!]' => $language_id - ] ); - - if ( $mdb -> count( 'pp_langs', [ 'id' => $language_id ] ) ) - { - $mdb -> update( 'pp_langs', - [ - 'status' => $status == 'on' ? 1 : 0, - 'start' => $start == 'on' ? 1 : 0, - 'name' => $name, - 'o' => $o - ], [ - 'id' => $language_id - ] ); - - if ( !$mdb -> count( 'pp_langs', [ 'start' => 1 ] ) ) - { - $id_tmp = $mdb -> get( 'pp_langs', 'id', [ 'ORDER' => [ 'o' => 'ASC' ] ] ); - $mdb -> update( 'pp_langs', [ 'start' => 1 ], [ 'id' => $id_tmp ] ); - } - - \S::htacces(); - \S::delete_dir( '../temp/' ); - return $language_id; - } - else - { - if ( $mdb -> query( 'ALTER TABLE pp_langs_translations ADD ' . $language_id . ' TEXT NULL DEFAULT NULL' ) ) - { - $mdb -> insert( 'pp_langs', - [ - 'id' => $language_id, - 'name' => $name, - 'status' => $status == 'on' ? 1 : 0, - 'start' => $start == 'on' ? 1 : 0, - 'o' => $o - ] ); - - \S::htacces(); - \S::delete_dir( '../temp/' ); - return $language_id; - } - } - - return faslse; + return self::repository()->saveLanguage((string)$language_id, (string)$name, $status, $start, (int)$o); } - public static function language_details( $language_id ) + public static function language_details($language_id) { - global $mdb; - return $mdb -> get( 'pp_langs', '*', [ 'id' => $language_id ] ); + return self::repository()->languageDetails((string)$language_id); } - public static function languages_list( $only_active = false ) + public static function languages_list($only_active = false) { - global $mdb; - - $where = []; - if ( $only_active ) - $where['status'] = 1; - - return $mdb -> select( 'pp_langs', '*', array_merge( [ 'ORDER' => [ 'o' => 'ASC' ] ], $where ) ); + return self::repository()->languagesList((bool)$only_active); } } -?> \ No newline at end of file +?> + diff --git a/autoload/admin/view/class.Languages.php b/autoload/admin/view/class.Languages.php deleted file mode 100644 index de6453d..0000000 --- a/autoload/admin/view/class.Languages.php +++ /dev/null @@ -1,34 +0,0 @@ - languages = $languages; - $tpl -> translation = $translation; - return $tpl -> render( 'languages/translation-edit' ); - } - - public static function language_edit( $language, $order ) - { - $tpl = new \Tpl; - $tpl -> language = $language; - $tpl -> order = $order; - return $tpl -> render( 'languages/language-edit' ); - } - - public static function translations_list() - { - $tpl = new \Tpl; - return $tpl -> render( 'languages/translations-list' ); - } - - public static function languages_list() - { - $tpl = new \Tpl; - return $tpl -> render( 'languages/languages-list' ); - } -} -?> \ No newline at end of file diff --git a/tests/Unit/Domain/Languages/LanguagesRepositoryTest.php b/tests/Unit/Domain/Languages/LanguagesRepositoryTest.php new file mode 100644 index 0000000..36fa2bf --- /dev/null +++ b/tests/Unit/Domain/Languages/LanguagesRepositoryTest.php @@ -0,0 +1,116 @@ +createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_langs', '*', ['id' => 'pl']) + ->willReturn(['id' => 'pl', 'name' => 'Polski']); + + $repository = new LanguagesRepository($mockDb); + $language = $repository->languageDetails('pl'); + + $this->assertIsArray($language); + $this->assertSame('pl', $language['id']); + } + + public function testLanguagesListReturnsArray(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('select') + ->with('pp_langs', '*', ['ORDER' => ['o' => 'ASC']]) + ->willReturn([ + ['id' => 'pl', 'name' => 'Polski', 'status' => 1], + ]); + + $repository = new LanguagesRepository($mockDb); + $list = $repository->languagesList(false); + + $this->assertCount(1, $list); + $this->assertSame('pl', $list[0]['id']); + } + + public function testSaveLanguageRejectsInvalidLanguageId(): void + { + $mockDb = $this->createMock(\medoo::class); + $repository = new LanguagesRepository($mockDb); + + $this->assertNull($repository->saveLanguage('pol', 'Polski', 1, 1, 1)); + $this->assertNull($repository->saveLanguage('p1', 'Polski', 1, 1, 1)); + } + + public function testSaveTranslationInsertsNewTranslationAndReturnsId(): void + { + $mockDb = $this->createMock(\medoo::class); + + $mockDb->expects($this->once()) + ->method('insert') + ->with('pp_langs_translations', ['text' => 'Hello']) + ->willReturn($this->createMock(\PDOStatement::class)); + + $mockDb->expects($this->once()) + ->method('id') + ->willReturn(15); + + $mockDb->expects($this->exactly(2)) + ->method('update') + ->withConsecutive( + ['pp_langs_translations', ['pl' => 'Czesc'], ['id' => 15]], + ['pp_langs_translations', ['en' => 'Hello'], ['id' => 15]] + ); + + $repository = new LanguagesRepository($mockDb); + $savedId = $repository->saveTranslation(0, 'Hello', ['pl' => 'Czesc', 'en' => 'Hello']); + + $this->assertSame(15, $savedId); + } + + public function testDeleteTranslationReturnsBoolean(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('delete') + ->with('pp_langs_translations', ['id' => 5]) + ->willReturn($this->createMock(\PDOStatement::class)); + + $repository = new LanguagesRepository($mockDb); + $this->assertTrue($repository->deleteTranslation(5)); + } + + public function testListForAdminReturnsItemsAndTotal(): void + { + $mockDb = $this->createMock(\medoo::class); + + $countStmt = $this->createMock(\PDOStatement::class); + $countStmt->expects($this->once()) + ->method('fetchAll') + ->willReturn([[1]]); + + $dataStmt = $this->createMock(\PDOStatement::class); + $dataStmt->expects($this->once()) + ->method('fetchAll') + ->willReturn([ + ['id' => 'pl', 'name' => 'Polski', 'status' => 1, 'start' => 1, 'o' => 1], + ]); + + $mockDb->expects($this->exactly(2)) + ->method('query') + ->willReturnOnConsecutiveCalls($countStmt, $dataStmt); + + $repository = new LanguagesRepository($mockDb); + $result = $repository->listForAdmin(['name' => '', 'status' => '', 'start' => ''], 'o', 'ASC', 1, 15); + + $this->assertSame(1, $result['total']); + $this->assertCount(1, $result['items']); + $this->assertSame('pl', $result['items'][0]['id']); + } +} + diff --git a/tests/Unit/admin/Controllers/LanguagesControllerTest.php b/tests/Unit/admin/Controllers/LanguagesControllerTest.php new file mode 100644 index 0000000..6a87661 --- /dev/null +++ b/tests/Unit/admin/Controllers/LanguagesControllerTest.php @@ -0,0 +1,63 @@ +mockRepository = $this->createMock(LanguagesRepository::class); + $this->controller = new LanguagesController($this->mockRepository); + } + + public function testConstructorAcceptsRepository(): void + { + $controller = new LanguagesController($this->mockRepository); + $this->assertInstanceOf(LanguagesController::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, 'language_edit')); + $this->assertTrue(method_exists($this->controller, 'language_save')); + $this->assertTrue(method_exists($this->controller, 'language_delete')); + $this->assertTrue(method_exists($this->controller, 'translation_list')); + $this->assertTrue(method_exists($this->controller, 'translation_edit')); + $this->assertTrue(method_exists($this->controller, 'translation_save')); + $this->assertTrue(method_exists($this->controller, 'translation_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('string', (string)$reflection->getMethod('language_edit')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('language_save')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('language_delete')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('translation_list')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('translation_edit')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('translation_save')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('translation_delete')->getReturnType()); + } + + public function testConstructorRequiresLanguagesRepository(): void + { + $reflection = new \ReflectionClass(LanguagesController::class); + $constructor = $reflection->getConstructor(); + $params = $constructor->getParameters(); + + $this->assertCount(1, $params); + $this->assertEquals('Domain\Languages\LanguagesRepository', $params[0]->getType()->getName()); + } +} + diff --git a/updates/0.20/ver_0.254.zip b/updates/0.20/ver_0.254.zip new file mode 100644 index 0000000..947fe90 Binary files /dev/null and b/updates/0.20/ver_0.254.zip differ diff --git a/updates/0.20/ver_0.254_files.txt b/updates/0.20/ver_0.254_files.txt new file mode 100644 index 0000000..6b81089 --- /dev/null +++ b/updates/0.20/ver_0.254_files.txt @@ -0,0 +1,2 @@ +F: ../autoload/admin/controls/class.Languages.php +F: ../autoload/admin/view/class.Languages.php diff --git a/updates/changelog.php b/updates/changelog.php index e591143..3f34830 100644 --- a/updates/changelog.php +++ b/updates/changelog.php @@ -1,3 +1,11 @@ +ver. 0.254 - 12.02.2026
+- UPDATE - modul `Languages` w panelu admin przepiety na `Domain\\Languages\\LanguagesRepository` + `admin\\Controllers\\LanguagesController` +- 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) +- CLEANUP - usuniete legacy klasy: `autoload/admin/controls/class.Languages.php`, `autoload/admin/view/class.Languages.php` +
ver. 0.253 - 12.02.2026
- UPDATE - modul `Users` w panelu admin w pelni przepiety na `Domain\\User\\UserRepository` + `admin\\Controllers\\UsersController` - UPDATE - migracja widokow users z `grid/gridEdit` na nowe komponenty (`components/table-list`, `components/form-edit`) diff --git a/updates/shopPRO.zip b/updates/shopPRO.zip index 1b2a62d..947fe90 100644 Binary files a/updates/shopPRO.zip and b/updates/shopPRO.zip differ diff --git a/updates/versions.php b/updates/versions.php index 72ca0f4..8f173d6 100644 --- a/updates/versions.php +++ b/updates/versions.php @@ -1,5 +1,5 @@