ver. 0.286: Layouts, Menu, Pages frontend migration to Domain

- Add 6 frontend methods to LayoutsRepository (Redis cache, 3-level fallback)
- Add 6 frontend methods to PagesRepository (Redis cache, recursive pages)
- Create front\Views\Menu (clean VIEW replacing front\view\Menu)
- Delete front\factory\Layouts, Menu, Pages + front\view\Menu + dead submenu.php
- Fix null $lang_id TypeError in check_url_params() (remove string type hint + ?? '')
- Optimize Helpers::htacces() from 3 layout calls to 1
- Tests: 470 OK, 1484 assertions (+16 new)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 08:21:53 +01:00
parent 920e65abd5
commit 8162df7356
27 changed files with 697 additions and 413 deletions

View File

@@ -227,6 +227,183 @@ class LayoutsRepository
];
}
// ── 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);
}
$layoutRows = $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"
)->fetchAll(\PDO::FETCH_ASSOC);
if (is_array($layoutRows) && isset($layoutRows[0])) {
$layout = $layoutRows[0];
} else {
$layoutRows = $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"
)->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 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);
}
$layoutRows = $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"
)->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) {

View File

@@ -628,4 +628,158 @@ class PagesRepository
$value = trim((string)$value);
return $value === '' ? null : $value;
}
// ── Frontend methods ──────────────────────────────────────────
public function frontPageDetails($id = '', $langId = ''): ?array
{
$langId = (string)$langId;
if (!$id) {
$id = $this->frontMainPageId();
}
$cacheHandler = new \Shared\Cache\CacheHandler();
$cacheKey = "PagesRepository::frontPageDetails:$id:$langId";
$objectData = $cacheHandler->get($cacheKey);
if ($objectData) {
$cached = @unserialize($objectData);
if (is_array($cached)) {
return $cached;
}
$cacheHandler->delete($cacheKey);
}
$page = $this->db->get('pp_pages', '*', ['id' => (int)$id]);
if (!is_array($page)) {
return null;
}
$page['language'] = $this->db->get('pp_pages_langs', '*', ['AND' => ['page_id' => (int)$id, 'lang_id' => $langId]]);
$cacheHandler->set($cacheKey, $page);
return $page;
}
public function frontPageSort(int $pageId)
{
$cacheHandler = new \Shared\Cache\CacheHandler();
$cacheKey = "PagesRepository::frontPageSort:$pageId";
$objectData = $cacheHandler->get($cacheKey);
if ($objectData) {
$cached = @unserialize($objectData);
if ($cached !== false) {
return $cached;
}
$cacheHandler->delete($cacheKey);
}
$sort = $this->db->get('pp_pages', 'sort_type', ['id' => $pageId]);
$cacheHandler->set($cacheKey, $sort);
return $sort;
}
public function frontMainPageId()
{
$cacheHandler = new \Shared\Cache\CacheHandler();
$cacheKey = 'PagesRepository::frontMainPageId';
$objectData = $cacheHandler->get($cacheKey);
if ($objectData) {
$cached = @unserialize($objectData);
if ($cached) {
return $cached;
}
$cacheHandler->delete($cacheKey);
}
$id = $this->db->get('pp_pages', 'id', ['AND' => ['status' => 1, 'start' => 1]]);
if (!$id) {
$id = $this->db->get('pp_pages', 'id', ['status' => 1, 'ORDER' => ['menu_id' => 'ASC', 'o' => 'ASC'], 'LIMIT' => 1]);
}
$cacheHandler->set($cacheKey, $id);
return $id;
}
public function frontLangUrl(int $pageId, string $langId): string
{
$page = $this->frontPageDetails($pageId, $langId);
if (!is_array($page) || !is_array($page['language'] ?? null)) {
return '/';
}
$seoLink = $page['language']['seo_link'] ?? '';
$title = $page['language']['title'] ?? '';
$url = $seoLink ? '/' . $seoLink : '/s-' . $page['id'] . '-' . \Shared\Helpers\Helpers::seo($title);
$defaultLang = (new \Domain\Languages\LanguagesRepository($this->db))->defaultLanguage();
if ($langId !== $defaultLang && $url !== '#') {
$url = '/' . $langId . $url;
}
return $url;
}
public function frontMenuDetails(int $menuId, string $langId): ?array
{
$cacheHandler = new \Shared\Cache\CacheHandler();
$cacheKey = "PagesRepository::frontMenuDetails:$menuId:$langId";
$objectData = $cacheHandler->get($cacheKey);
if ($objectData) {
$cached = @unserialize($objectData);
if (is_array($cached)) {
return $cached;
}
$cacheHandler->delete($cacheKey);
}
$menu = $this->db->get('pp_menus', '*', ['id' => (int)$menuId]);
if (!is_array($menu)) {
return null;
}
$menu['pages'] = $this->frontMenuPages($menuId, $langId);
$cacheHandler->set($cacheKey, $menu);
return $menu;
}
public function frontMenuPages(int $menuId, string $langId, $parentId = null): ?array
{
$cacheHandler = new \Shared\Cache\CacheHandler();
$cacheKey = "PagesRepository::frontMenuPages:$menuId:$langId:$parentId";
$objectData = $cacheHandler->get($cacheKey);
if ($objectData) {
$cached = @unserialize($objectData);
if (is_array($cached)) {
return $cached;
}
$cacheHandler->delete($cacheKey);
}
$results = $this->db->select('pp_pages', ['id'], [
'AND' => ['status' => 1, 'menu_id' => (int)$menuId, 'parent_id' => $parentId],
'ORDER' => ['o' => 'ASC'],
]);
$pages = [];
if (is_array($results)) {
foreach ($results as $row) {
$page = $this->frontPageDetails($row['id'], $langId);
if (is_array($page)) {
$page['pages'] = $this->frontMenuPages($menuId, $langId, $row['id']);
$pages[] = $page;
}
}
}
$cacheHandler->set($cacheKey, $pages);
return $pages;
}
}