db = $db; } public function delete(int $layoutId): bool { if ((int)$this->db->count('pp_layouts') <= 1) { return false; } $deleted = (bool)$this->db->delete('pp_layouts', ['id' => $layoutId]); if ($deleted) { \Shared\Helpers\Helpers::delete_dir('../temp/'); $this->clearFrontLayoutsCache(); } return $deleted; } 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'] ?? ''), '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'] ?? []); \Shared\Helpers\Helpers::delete_dir('../temp/'); $this->clearFrontLayoutsCache(); 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, ]; } // ── Frontend methods ────────────────────────────────────────── public function categoryDefaultLayoutId() { return $this->db->get('pp_layouts', 'id', ['categories_default' => 1]); } public function getDefaultLayout(): ?array { $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = 'LayoutsRepository::getDefaultLayout'; $objectData = $cacheHandler->get($cacheKey); if ($objectData) { $cached = @unserialize($objectData); if (is_array($cached) && !empty($cached)) { return $cached; } $cacheHandler->delete($cacheKey); } $layout = $this->db->get('pp_layouts', '*', ['status' => 1]); if (!is_array($layout) || empty($layout)) { return null; } $cacheHandler->set($cacheKey, $layout); return $layout; } public function getProductLayout(int $productId): ?array { $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "LayoutsRepository::getProductLayout:$productId"; $objectData = $cacheHandler->get($cacheKey); if ($objectData) { $cached = @unserialize($objectData); if (is_array($cached) && !empty($cached)) { return $cached; } $cacheHandler->delete($cacheKey); } $stmt = $this->db->query( "SELECT pp_layouts.* FROM pp_layouts JOIN pp_shop_products ON pp_layouts.id = pp_shop_products.layout_id WHERE pp_shop_products.id = " . (int)$productId . " ORDER BY pp_layouts.id DESC" ); $layoutRows = $stmt ? $stmt->fetchAll(\PDO::FETCH_ASSOC) : []; if (is_array($layoutRows) && isset($layoutRows[0])) { $layout = $layoutRows[0]; } else { $stmt2 = $this->db->query( "SELECT pp_layouts.* FROM pp_layouts JOIN pp_layouts_categories ON pp_layouts.id = pp_layouts_categories.layout_id JOIN pp_shop_products_categories ON pp_shop_products_categories.category_id = pp_layouts_categories.category_id WHERE pp_shop_products_categories.product_id = " . (int)$productId . " ORDER BY pp_shop_products_categories.o ASC, pp_layouts.id DESC" ); $layoutRows = $stmt2 ? $stmt2->fetchAll(\PDO::FETCH_ASSOC) : []; if (is_array($layoutRows) && isset($layoutRows[0])) { $layout = $layoutRows[0]; } else { $layout = $this->db->get('pp_layouts', '*', ['status' => 1]); } } if (!$layout) { $layout = $this->db->get('pp_layouts', '*', ['status' => 1]); } if (!is_array($layout) || empty($layout)) { return null; } $cacheHandler->set($cacheKey, $layout); return $layout; } public function getArticleLayout(int $articleId): ?array { $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "LayoutsRepository::getArticleLayout:$articleId"; $objectData = $cacheHandler->get($cacheKey); if ($objectData) { $cached = @unserialize($objectData); if (is_array($cached)) { return $cached; } $cacheHandler->delete($cacheKey); } $layout = $this->db->get('pp_layouts', ['[><]pp_articles' => ['id' => 'layout_id']], '*', ['pp_articles.id' => (int)$articleId]); if (is_array($layout)) { $cacheHandler->set($cacheKey, $layout); return $layout; } return null; } public function getCategoryLayout(int $categoryId): ?array { $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "LayoutsRepository::getCategoryLayout:$categoryId"; $objectData = $cacheHandler->get($cacheKey); if ($objectData) { $cached = @unserialize($objectData); if (is_array($cached) && !empty($cached)) { return $cached; } $cacheHandler->delete($cacheKey); } $stmt = $this->db->query( "SELECT pp_layouts.* FROM pp_layouts JOIN pp_layouts_categories ON pp_layouts.id = pp_layouts_categories.layout_id WHERE pp_layouts_categories.category_id = " . (int)$categoryId . " ORDER BY pp_layouts.id DESC" ); $layoutRows = $stmt ? $stmt->fetchAll(\PDO::FETCH_ASSOC) : []; if (is_array($layoutRows) && isset($layoutRows[0])) { $layout = $layoutRows[0]; } else { $layout = $this->db->get('pp_layouts', '*', ['categories_default' => 1]); } if (!$layout) { $layout = $this->db->get('pp_layouts', '*', ['status' => 1]); } if (!is_array($layout) || empty($layout)) { return null; } $cacheHandler->set($cacheKey, $layout); return $layout; } public function getActiveLayout(int $pageId): ?array { $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "LayoutsRepository::getActiveLayout:$pageId"; $objectData = $cacheHandler->get($cacheKey); if ($objectData) { $cached = @unserialize($objectData); if (is_array($cached)) { return $cached; } $cacheHandler->delete($cacheKey); } $layout = $this->db->get('pp_layouts', ['[><]pp_layouts_pages' => ['id' => 'layout_id']], '*', ['page_id' => (int)$pageId]); if (!$layout) { $layout = $this->db->get('pp_layouts', '*', ['status' => 1]); } if (is_array($layout)) { $cacheHandler->set($cacheKey, $layout); return $layout; } return null; } // ── Private helpers ────────────────────────────────────────── 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' => '', 'pages' => [], 'categories' => [], ]; } private function clearFrontLayoutsCache(): void { if (!class_exists('\Shared\Cache\CacheHandler')) { return; } try { $cacheHandler = new \Shared\Cache\CacheHandler(); if (method_exists($cacheHandler, 'deletePattern')) { $cacheHandler->deletePattern('*Layouts::*'); } } catch (\Throwable $e) { // Inwalidacja cache nie moze blokowac zapisu/usuwania. } } 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 ''; } }