diff --git a/DATABASE_STRUCTURE.md b/DATABASE_STRUCTURE.md index 99353fe..323bfc7 100644 --- a/DATABASE_STRUCTURE.md +++ b/DATABASE_STRUCTURE.md @@ -209,3 +209,43 @@ Slownik tlumaczen panelu/frontendu. **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`. + +## pp_layouts +Szablony layoutow (HTML/CSS/JS + flagi domyslne). + +| Kolumna | Opis | +|---------|------| +| id | PK | +| name | Nazwa szablonu | +| html | Kod HTML | +| css | Kod CSS | +| js | Kod JS | +| m_html | Kod HTML mobilny | +| m_css | Kod CSS mobilny | +| m_js | Kod JS mobilny | +| status | Domyslny layout stron (0/1) | +| categories_default | Domyslny layout kategorii (0/1) | + +**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `admin\\Controllers\\LayoutsController`, `front\\factory\\Layouts` + +## pp_layouts_pages +Przypisanie layoutow do stron CMS. + +| Kolumna | Opis | +|---------|------| +| layout_id | FK do pp_layouts | +| page_id | FK do pp_pages | + +**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `front\\factory\\Layouts` + +## pp_layouts_categories +Przypisanie layoutow do kategorii sklepu. + +| Kolumna | Opis | +|---------|------| +| layout_id | FK do pp_layouts | +| category_id | FK do pp_shop_categories | + +**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). diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 75d5034..8a54078 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -408,4 +408,14 @@ Aktualnie w suite są też testy modułów `Dictionaries`, `Articles` i `Users` - UPDATE: w admin/Site fabryki DI dla Articles, Banners, Settings, Dictionaries przekazuja rowniez LanguagesRepository. - UPDATE: legacy admin/controls/* oraz admin/factory/Shop* przepiete z admin/factory/Languages::languages_list() na bezposrednie wywolania LanguagesRepository. - FIX: autoload/admin/factory/class.Languages.php uzywa pelnego znacznika Menu: - layout['pages'] );?> + $menu['pages'], + 'layout_pages' => $this -> layout['pages'], + 'step' => 1 + ] );?> @@ -202,8 +206,8 @@ ob_start(); layout['categories'] ) and in_array( $category['id'], $this -> layout['categories'] ) ):?>checked="checked" /> dlang]['title'];?> - \admin\factory\ShopCategory::subcategories( $category['id'] ), + $category['subcategories'], 'product_categories' => $this -> layout['categories'], 'dlang' => $this -> dlang ] );?> @@ -250,4 +254,4 @@ $grid -> persist_edit = true; $grid -> id_param = 'id'; echo $grid -> draw(); -?> \ No newline at end of file +?> diff --git a/admin/templates/layouts/layouts-list.php b/admin/templates/layouts/layouts-list.php index 2e7a218..0f89c5b 100644 --- a/admin/templates/layouts/layouts-list.php +++ b/admin/templates/layouts/layouts-list.php @@ -1,54 +1 @@ - gdb_opt = $gdb; -$grid -> order = [ 'column' => 'name', 'type' => 'ASC' ]; -$grid -> search = [ - [ 'name' => 'Nazwa', 'db' => 'name', 'type' => 'text' ], - [ 'name' => 'Szablon domyślny', 'db' => 'status', 'type' => 'select', 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ] ] - ]; -$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' => 'Szablon domyślny', - 'db' => 'status', - 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ], - 'td' => [ 'class' => 'g-center' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ] - ], - [ - 'name' => 'Szablon domyślny (kategorie)', - 'db' => 'categories_default', - 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ], - 'td' => [ 'class' => 'g-center' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ] - ], - [ - 'name' => 'Akcja', - 'action' => [ 'type' => 'edit', 'url' => '/admin/layouts/layout_edit/id=[id]' ], - 'th' => [ 'class' => 'g-center' ], - 'td' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ] - ], - [ - 'name' => 'Akcja', - 'action' => [ 'type' => 'delete', 'url' => '/admin/layouts/layout_delete/id=[id]' ], - 'th' => [ 'class' => 'g-center' ], - 'td' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ] - ] - ]; -$grid -> buttons = [ - [ 'label' => 'Dodaj szablon', 'url' => '/admin/layouts/layout_edit/', 'icon' => 'fa-plus-circle', 'class' => 'btn-success' ] - ]; -echo $grid -> draw(); \ No newline at end of file + $this->viewModel]); ?> diff --git a/admin/templates/layouts/subcategories-list.php b/admin/templates/layouts/subcategories-list.php new file mode 100644 index 0000000..e1e5bf5 --- /dev/null +++ b/admin/templates/layouts/subcategories-list.php @@ -0,0 +1,20 @@ + categories ) ):?> +
    + categories as $category ):?> +
  1. +
    + + ';?> + product_categories ) and in_array( $category['id'], $this -> product_categories ) ):?>checked="checked" /> + dlang]['title'];?> +
    + $category['subcategories'], + 'product_categories' => $this -> product_categories, + 'dlang' => $this -> dlang + ] );?> +
  2. + +
+ + diff --git a/admin/templates/layouts/subpages-list.php b/admin/templates/layouts/subpages-list.php index d46b0a9..3f68d21 100644 --- a/admin/templates/layouts/subpages-list.php +++ b/admin/templates/layouts/subpages-list.php @@ -8,9 +8,13 @@ layout_pages, $page['id'], $this -> step + 1 ); + echo \Tpl::view( 'layouts/subpages-list', [ + 'pages' => $page['subpages'], + 'layout_pages' => $this -> layout_pages, + 'step' => $this -> step + 1 + ] ); ?> - \ No newline at end of file + diff --git a/autoload/Domain/Languages/LanguagesRepository.php b/autoload/Domain/Languages/LanguagesRepository.php index 7957acc..cce748d 100644 --- a/autoload/Domain/Languages/LanguagesRepository.php +++ b/autoload/Domain/Languages/LanguagesRepository.php @@ -187,6 +187,26 @@ class LanguagesRepository return is_array($rows) ? $rows : []; } + public function defaultLanguageId(): string + { + $languages = $this->languagesList(); + if (empty($languages)) { + return 'pl'; + } + + foreach ($languages as $language) { + if ((int)($language['start'] ?? 0) === 1 && !empty($language['id'])) { + return (string)$language['id']; + } + } + + if (!empty($languages[0]['id'])) { + return (string)$languages[0]['id']; + } + + return 'pl'; + } + public function deleteLanguage(string $languageId): bool { $languageId = $this->sanitizeLanguageId($languageId); @@ -327,4 +347,3 @@ class LanguagesRepository return ($value === 'on' || $value === 1 || $value === '1' || $value === true) ? 1 : 0; } } - diff --git a/autoload/Domain/Layouts/LayoutsRepository.php b/autoload/Domain/Layouts/LayoutsRepository.php new file mode 100644 index 0000000..7cf6be6 --- /dev/null +++ b/autoload/Domain/Layouts/LayoutsRepository.php @@ -0,0 +1,343 @@ +db = $db; + } + + public function delete(int $layoutId): bool + { + if ((int)$this->db->count('pp_layouts') <= 1) { + return false; + } + + return (bool)$this->db->delete('pp_layouts', ['id' => $layoutId]); + } + + public function find(int $layoutId): array + { + $layout = $this->db->get('pp_layouts', '*', ['id' => $layoutId]); + if (!is_array($layout)) { + return $this->defaultLayout(); + } + + $layout['pages'] = $this->db->select('pp_layouts_pages', 'page_id', ['layout_id' => $layoutId]); + $layout['categories'] = $this->db->select('pp_layouts_categories', 'category_id', ['layout_id' => $layoutId]); + + return $layout; + } + + public function save(array $data): ?int + { + $layoutId = (int)($data['id'] ?? 0); + $status = $this->toSwitchValue($data['status'] ?? 0); + $categoriesDefault = $this->toSwitchValue($data['categories_default'] ?? 0); + + $row = [ + 'name' => (string)($data['name'] ?? ''), + 'html' => (string)($data['html'] ?? ''), + 'css' => (string)($data['css'] ?? ''), + 'js' => (string)($data['js'] ?? ''), + 'm_html' => (string)($data['m_html'] ?? ''), + 'm_css' => (string)($data['m_css'] ?? ''), + 'm_js' => (string)($data['m_js'] ?? ''), + 'status' => $status, + 'categories_default' => $categoriesDefault, + ]; + + if ($status === 1) { + $this->db->update('pp_layouts', ['status' => 0]); + } + + if ($categoriesDefault === 1) { + $this->db->update('pp_layouts', ['categories_default' => 0]); + } + + if ($layoutId <= 0) { + $this->db->insert('pp_layouts', $row); + $layoutId = (int)$this->db->id(); + if ($layoutId <= 0) { + return null; + } + } else { + $this->db->update('pp_layouts', $row, ['id' => $layoutId]); + } + + $this->db->delete('pp_layouts_pages', ['layout_id' => $layoutId]); + $this->syncPages($layoutId, $data['pages'] ?? []); + + $this->db->delete('pp_layouts_categories', ['layout_id' => $layoutId]); + $this->syncCategories($layoutId, $data['categories'] ?? []); + + \S::delete_dir('../temp/'); + + return $layoutId; + } + + public function listAll(): array + { + $rows = $this->db->select('pp_layouts', '*', ['ORDER' => ['name' => 'ASC']]); + return is_array($rows) ? $rows : []; + } + + public function menusWithPages(): array + { + $menus = $this->db->select('pp_menus', '*', ['ORDER' => ['id' => 'ASC']]); + if (!is_array($menus)) { + return []; + } + + foreach ($menus as $key => $menu) { + $menuId = (int)($menu['id'] ?? 0); + $menus[$key]['pages'] = $this->menuPages($menuId, null); + } + + return $menus; + } + + public function categoriesTree($parentId = null): array + { + $rows = $this->db->select('pp_shop_categories', ['id'], [ + 'parent_id' => $parentId, + 'ORDER' => ['o' => 'ASC'], + ]); + + if (!is_array($rows)) { + return []; + } + + $categories = []; + foreach ($rows as $row) { + $categoryId = (int)($row['id'] ?? 0); + if ($categoryId <= 0) { + continue; + } + + $category = $this->db->get('pp_shop_categories', '*', ['id' => $categoryId]); + if (!is_array($category)) { + continue; + } + + $translations = $this->db->select('pp_shop_categories_langs', '*', ['category_id' => $categoryId]); + $category['languages'] = []; + if (is_array($translations)) { + foreach ($translations as $translation) { + $langId = (string)($translation['lang_id'] ?? ''); + if ($langId !== '') { + $category['languages'][$langId] = $translation; + } + } + } + + $category['subcategories'] = $this->categoriesTree($categoryId); + $categories[] = $category; + } + + return $categories; + } + + /** + * @return array{items: array>, total: int} + */ + public function listForAdmin( + array $filters, + string $sortColumn = 'name', + string $sortDir = 'ASC', + int $page = 1, + int $perPage = 15 + ): array { + $allowedSortColumns = [ + 'id' => 'pl.id', + 'name' => 'pl.name', + 'status' => 'pl.status', + 'categories_default' => 'pl.categories_default', + ]; + + $sortSql = $allowedSortColumns[$sortColumn] ?? 'pl.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 = ['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; + } + + $categoriesDefault = trim((string)($filters['categories_default'] ?? '')); + if ($categoriesDefault === '0' || $categoriesDefault === '1') { + $where[] = 'pl.categories_default = :categories_default'; + $params[':categories_default'] = (int)$categoriesDefault; + } + + $whereSql = implode(' AND ', $where); + + $sqlCount = " + SELECT COUNT(0) + FROM pp_layouts 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.categories_default + FROM pp_layouts AS pl + WHERE {$whereSql} + ORDER BY {$sortSql} {$sortDir}, 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, + ]; + } + + private function syncPages(int $layoutId, $pages): void + { + foreach ($this->normalizeIds($pages) as $pageId) { + $this->db->delete('pp_layouts_pages', ['page_id' => $pageId]); + $this->db->insert('pp_layouts_pages', [ + 'layout_id' => $layoutId, + 'page_id' => $pageId, + ]); + } + } + + private function syncCategories(int $layoutId, $categories): void + { + foreach ($this->normalizeIds($categories) as $categoryId) { + $this->db->delete('pp_layouts_categories', ['category_id' => $categoryId]); + $this->db->insert('pp_layouts_categories', [ + 'layout_id' => $layoutId, + 'category_id' => $categoryId, + ]); + } + } + + /** + * @return int[] + */ + private function normalizeIds($values): array + { + if (!is_array($values)) { + $values = [$values]; + } + + $ids = []; + foreach ($values as $value) { + $id = (int)$value; + if ($id > 0) { + $ids[$id] = $id; + } + } + + return array_values($ids); + } + + private function toSwitchValue($value): int + { + return ($value === 'on' || $value === 1 || $value === '1' || $value === true) ? 1 : 0; + } + + private function defaultLayout(): array + { + return [ + 'id' => 0, + 'name' => '', + 'status' => 0, + 'categories_default' => 0, + 'html' => '', + 'css' => '', + 'js' => '', + 'm_html' => '', + 'm_css' => '', + 'm_js' => '', + 'pages' => [], + 'categories' => [], + ]; + } + + private function menuPages(int $menuId, $parentId = null): array + { + if ($menuId <= 0) { + return []; + } + + $rows = $this->db->select('pp_pages', ['id', 'menu_id', 'status', 'parent_id', 'start'], [ + 'AND' => [ + 'menu_id' => $menuId, + 'parent_id' => $parentId, + ], + 'ORDER' => ['o' => 'ASC'], + ]); + + if (!is_array($rows)) { + return []; + } + + $pages = []; + foreach ($rows as $row) { + $pageId = (int)($row['id'] ?? 0); + if ($pageId <= 0) { + continue; + } + + $row['title'] = $this->pageTitle($pageId); + $row['subpages'] = $this->menuPages($menuId, $pageId); + $pages[] = $row; + } + + return $pages; + } + + private function pageTitle(int $pageId): string + { + $result = $this->db->select('pp_pages_langs', [ + '[><]pp_langs' => ['lang_id' => 'id'], + ], 'title', [ + 'AND' => [ + 'page_id' => $pageId, + 'title[!]' => '', + ], + 'ORDER' => ['o' => 'ASC'], + 'LIMIT' => 1, + ]); + + if (is_array($result) && isset($result[0])) { + return (string)$result[0]; + } + + return ''; + } +} diff --git a/autoload/admin/Controllers/ArticlesController.php b/autoload/admin/Controllers/ArticlesController.php index 807880e..9766a01 100644 --- a/autoload/admin/Controllers/ArticlesController.php +++ b/autoload/admin/Controllers/ArticlesController.php @@ -3,16 +3,23 @@ namespace admin\Controllers; use Domain\Article\ArticleRepository; use Domain\Languages\LanguagesRepository; +use Domain\Layouts\LayoutsRepository; class ArticlesController { private ArticleRepository $repository; private LanguagesRepository $languagesRepository; + private LayoutsRepository $layoutsRepository; - public function __construct(ArticleRepository $repository, LanguagesRepository $languagesRepository) + public function __construct( + ArticleRepository $repository, + LanguagesRepository $languagesRepository, + LayoutsRepository $layoutsRepository + ) { $this->repository = $repository; $this->languagesRepository = $languagesRepository; + $this->layoutsRepository = $layoutsRepository; } /** @@ -189,7 +196,7 @@ class ArticlesController 'article' => $this->repository->find((int)\S::get('id')), 'menus' => \admin\factory\Pages::menus_list(), 'languages' => $this->languagesRepository->languagesList(), - 'layouts' => \admin\factory\Layouts::layouts_list(), + 'layouts' => $this->layoutsRepository->listAll(), 'user' => $user ]); } diff --git a/autoload/admin/Controllers/LayoutsController.php b/autoload/admin/Controllers/LayoutsController.php new file mode 100644 index 0000000..f02c6ac --- /dev/null +++ b/autoload/admin/Controllers/LayoutsController.php @@ -0,0 +1,172 @@ +repository = $repository; + $this->languagesRepository = $languagesRepository; + } + + public function list(): string + { + $sortableColumns = ['name', 'status', 'categories_default']; + + $filterDefinitions = [ + [ + 'key' => 'name', + 'label' => 'Nazwa', + 'type' => 'text', + ], + [ + 'key' => 'status', + 'label' => 'Szablon domyslny', + 'type' => 'select', + 'options' => [ + '' => '- domyslny -', + '1' => 'tak', + '0' => 'nie', + ], + ], + [ + 'key' => 'categories_default', + 'label' => 'Domyslny (kategorie)', + 'type' => 'select', + 'options' => [ + '' => '- kategorie -', + '1' => 'tak', + '0' => 'nie', + ], + ], + ]; + + $listRequest = \admin\Support\TableListRequestFactory::fromRequest( + $filterDefinitions, + $sortableColumns, + 'name' + ); + + $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 = (int)($item['id'] ?? 0); + $name = trim((string)($item['name'] ?? '')); + + $rows[] = [ + 'lp' => $lp++ . '.', + 'name' => '' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '', + 'status' => ((int)($item['status'] ?? 0) === 1) ? 'tak' : 'nie', + 'categories_default' => ((int)($item['categories_default'] ?? 0) === 1) ? 'tak' : 'nie', + '_actions' => [ + [ + 'label' => 'Edytuj', + 'url' => '/admin/layouts/layout_edit/id=' . $id, + 'class' => 'btn btn-xs btn-primary', + ], + [ + 'label' => 'Usun', + 'url' => '/admin/layouts/layout_delete/id=' . $id, + 'class' => 'btn btn-xs btn-danger', + 'confirm' => 'Na pewno chcesz usunac wybrany szablon?', + ], + ], + ]; + } + + $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' => 'name', 'sort_key' => 'name', 'label' => 'Nazwa', 'sortable' => true, 'raw' => true], + ['key' => 'status', 'sort_key' => 'status', 'label' => 'Szablon domyslny', 'class' => 'text-center', 'sortable' => true, 'raw' => true], + ['key' => 'categories_default', 'sort_key' => 'categories_default', 'label' => 'Domyslny (kategorie)', '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/layouts/view_list/', + 'Brak danych w tabeli.', + '/admin/layouts/layout_edit/', + 'Dodaj szablon' + ); + + return \Tpl::view('layouts/layouts-list', [ + 'viewModel' => $viewModel, + ]); + } + + public function edit(): string + { + return \Tpl::view('layouts/layout-edit', [ + 'layout' => $this->repository->find((int)\S::get('id')), + 'menus' => $this->repository->menusWithPages(), + 'categories' => $this->repository->categoriesTree(), + 'dlang' => $this->languagesRepository->defaultLanguageId(), + ]); + } + + public function save(): void + { + $response = ['status' => 'error', 'msg' => 'Podczas zapisywania szablonu wystapil blad. Prosze sprobowac ponownie.']; + $values = json_decode((string)\S::get('values'), true); + + if (is_array($values)) { + $id = $this->repository->save($values); + if (!empty($id)) { + $response = ['status' => 'ok', 'msg' => 'Szablon zostal zapisany.', 'id' => $id]; + } + } + + echo json_encode($response); + exit; + } + + public function delete(): void + { + if ($this->repository->delete((int)\S::get('id'))) { + \S::alert('Szablon zostal usuniety.'); + } + + header('Location: /admin/layouts/view_list/'); + exit; + } + +} diff --git a/autoload/admin/class.Site.php b/autoload/admin/class.Site.php index 5e14bd6..12f12e8 100644 --- a/autoload/admin/class.Site.php +++ b/autoload/admin/class.Site.php @@ -207,7 +207,8 @@ class Site return new \admin\Controllers\ArticlesController( new \Domain\Article\ArticleRepository( $mdb ), - new \Domain\Languages\LanguagesRepository( $mdb ) + new \Domain\Languages\LanguagesRepository( $mdb ), + new \Domain\Layouts\LayoutsRepository( $mdb ) ); }, 'Banners' => function() { @@ -266,6 +267,14 @@ class Site new \Domain\Languages\LanguagesRepository( $mdb ) ); }, + 'Layouts' => function() { + global $mdb; + + return new \admin\Controllers\LayoutsController( + new \Domain\Layouts\LayoutsRepository( $mdb ), + new \Domain\Languages\LanguagesRepository( $mdb ) + ); + }, ]; return self::$newControllers; @@ -309,6 +318,9 @@ class Site 'unit_edit' => 'edit', 'unit_save' => 'save', 'unit_delete' => 'delete', + 'layout_edit' => 'edit', + 'layout_save' => 'save', + 'layout_delete' => 'delete', ]; public static function route() diff --git a/autoload/admin/controls/class.Layouts.php b/autoload/admin/controls/class.Layouts.php deleted file mode 100644 index 9325906..0000000 --- a/autoload/admin/controls/class.Layouts.php +++ /dev/null @@ -1,43 +0,0 @@ - 'error', 'msg' => 'Podczas zapisywania szablonu wystąpił błąd. Proszę spróbować ponownie.' ]; - $values = json_decode( \S::get( 'values' ), true ); - - if ( $id = \admin\factory\Layouts::layout_save( $values['id'], $values['name'], $values['status'], $values['pages'], $values['html'], $values['css'], $values['js'], $values['m_html'], - $values['m_css'], $values['m_js'], $values['categories'], $values['categories_default'] ) - ) - $response = [ 'status' => 'ok', 'msg' => 'Szablon został zapisany.', 'id' => $id ]; - - echo json_encode( $response ); - exit; - } - - public static function layout_edit() - { - return \Tpl::view( 'layouts/layout-edit', [ - 'layout' => \admin\factory\Layouts::layout_details( \S::get( 'id' ) ), - 'menus' => \admin\factory\Layouts::menus_list(), - 'categories' => \admin\factory\ShopCategory::subcategories( null ), - 'dlang' => \front\factory\Languages::default_language() - ] ); - } - - public static function view_list() - { - return \admin\view\Layouts::layouts_list(); - } -} -?> \ No newline at end of file diff --git a/autoload/admin/factory/class.Layouts.php b/autoload/admin/factory/class.Layouts.php index 367932d..4031bb3 100644 --- a/autoload/admin/factory/class.Layouts.php +++ b/autoload/admin/factory/class.Layouts.php @@ -1,190 +1,78 @@ count( 'pp_layouts' ) > 1 ) - return $mdb -> delete( 'pp_layouts', [ 'id' => (int)$layout_id ] ); - return false; - } - - public static function layout_details( $layout_id ) - { - global $mdb; - - $layout = $mdb -> get( 'pp_layouts', '*', [ 'id' => (int)$layout_id ] ); - - $layout['pages'] = $mdb -> select( 'pp_layouts_pages', 'page_id', [ 'layout_id' => (int)$layout_id ] ); - $layout['categories'] = $mdb -> select( 'pp_layouts_categories', 'category_id', [ 'layout_id' => (int)$layout_id ] ); - - return $layout; - } - - public static function layout_save( $layout_id, $name, $status, $pages, $html, $css, $js, $m_html, $m_css, $m_js, $categories, $categories_default ) - { - global $mdb; - - if ( !$layout_id ) + public static function layout_delete($layout_id) { - if ( $status == 'on' ) - $mdb -> update( 'pp_layouts', [ 'status' => 0 ] ); - - if ( $categories_default == 'on' ) - $mdb -> update( 'pp_layouts', [ 'categories_default' => 0 ] ); - - $mdb -> insert( 'pp_layouts', [ - 'name' => $name, - 'html' => $html, - 'css' => $css, - 'js' => $js, - 'm_html' => $m_html, - 'm_css' => $m_css, - 'm_js' => $m_js, - 'status' => $status == 'on' ? 1 : 0, - 'categories_default' => $categories_default == 'on' ? 1 : 0 - ] ); - - $id = $mdb -> id(); - - if ( $id ) - { - if ( is_array( $pages ) ) foreach ( $pages as $page ) - { - $mdb -> delete( 'pp_layouts_pages', [ 'page_id' => (int)$page ] ); - - $mdb -> insert( 'pp_layouts_pages', [ - 'layout_id' => (int)$id, - 'page_id' => (int)$page - ] ); - } - else if ( $pages ) - { - $mdb -> delete( 'pp_layouts_pages', [ 'page_id' => (int)$pages ] ); - - $mdb -> insert( 'pp_layouts_pages', [ - 'layout_id' => (int)$id, - 'page_id' => (int)$pages - ] ); - } - - if ( is_array( $categories ) ) foreach ( $categories as $category ) - { - $mdb -> delete( 'pp_layouts_categories', [ 'category_id' => (int)$category ] ); - - $mdb -> insert( 'pp_layouts_categories', [ - 'layout_id' => (int)$id, - 'category_id' => (int)$category - ] ); - } - else if ( $categories ) - { - $mdb -> delete( 'pp_layouts_categories', [ 'category_id' => (int)$categories ] ); - - $mdb -> insert( 'pp_layouts_categories', [ - 'layout_id' => (int)$id, - 'category_id' => (int)$categories - ] ); - } - - \S::delete_dir( '../temp/' ); - - return $id; - } + return self::repository()->delete((int)$layout_id); } - else + + public static function layout_details($layout_id) { - if ( $status == 'on' ) - $mdb -> update( 'pp_layouts', [ 'status' => 0 ] ); - - if ( $categories_default == 'on' ) - $mdb -> update( 'pp_layouts', [ 'categories_default' => 0 ] ); - - $mdb -> update( 'pp_layouts', [ - 'name' => $name, - 'html' => $html, - 'css' => $css, - 'js' => $js, - 'm_html' => $m_html, - 'm_css' => $m_css, - 'm_js' => $m_js, - 'status' => $status == 'on' ? 1 : 0, - 'categories_default' => $categories_default == 'on' ? 1 : 0 - ], [ - 'id' => $layout_id - ] ); - - $mdb -> delete( 'pp_layouts_pages', [ 'layout_id' => (int)$layout_id ] ); - - if ( is_array( $pages ) ) foreach ( $pages as $page ) - { - $mdb -> delete( 'pp_layouts_pages', [ 'page_id' => (int)$page ] ); - - $mdb -> insert( 'pp_layouts_pages', [ - 'layout_id' => (int)$layout_id, - 'page_id' => (int)$page - ] ); - } - else if ( $pages ) - { - $mdb -> delete( 'pp_layouts_pages', [ 'page_id' => (int)$pages ] ); - - $mdb -> insert( 'pp_layouts_pages', [ - 'layout_id' => (int)$layout_id, - 'page_id' => (int)$pages - ] ); - } - - $mdb -> delete( 'pp_layouts_categories', [ 'layout_id' => (int)$layout_id ] ); - - if ( is_array( $categories ) ) foreach ( $categories as $category ) - { - $mdb -> delete( 'pp_layouts_categories', [ 'category_id' => (int)$category ] ); - - $mdb -> insert( 'pp_layouts_categories', [ - 'layout_id' => (int)$layout_id, - 'category_id' => (int)$category - ] ); - } - else if ( $categories ) - { - $mdb -> delete( 'pp_layouts_categories', [ 'category_id' => (int)$categories ] ); - - $mdb -> insert( 'pp_layouts_categories', [ - 'layout_id' => (int)$layout_id, - 'category_id' => (int)$categories - ] ); - } - - \S::delete_dir( '../temp/' ); - - return $layout_id; + return self::repository()->find((int)$layout_id); } - return false; - } - - public static function menus_list() - { - global $mdb; - - $results = $mdb -> select( 'pp_menus', 'id', [ 'ORDER' => [ 'id' => 'ASC' ] ] ); - if ( is_array( $results ) ) foreach ( $results as $row ) + + public static function layout_save( + $layout_id, + $name, + $status, + $pages, + $html, + $css, + $js, + $m_html, + $m_css, + $m_js, + $categories, + $categories_default + ) { + return self::repository()->save([ + 'id' => $layout_id, + 'name' => $name, + 'status' => $status, + 'pages' => $pages, + 'html' => $html, + 'css' => $css, + 'js' => $js, + 'm_html' => $m_html, + 'm_css' => $m_css, + 'm_js' => $m_js, + 'categories' => $categories, + 'categories_default' => $categories_default, + ]); + } + + public static function menus_list() { - $menu = \admin\factory\Pages::menu_details( $row ); - $menu['pages'] = \admin\factory\Pages::menu_pages( $row ); - - $menus[] = $menu; + $menus = \admin\factory\Pages::menus_list(); + if (!is_array($menus)) { + return []; + } + + foreach ($menus as $key => $menu) { + $menuId = (int)($menu['id'] ?? 0); + if ($menuId <= 0) { + continue; + } + + $menus[$key]['pages'] = \admin\factory\Pages::menu_pages($menuId); + } + + return $menus; + } + + public static function layouts_list() + { + return self::repository()->listAll(); + } + + private static function repository(): LayoutsRepository + { + global $mdb; + return new LayoutsRepository($mdb); } - return $menus; - } - - public static function layouts_list() - { - global $mdb; - return $mdb -> select( 'pp_layouts', '*', [ 'ORDER' => [ 'name' => 'ASC' ] ] ); - } } -?> \ No newline at end of file + diff --git a/autoload/admin/view/class.Layouts.php b/autoload/admin/view/class.Layouts.php deleted file mode 100644 index fd4ea67..0000000 --- a/autoload/admin/view/class.Layouts.php +++ /dev/null @@ -1,21 +0,0 @@ - pages = $pages; - $tpl -> step = $step; - $tpl -> layout_pages = $layout_pages; - return $tpl -> render( 'layouts/subpages-list' ); - } - - public static function layouts_list() - { - $tpl = new \Tpl; - return $tpl -> render( 'layouts/layouts-list' ); - } -} -?> \ No newline at end of file diff --git a/tests/Unit/Domain/Languages/LanguagesRepositoryTest.php b/tests/Unit/Domain/Languages/LanguagesRepositoryTest.php index 36fa2bf..97375f2 100644 --- a/tests/Unit/Domain/Languages/LanguagesRepositoryTest.php +++ b/tests/Unit/Domain/Languages/LanguagesRepositoryTest.php @@ -112,5 +112,37 @@ class LanguagesRepositoryTest extends TestCase $this->assertCount(1, $result['items']); $this->assertSame('pl', $result['items'][0]['id']); } -} + public function testDefaultLanguageIdReturnsLanguageWithStartFlag(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('select') + ->with('pp_langs', '*', ['ORDER' => ['o' => 'ASC']]) + ->willReturn([ + ['id' => 'en', 'start' => 0], + ['id' => 'pl', 'start' => 1], + ]); + + $repository = new LanguagesRepository($mockDb); + $this->assertSame('pl', $repository->defaultLanguageId()); + } + + public function testDefaultLanguageIdFallsBackToFirstLanguageOrPl(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->exactly(2)) + ->method('select') + ->with('pp_langs', '*', ['ORDER' => ['o' => 'ASC']]) + ->willReturnOnConsecutiveCalls( + [ + ['id' => 'en', 'start' => 0], + ], + [] + ); + + $repository = new LanguagesRepository($mockDb); + $this->assertSame('en', $repository->defaultLanguageId()); + $this->assertSame('pl', $repository->defaultLanguageId()); + } +} diff --git a/tests/Unit/Domain/Layouts/LayoutsRepositoryTest.php b/tests/Unit/Domain/Layouts/LayoutsRepositoryTest.php new file mode 100644 index 0000000..fdcb4a8 --- /dev/null +++ b/tests/Unit/Domain/Layouts/LayoutsRepositoryTest.php @@ -0,0 +1,110 @@ +createMock(\medoo::class); + + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_layouts', '*', ['id' => 5]) + ->willReturn(['id' => 5, 'name' => 'Main']); + + $mockDb->expects($this->exactly(2)) + ->method('select') + ->withConsecutive( + ['pp_layouts_pages', 'page_id', ['layout_id' => 5]], + ['pp_layouts_categories', 'category_id', ['layout_id' => 5]] + ) + ->willReturnOnConsecutiveCalls([10, 11], [2, 3]); + + $repository = new LayoutsRepository($mockDb); + $layout = $repository->find(5); + + $this->assertSame(5, $layout['id']); + $this->assertSame([10, 11], $layout['pages']); + $this->assertSame([2, 3], $layout['categories']); + } + + public function testDeleteReturnsFalseWhenOnlyOneLayoutExists(): void + { + $mockDb = $this->createMock(\medoo::class); + + $mockDb->expects($this->once()) + ->method('count') + ->with('pp_layouts') + ->willReturn(1); + + $repository = new LayoutsRepository($mockDb); + $this->assertFalse($repository->delete(1)); + } + + public function testFindReturnsDefaultLayoutWhenRecordDoesNotExist(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_layouts', '*', ['id' => 999]) + ->willReturn(false); + + $repository = new LayoutsRepository($mockDb); + $layout = $repository->find(999); + + $this->assertSame(0, $layout['id']); + $this->assertSame([], $layout['pages']); + $this->assertSame([], $layout['categories']); + } + + public function testSaveInsertsNewLayoutAndReturnsId(): void + { + $mockDb = $this->createMock(\medoo::class); + + $mockDb->expects($this->once()) + ->method('insert') + ->with('pp_layouts', $this->arrayHasKey('name')); + + $mockDb->expects($this->once()) + ->method('id') + ->willReturn(9); + + $mockDb->expects($this->exactly(2)) + ->method('delete') + ->withConsecutive( + ['pp_layouts_pages', ['layout_id' => 9]], + ['pp_layouts_categories', ['layout_id' => 9]] + ) + ->willReturn(true); + + $repository = new LayoutsRepository($mockDb); + $savedId = $repository->save([ + 'name' => 'Nowy szablon', + 'status' => 0, + 'categories_default' => 0, + ]); + + $this->assertSame(9, $savedId); + } + + public function testListAllReturnsArray(): void + { + $mockDb = $this->createMock(\medoo::class); + + $mockDb->expects($this->once()) + ->method('select') + ->with('pp_layouts', '*', ['ORDER' => ['name' => 'ASC']]) + ->willReturn([ + ['id' => 1, 'name' => 'Default'], + ]); + + $repository = new LayoutsRepository($mockDb); + $rows = $repository->listAll(); + + $this->assertCount(1, $rows); + $this->assertSame('Default', $rows[0]['name']); + } +} diff --git a/tests/Unit/admin/Controllers/ArticlesControllerTest.php b/tests/Unit/admin/Controllers/ArticlesControllerTest.php index 6b1ad26..15b92e2 100644 --- a/tests/Unit/admin/Controllers/ArticlesControllerTest.php +++ b/tests/Unit/admin/Controllers/ArticlesControllerTest.php @@ -5,18 +5,25 @@ use PHPUnit\Framework\TestCase; use admin\Controllers\ArticlesController; use Domain\Article\ArticleRepository; use Domain\Languages\LanguagesRepository; +use Domain\Layouts\LayoutsRepository; class ArticlesControllerTest extends TestCase { private $mockRepository; private $mockLanguagesRepository; + private $mockLayoutsRepository; private $controller; protected function setUp(): void { $this->mockRepository = $this->createMock(ArticleRepository::class); $this->mockLanguagesRepository = $this->createMock(LanguagesRepository::class); - $this->controller = new ArticlesController($this->mockRepository, $this->mockLanguagesRepository); + $this->mockLayoutsRepository = $this->createMock(LayoutsRepository::class); + $this->controller = new ArticlesController( + $this->mockRepository, + $this->mockLanguagesRepository, + $this->mockLayoutsRepository + ); } public function testCanCreateController(): void @@ -26,7 +33,11 @@ class ArticlesControllerTest extends TestCase public function testConstructorAcceptsRepository(): void { - $controller = new ArticlesController($this->mockRepository, $this->mockLanguagesRepository); + $controller = new ArticlesController( + $this->mockRepository, + $this->mockLanguagesRepository, + $this->mockLayoutsRepository + ); $this->assertInstanceOf(ArticlesController::class, $controller); } @@ -69,8 +80,9 @@ class ArticlesControllerTest extends TestCase $constructor = $reflection->getConstructor(); $params = $constructor->getParameters(); - $this->assertCount(2, $params); + $this->assertCount(3, $params); $this->assertEquals('Domain\Article\ArticleRepository', $params[0]->getType()->getName()); $this->assertEquals('Domain\Languages\LanguagesRepository', $params[1]->getType()->getName()); + $this->assertEquals('Domain\Layouts\LayoutsRepository', $params[2]->getType()->getName()); } } diff --git a/tests/Unit/admin/Controllers/LayoutsControllerTest.php b/tests/Unit/admin/Controllers/LayoutsControllerTest.php new file mode 100644 index 0000000..e9d47ed --- /dev/null +++ b/tests/Unit/admin/Controllers/LayoutsControllerTest.php @@ -0,0 +1,56 @@ +mockRepository = $this->createMock(LayoutsRepository::class); + $this->mockLanguagesRepository = $this->createMock(LanguagesRepository::class); + $this->controller = new LayoutsController($this->mockRepository, $this->mockLanguagesRepository); + } + + public function testConstructorAcceptsRepository(): void + { + $controller = new LayoutsController($this->mockRepository, $this->mockLanguagesRepository); + $this->assertInstanceOf(LayoutsController::class, $controller); + } + + public function testHasMainActionMethods(): void + { + $this->assertTrue(method_exists($this->controller, 'list')); + $this->assertTrue(method_exists($this->controller, 'edit')); + $this->assertTrue(method_exists($this->controller, 'save')); + $this->assertTrue(method_exists($this->controller, 'delete')); + } + + public function testActionMethodReturnTypes(): void + { + $reflection = new \ReflectionClass($this->controller); + + $this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('edit')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('delete')->getReturnType()); + } + + public function testConstructorRequiresLayoutsRepository(): void + { + $reflection = new \ReflectionClass(LayoutsController::class); + $constructor = $reflection->getConstructor(); + $params = $constructor->getParameters(); + + $this->assertCount(2, $params); + $this->assertEquals('Domain\Layouts\LayoutsRepository', $params[0]->getType()->getName()); + $this->assertEquals('Domain\Languages\LanguagesRepository', $params[1]->getType()->getName()); + } +} diff --git a/updates/0.20/ver_0.256.zip b/updates/0.20/ver_0.256.zip new file mode 100644 index 0000000..bb16d60 Binary files /dev/null and b/updates/0.20/ver_0.256.zip differ diff --git a/updates/0.20/ver_0.256_files.txt b/updates/0.20/ver_0.256_files.txt new file mode 100644 index 0000000..b90b43d --- /dev/null +++ b/updates/0.20/ver_0.256_files.txt @@ -0,0 +1,2 @@ +F: ../autoload/admin/controls/class.Layouts.php +F: ../autoload/admin/view/class.Layouts.php diff --git a/updates/changelog.php b/updates/changelog.php index c038a18..b402229 100644 --- a/updates/changelog.php +++ b/updates/changelog.php @@ -1,8 +1,15 @@ -ver. 0.255 - 12.02.2026
+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 +- UPDATE - `Domain\\Languages\\LanguagesRepository` rozszerzone o wspolna metode `defaultLanguageId()` +- UPDATE - `admin\\Controllers\\ArticlesController` pobiera layouty przez `Domain\\Layouts\\LayoutsRepository` (DI) +- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.Layouts.php`, `autoload/admin/view/class.Layouts.php` +
ver. 0.255 - 12.02.2026
- UPDATE - kontrolery admin `Settings`, `Banners`, `Dictionaries`, `Articles` pobieraja liste jezykow przez `Domain\\Languages\\LanguagesRepository` (DI) - UPDATE - routing DI (`admin\\Site`) przekazuje `LanguagesRepository` do kontrolerow `Articles`, `Banners`, `Settings`, `Dictionaries` - UPDATE - aktywne legacy odwolania (`admin\\controls`, `admin\\factory\\Shop*`) przepiete z `admin\\factory\\Languages` na `LanguagesRepository` -- FIX - `autoload/admin/factory/class.Languages.php` uzywa `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` @@ -383,3 +390,4 @@ + diff --git a/updates/versions.php b/updates/versions.php index f6ef0de..b64e3e9 100644 --- a/updates/versions.php +++ b/updates/versions.php @@ -1,5 +1,5 @@