db = $db; } /** * Pobiera artykul po ID wraz z tlumaczeniami, obrazami, plikami i powiazanymi stronami */ public function find(int $articleId): ?array { $article = $this->db->get('pp_articles', '*', ['id' => $articleId]); if (!$article) { return null; } $results = $this->db->select('pp_articles_langs', '*', ['article_id' => $articleId]); if (is_array($results)) { foreach ($results as $row) { $article['languages'][$row['lang_id']] = $row; } } $article['images'] = $this->db->select('pp_articles_images', '*', [ 'article_id' => $articleId, 'ORDER' => ['o' => 'ASC', 'id' => 'DESC'] ]); try { $article['files'] = $this->db->select('pp_articles_files', '*', [ 'article_id' => $articleId, 'ORDER' => ['o' => 'ASC', 'id' => 'DESC'] ]); } catch (\Throwable $e) { // Fallback for instances where pp_articles_files does not yet have "o" column. $article['files'] = $this->db->select('pp_articles_files', '*', ['article_id' => $articleId]); } $article['pages'] = $this->db->select('pp_articles_pages', 'page_id', ['article_id' => $articleId]); return $article; } /** * Zapisuje artykul (tworzy nowy lub aktualizuje istniejacy). * Zwraca ID artykulu. */ public function save(int $articleId, array $data, int $userId): int { if (!$articleId) { return $this->createArticle($data, $userId); } return $this->updateArticle($articleId, $data, $userId); } private function createArticle(array $data, int $userId): int { $this->db->insert('pp_articles', $this->buildArticleRow($data, $userId, true)); $id = $this->db->id(); if (!$id) { return 0; } $this->saveTranslations($id, $data, true); $this->savePages($id, $data['pages'] ?? null, true); $this->assignTempFiles($id); $this->assignTempImages($id); $this->applyGalleryOrderIfProvided($id, $data); $this->applyFilesOrderIfProvided($id, $data); \Shared\Helpers\Helpers::htacces(); \Shared\Helpers\Helpers::delete_dir('../temp/'); return (int)$id; } private function updateArticle(int $articleId, array $data, int $userId): int { $this->db->update('pp_articles', $this->buildArticleRow($data, $userId, false), [ 'id' => $articleId ]); $this->saveTranslations($articleId, $data, false); $this->savePages($articleId, $data['pages'] ?? null, false); $this->assignTempFiles($articleId); $this->assignTempImages($articleId); $this->applyGalleryOrderIfProvided($articleId, $data); $this->applyFilesOrderIfProvided($articleId, $data); $this->deleteMarkedImages($articleId); $this->deleteMarkedFiles($articleId); \Shared\Helpers\Helpers::htacces(); \Shared\Helpers\Helpers::delete_dir('../temp/'); return $articleId; } private function buildArticleRow(array $data, int $userId, bool $isNew): array { $row = [ 'show_title' => $this->isCheckedValue($data['show_title'] ?? null) ? 1 : 0, 'show_date_add' => $this->isCheckedValue($data['show_date_add'] ?? null) ? 1 : 0, 'show_date_modify' => $this->isCheckedValue($data['show_date_modify'] ?? null) ? 1 : 0, 'date_modify' => date('Y-m-d H:i:s'), 'modify_by' => $userId, 'layout_id' => !empty($data['layout_id']) ? (int)$data['layout_id'] : null, 'status' => $this->isCheckedValue($data['status'] ?? null) ? 1 : 0, 'repeat_entry' => $this->isCheckedValue($data['repeat_entry'] ?? null) ? 1 : 0, 'social_icons' => $this->isCheckedValue($data['social_icons'] ?? null) ? 1 : 0, 'show_table_of_contents' => $this->isCheckedValue($data['show_table_of_contents'] ?? null) ? 1 : 0, ]; if ($isNew) { $row['date_add'] = date('Y-m-d H:i:s'); } return $row; } private function buildLangRow($langId, array $data): array { return [ 'lang_id' => $langId, 'title' => ($data['title'][$langId] ?? '') != '' ? $data['title'][$langId] : null, 'main_image' => ($data['main_image'][$langId] ?? '') != '' ? $data['main_image'][$langId] : null, 'entry' => ($data['entry'][$langId] ?? '') != '' ? $data['entry'][$langId] : null, 'text' => ($data['text'][$langId] ?? '') != '' ? $data['text'][$langId] : null, 'table_of_contents' => ($data['table_of_contents'][$langId] ?? '') != '' ? $data['table_of_contents'][$langId] : null, 'meta_title' => ($data['meta_title'][$langId] ?? '') != '' ? $data['meta_title'][$langId] : null, 'meta_description' => ($data['meta_description'][$langId] ?? '') != '' ? $data['meta_description'][$langId] : null, 'meta_keywords' => ($data['meta_keywords'][$langId] ?? '') != '' ? $data['meta_keywords'][$langId] : null, 'seo_link' => \Shared\Helpers\Helpers::seo($data['seo_link'][$langId] ?? '') != '' ? \Shared\Helpers\Helpers::seo($data['seo_link'][$langId]) : null, 'noindex' => $this->isCheckedValue($data['noindex'][$langId] ?? null) ? 1 : 0, 'copy_from' => ($data['copy_from'][$langId] ?? '') != '' ? $data['copy_from'][$langId] : null, 'block_direct_access' => $this->isCheckedValue($data['block_direct_access'][$langId] ?? null) ? 1 : 0, ]; } private function applyGalleryOrderIfProvided(int $articleId, array $data): void { $order = trim((string)($data['gallery_order'] ?? '')); if ($order === '') { return; } $this->saveGalleryOrder($articleId, $order); } private function applyFilesOrderIfProvided(int $articleId, array $data): void { $order = trim((string)($data['files_order'] ?? '')); if ($order === '') { return; } $this->saveFilesOrder($articleId, $order); } private function saveTranslations(int $articleId, array $data, bool $isNew): void { $titles = $data['title'] ?? []; foreach ($titles as $langId => $val) { $langRow = $this->buildLangRow($langId, $data); if ($isNew) { $langRow['article_id'] = $articleId; $this->db->insert('pp_articles_langs', $langRow); } else { $translationId = $this->db->get('pp_articles_langs', 'id', [ 'AND' => ['article_id' => $articleId, 'lang_id' => $langId] ]); if ($translationId) { $this->db->update('pp_articles_langs', $langRow, ['id' => $translationId]); } else { $langRow['article_id'] = $articleId; $this->db->insert('pp_articles_langs', $langRow); } } } } private function savePages(int $articleId, $pages, bool $isNew): void { if (!$isNew) { $notIn = [0]; if (is_array($pages)) { foreach ($pages as $page) { $notIn[] = $page; } } elseif ($pages) { $notIn[] = $pages; } $this->db->delete('pp_articles_pages', [ 'AND' => ['article_id' => $articleId, 'page_id[!]' => $notIn] ]); $existingPages = $this->db->select('pp_articles_pages', 'page_id', ['article_id' => $articleId]); if (!is_array($pages)) { $pages = [$pages]; } $pages = array_diff($pages, is_array($existingPages) ? $existingPages : []); } else { if (!is_array($pages)) { $pages = $pages ? [$pages] : []; } } if (is_array($pages)) { foreach ($pages as $page) { $order = $this->maxPageOrder() + 1; $this->db->insert('pp_articles_pages', [ 'article_id' => $articleId, 'page_id' => (int)$page, 'o' => $order, ]); } } } private function assignTempFiles(int $articleId): void { $results = $this->db->select('pp_articles_files', '*', ['article_id' => null]); if (!is_array($results)) { return; } $created = false; $dir = '/upload/article_files/article_' . $articleId; foreach ($results as $row) { $newFileName = str_replace('/upload/article_files/tmp', $dir, $row['src']); if (file_exists('..' . $row['src'])) { if (!is_dir('../' . $dir) && $created !== true) { if (mkdir('../' . $dir, 0755, true)) { $created = true; } } rename('..' . $row['src'], '..' . $newFileName); } $this->db->update('pp_articles_files', [ 'src' => $newFileName, 'article_id' => $articleId, ], ['id' => $row['id']]); } } private function assignTempImages(int $articleId): void { $results = $this->db->select('pp_articles_images', '*', ['article_id' => null]); if (!is_array($results)) { return; } $created = false; $dir = '/upload/article_images/article_' . $articleId; foreach ($results as $row) { $newFileName = str_replace('/upload/article_images/tmp', $dir, $row['src']); if (file_exists('../' . $newFileName)) { $ext = strrpos($newFileName, '.'); $fileNameA = substr($newFileName, 0, $ext); $fileNameB = substr($newFileName, $ext); $count = 1; while (file_exists('../' . $fileNameA . '_' . $count . $fileNameB)) { $count++; } $newFileName = $fileNameA . '_' . $count . $fileNameB; } if (file_exists('..' . $row['src'])) { if (!is_dir('../' . $dir) && $created !== true) { if (mkdir('../' . $dir, 0755, true)) { $created = true; } } rename('..' . $row['src'], '..' . $newFileName); } $this->db->update('pp_articles_images', [ 'src' => $newFileName, 'article_id' => $articleId, ], ['id' => $row['id']]); } } private function deleteMarkedImages(int $articleId): void { $results = $this->db->select('pp_articles_images', '*', [ 'AND' => ['article_id' => $articleId, 'to_delete' => 1] ]); if (is_array($results)) { foreach ($results as $row) { $this->safeUnlink($row['src']); } } $this->db->delete('pp_articles_images', [ 'AND' => ['article_id' => $articleId, 'to_delete' => 1] ]); } private function deleteMarkedFiles(int $articleId): void { $results = $this->db->select('pp_articles_files', '*', [ 'AND' => ['article_id' => $articleId, 'to_delete' => 1] ]); if (is_array($results)) { foreach ($results as $row) { $this->safeUnlink($row['src']); } } $this->db->delete('pp_articles_files', [ 'AND' => ['article_id' => $articleId, 'to_delete' => 1] ]); } private function maxPageOrder(): int { $max = $this->db->max('pp_articles_pages', 'o'); return $max ? (int)$max : 0; } /** * Archiwizuje artykul (ustawia status = -1). */ public function archive(int $articleId): bool { $result = $this->db->update('pp_articles', ['status' => -1], ['id' => $articleId]); if ($result) { $this->db->delete('pp_routes', ['article_id' => $articleId]); } return (bool)$result; } /** * Przywraca artykul z archiwum (status = 0). */ public function restore(int $articleId): bool { $result = $this->db->update('pp_articles', ['status' => 0], ['id' => $articleId]); return (bool)$result; } /** * Trwale usuwa artykul wraz z relacjami i plikami z dysku. */ public function deletePermanently(int $articleId): bool { $this->db->delete('pp_articles_pages', ['article_id' => $articleId]); $this->db->delete('pp_articles_langs', ['article_id' => $articleId]); $this->db->delete('pp_articles_images', ['article_id' => $articleId]); $this->db->delete('pp_articles_files', ['article_id' => $articleId]); $this->db->delete('pp_routes', ['article_id' => $articleId]); $this->db->delete('pp_articles', ['id' => $articleId]); \Shared\Helpers\Helpers::delete_dir('../upload/article_images/article_' . $articleId . '/'); \Shared\Helpers\Helpers::delete_dir('../upload/article_files/article_' . $articleId . '/'); return true; } /** * Zwraca liste artykulow do panelu admin z filtrowaniem, sortowaniem i paginacja. * * @return array{items: array>, total: int} */ public function listForAdmin( array $filters, string $sortColumn = 'date_add', string $sortDir = 'DESC', int $page = 1, int $perPage = 15 ): array { $sortColumn = trim($sortColumn); $sortDir = strtoupper(trim($sortDir)); $allowedSortColumns = [ 'title' => 'title', 'status' => 'pa.status', 'date_add' => 'pa.date_add', 'date_modify' => 'pa.date_modify', 'user' => 'user', ]; $sortSql = $allowedSortColumns[$sortColumn] ?? 'pa.date_add'; $sortDir = $sortDir === 'ASC' ? 'ASC' : 'DESC'; $page = max(1, $page); $perPage = min(self::MAX_PER_PAGE, max(1, $perPage)); $offset = ($page - 1) * $perPage; $where = ['pa.status != -1']; $params = []; $title = trim((string)($filters['title'] ?? '')); if (strlen($title) > 255) { $title = substr($title, 0, 255); } if ($title !== '') { $where[] = "( SELECT title FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = pa.id AND title != '' ORDER BY o ASC LIMIT 1 ) LIKE :title"; $params[':title'] = '%' . $title . '%'; } if (($filters['status'] ?? '') !== '' && ($filters['status'] === '0' || $filters['status'] === '1')) { $where[] = 'pa.status = :status'; $params[':status'] = (int)$filters['status']; } $this->appendDateRangeFilter($where, $params, 'pa.date_add', 'date_add_from', 'date_add_to', $filters); $this->appendDateRangeFilter($where, $params, 'pa.date_modify', 'date_modify_from', 'date_modify_to', $filters); $whereSql = implode(' AND ', $where); $sqlCount = " SELECT COUNT(0) FROM pp_articles AS pa WHERE {$whereSql} "; $stmtCount = $this->db->query($sqlCount, $params); $countRows = $stmtCount ? $stmtCount->fetchAll() : []; $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; $sql = " SELECT pa.id, pa.date_add, pa.date_modify, pa.status, ( SELECT title FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = pa.id AND title != '' ORDER BY o ASC LIMIT 1 ) AS title, ( SELECT login FROM pp_users AS pu WHERE pu.id = pa.modify_by ) AS user FROM pp_articles AS pa WHERE {$whereSql} ORDER BY {$sortSql} {$sortDir}, pa.id {$sortDir} LIMIT {$perPage} OFFSET {$offset} "; $stmt = $this->db->query($sql, $params); $items = $stmt ? $stmt->fetchAll() : []; return [ 'items' => is_array($items) ? $items : [], 'total' => $total, ]; } /** * Zwraca liste artykulow z archiwum do panelu admin z filtrowaniem, sortowaniem i paginacja. * * @return array{items: array>, total: int} */ public function listArchivedForAdmin( array $filters, string $sortColumn = 'date_add', string $sortDir = 'DESC', int $page = 1, int $perPage = 15 ): array { $sortColumn = trim($sortColumn); $sortDir = strtoupper(trim($sortDir)); $allowedSortColumns = [ 'title' => 'title', 'date_add' => 'pa.date_add', 'date_modify' => 'pa.date_modify', ]; $sortSql = $allowedSortColumns[$sortColumn] ?? 'pa.date_add'; $sortDir = $sortDir === 'ASC' ? 'ASC' : 'DESC'; $page = max(1, $page); $perPage = min(self::MAX_PER_PAGE, max(1, $perPage)); $offset = ($page - 1) * $perPage; $where = ['pa.status = -1']; $params = []; $title = trim((string)($filters['title'] ?? '')); if (strlen($title) > 255) { $title = substr($title, 0, 255); } if ($title !== '') { $where[] = "( SELECT title FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = pa.id AND title != '' ORDER BY o ASC LIMIT 1 ) LIKE :title"; $params[':title'] = '%' . $title . '%'; } $this->appendDateRangeFilter($where, $params, 'pa.date_add', 'date_add_from', 'date_add_to', $filters); $this->appendDateRangeFilter($where, $params, 'pa.date_modify', 'date_modify_from', 'date_modify_to', $filters); $whereSql = implode(' AND ', $where); $sqlCount = " SELECT COUNT(0) FROM pp_articles AS pa WHERE {$whereSql} "; $stmtCount = $this->db->query($sqlCount, $params); $countRows = $stmtCount ? $stmtCount->fetchAll() : []; $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; $sql = " SELECT pa.id, pa.date_add, pa.date_modify, ( SELECT title FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = pa.id AND title != '' ORDER BY o ASC LIMIT 1 ) AS title FROM pp_articles AS pa WHERE {$whereSql} ORDER BY {$sortSql} {$sortDir}, pa.id {$sortDir} LIMIT {$perPage} OFFSET {$offset} "; $stmt = $this->db->query($sql, $params); $items = $stmt ? $stmt->fetchAll() : []; return [ 'items' => is_array($items) ? $items : [], 'total' => $total, ]; } /** * Zapisuje kolejnosc zdjec galerii artykulu. */ public function saveGalleryOrder(int $articleId, string $order): bool { $imageIds = explode(';', $order); if (!is_array($imageIds) || empty($imageIds)) { return true; } $position = 0; foreach ($imageIds as $imageId) { if ($imageId === '' || $imageId === null) { continue; } $this->db->update('pp_articles_images', [ 'o' => $position++, ], [ 'AND' => [ 'article_id' => $articleId, 'id' => (int)$imageId, ], ]); } \Shared\Helpers\Helpers::delete_dir('../temp/'); return true; } /** * Zapisuje kolejnosc zalacznikow artykulu. */ public function saveFilesOrder(int $articleId, string $order): bool { $fileIds = explode(';', $order); if (!is_array($fileIds) || empty($fileIds)) { return true; } $position = 0; foreach ($fileIds as $fileId) { if ($fileId === '' || $fileId === null) { continue; } try { $this->db->update('pp_articles_files', [ 'o' => $position++, ], [ 'AND' => [ 'article_id' => $articleId, 'id' => (int)$fileId, ], ]); } catch (\Throwable $e) { // Fallback for instances where pp_articles_files does not yet have "o" column. return true; } } \Shared\Helpers\Helpers::delete_dir('../temp/'); return true; } /** * Zwraca mape: article_id => etykieta stron (np. " - Strona A / Strona B"). * * @param array $articleIds * @return array */ public function pagesSummaryForArticles(array $articleIds): array { $normalizedIds = []; foreach ($articleIds as $articleId) { $id = (int)$articleId; if ($id > 0) { $normalizedIds[$id] = $id; } } if (empty($normalizedIds)) { return []; } $placeholders = []; $params = []; foreach (array_values($normalizedIds) as $index => $id) { $key = ':article_id_' . $index; $placeholders[] = $key; $params[$key] = $id; } $sql = " SELECT ap.article_id, ap.page_id, ( SELECT title FROM pp_pages_langs AS ppl, pp_langs AS pl WHERE ppl.lang_id = pl.id AND ppl.page_id = ap.page_id AND ppl.title != '' ORDER BY pl.o ASC LIMIT 1 ) AS title FROM pp_articles_pages AS ap WHERE ap.article_id IN (" . implode(', ', $placeholders) . ") ORDER BY ap.article_id ASC, ap.o ASC, ap.page_id ASC "; $stmt = $this->db->query($sql, $params); $rows = $stmt ? $stmt->fetchAll() : []; if (!is_array($rows)) { return []; } $titlesByArticle = []; foreach ($rows as $row) { $articleId = (int)($row['article_id'] ?? 0); if ($articleId <= 0) { continue; } $title = trim((string)($row['title'] ?? '')); if ($title === '') { continue; } $titlesByArticle[$articleId][] = $title; } $summary = []; foreach (array_values($normalizedIds) as $articleId) { if (empty($titlesByArticle[$articleId])) { $summary[$articleId] = ''; continue; } $summary[$articleId] = ' - ' . implode(' / ', $titlesByArticle[$articleId]); } return $summary; } public function updateImageAlt(int $imageId, string $imageAlt): bool { $result = $this->db->update('pp_articles_images', [ 'alt' => $imageAlt, ], [ 'id' => $imageId, ]); \Shared\Helpers\Helpers::delete_cache(); return (bool)$result; } public function updateFileName(int $fileId, string $fileName): bool { $result = $this->db->update('pp_articles_files', [ 'name' => $fileName, ], [ 'id' => $fileId, ]); return (bool)$result; } public function markFileToDelete(int $fileId): bool { $result = $this->db->update('pp_articles_files', [ 'to_delete' => 1, ], [ 'id' => $fileId, ]); return (bool)$result; } public function markImageToDelete(int $imageId): bool { $result = $this->db->update('pp_articles_images', [ 'to_delete' => 1, ], [ 'id' => $imageId, ]); return (bool)$result; } private function isCheckedValue($value): bool { if (is_bool($value)) { return $value; } if (is_numeric($value)) { return ((int)$value) === 1; } if (is_string($value)) { $normalized = strtolower(trim($value)); return in_array($normalized, ['1', 'on', 'true', 'yes'], true); } return false; } private function appendDateRangeFilter( array &$where, array &$params, string $column, string $fromKey, string $toKey, array $filters ): void { $from = trim((string)($filters[$fromKey] ?? '')); $to = trim((string)($filters[$toKey] ?? '')); if ($from !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $from)) { $fromParam = ':' . str_replace('.', '_', $column) . '_from'; $where[] = "{$column} >= {$fromParam}"; $params[$fromParam] = $from . ' 00:00:00'; } if ($to !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $to)) { $toParam = ':' . str_replace('.', '_', $column) . '_to'; $where[] = "{$column} <= {$toParam}"; $params[$toParam] = $to . ' 23:59:59'; } } /** * Usuwa nieprzypisane pliki artykulow (article_id = null) wraz z plikami z dysku. */ public function deleteNonassignedFiles(): void { $results = $this->db->select('pp_articles_files', '*', ['article_id' => null]); if (is_array($results)) { foreach ($results as $row) { $this->safeUnlink($row['src']); } } $this->db->delete('pp_articles_files', ['article_id' => null]); } /** * Usuwa nieprzypisane zdjecia artykulow (article_id = null) wraz z plikami z dysku. */ public function deleteNonassignedImages(): void { $results = $this->db->select('pp_articles_images', '*', ['article_id' => null]); if (is_array($results)) { foreach ($results as $row) { $this->safeUnlink($row['src']); } } $this->db->delete('pp_articles_images', ['article_id' => null]); } /** * Usuwa plik z dysku tylko jeśli ścieżka pozostaje wewnątrz katalogu upload/. * Zapobiega path traversal przy danych z bazy. */ private function safeUnlink(string $src): void { $base = realpath('../upload'); if (!$base) { return; } $full = realpath('../' . ltrim($src, '/')); if ($full && strpos($full, $base . DIRECTORY_SEPARATOR) === 0 && is_file($full)) { unlink($full); } elseif ($full) { error_log( '[shopPRO] safeUnlink: ścieżka poza upload/: ' . $src ); } } /** * Pobiera artykuly opublikowane w podanym zakresie dat. */ public function articlesByDateAdd( string $dateStart, string $dateEnd, string $langId = 'pl' ): array { $stmt = $this->db->query( 'SELECT id FROM pp_articles ' . 'WHERE status = 1 ' . 'AND date_add BETWEEN :date_start AND :date_end ' . 'ORDER BY date_add DESC', [':date_start' => $dateStart, ':date_end' => $dateEnd] ); $articles = []; $rows = $stmt ? $stmt->fetchAll( \PDO::FETCH_ASSOC ) : []; if ( is_array( $rows ) ) { foreach ( $rows as $row ) { $articles[] = $this->articleDetailsFrontend( $row['id'], $langId ); } } return $articles; } // ========================================================================= // FRONTEND METHODS (z Redis cache) // ========================================================================= /** * Pobiera szczegoly artykulu dla frontendu (z copy_from fallback + Redis cache). */ public function articleDetailsFrontend(int $articleId, string $langId): ?array { $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::articleDetailsFrontend:{$articleId}:{$langId}"; $objectData = $cacheHandler->get($cacheKey); if ($objectData) { return unserialize($objectData); } $article = $this->db->get('pp_articles', '*', ['id' => $articleId]); if (!$article) { return null; } $langRow = $this->db->get('pp_articles_langs', '*', [ 'AND' => ['article_id' => $articleId, 'lang_id' => $langId] ]); if ($langRow) { if ($langRow['copy_from']) { $copyRow = $this->db->get('pp_articles_langs', '*', [ 'AND' => ['article_id' => $articleId, 'lang_id' => $langRow['copy_from']] ]); $article['language'] = $copyRow ? $copyRow : $langRow; } else { $article['language'] = $langRow; } } $article['images'] = $this->db->select('pp_articles_images', '*', [ 'article_id' => $articleId, 'ORDER' => ['o' => 'ASC', 'id' => 'DESC'] ]); $article['files'] = $this->db->select('pp_articles_files', '*', [ 'article_id' => $articleId ]); $article['pages'] = $this->db->select('pp_articles_pages', 'page_id', [ 'article_id' => $articleId ]); $cacheHandler->set($cacheKey, $article); return $article; } /** * Pobiera ID artykulow ze strony z sortowaniem i paginacja (z Redis cache). */ public function articlesIds(int $pageId, string $langId, int $limit, int $sortType, int $from): ?array { $output = null; switch ($sortType) { case 0: $order = 'date_add ASC'; break; case 1: $order = 'date_add DESC'; break; case 2: $order = 'date_modify ASC'; break; case 3: $order = 'date_modify DESC'; break; case 4: $order = 'o ASC'; break; case 5: $order = 'title ASC'; break; case 6: $order = 'title DESC'; break; default: $order = 'id ASC'; break; } $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::articlesIds:{$pageId}:{$langId}:{$limit}:{$sortType}:{$from}:{$order}"; $objectData = $cacheHandler->get($cacheKey); if ($objectData) { return unserialize($objectData); } $stmt = $this->db->query( 'SELECT * FROM ( ' . 'SELECT ' . 'a.id, date_modify, date_add, o, ' . '( CASE ' . 'WHEN copy_from IS NULL THEN title ' . 'WHEN copy_from IS NOT NULL THEN ( ' . 'SELECT title FROM pp_articles_langs ' . 'WHERE lang_id = al.copy_from AND article_id = a.id ' . ') ' . 'END ) AS title ' . 'FROM ' . 'pp_articles_pages AS ap ' . 'INNER JOIN pp_articles AS a ON a.id = ap.article_id ' . 'INNER JOIN pp_articles_langs AS al ON al.article_id = ap.article_id ' . 'WHERE ' . 'status = 1 AND page_id = ' . (int)$pageId . ' AND lang_id = :lang_id ' . ') AS q1 ' . 'WHERE q1.title IS NOT NULL ' . 'ORDER BY q1.' . $order . ' ' . 'LIMIT ' . (int)$from . ',' . (int)$limit, [':lang_id' => $langId] ); $results = $stmt ? $stmt->fetchAll() : []; if (is_array($results) && !empty($results)) { foreach ($results as $row) { $output[] = $row['id']; } } $cacheHandler->set($cacheKey, $output); return $output; } /** * Zlicza artykuly na stronie (z Redis cache). */ public function pageArticlesCount(int $pageId, string $langId): int { $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::pageArticlesCount:{$pageId}:{$langId}"; $objectData = $cacheHandler->get($cacheKey); if ($objectData) { return (int)unserialize($objectData); } $stmt = $this->db->query( 'SELECT COUNT(0) FROM ( ' . 'SELECT ' . 'a.id, ' . '( CASE ' . 'WHEN copy_from IS NULL THEN title ' . 'WHEN copy_from IS NOT NULL THEN ( ' . 'SELECT title FROM pp_articles_langs ' . 'WHERE lang_id = al.copy_from AND article_id = a.id ' . ') ' . 'END ) AS title ' . 'FROM ' . 'pp_articles_pages AS ap ' . 'INNER JOIN pp_articles AS a ON a.id = ap.article_id ' . 'INNER JOIN pp_articles_langs AS al ON al.article_id = ap.article_id ' . 'WHERE ' . 'status = 1 AND page_id = ' . (int)$pageId . ' AND lang_id = :lang_id ' . ') AS q1 ' . 'WHERE q1.title IS NOT NULL', [':lang_id' => $langId] ); $results = $stmt ? $stmt->fetchAll() : []; $count = isset($results[0][0]) ? (int)$results[0][0] : 0; $cacheHandler->set($cacheKey, $count); return $count; } /** * Pobiera paginowane artykuly ze strony. * * @return array{articles: ?array, ls: int} */ public function pageArticles(array $page, string $langId, int $bs): array { $count = $this->pageArticlesCount((int)$page['id'], $langId); $articlesLimit = (int)($page['articles_limit'] ?: 10); $ls = (int)ceil($count / $articlesLimit); if ($bs < 1) { $bs = 1; } elseif ($bs > $ls) { $bs = $ls; } $from = $articlesLimit * ($bs - 1); if ($from < 0) { $from = 0; } return [ 'articles' => $this->articlesIds((int)$page['id'], $langId, $articlesLimit, (int)($page['sort_type'] ?? 0), $from), 'ls' => $ls, ]; } /** * Pobiera artykuly-aktualnosci ze strony (z sortowaniem wg page_sort). */ public function news(int $pageId, int $limit, string $langId): ?array { $sort = (int)$this->db->get('pp_pages', 'sort_type', ['id' => $pageId]); $articlesIds = $this->articlesIds($pageId, $langId, $limit, $sort, 0); $articles = null; if (is_array($articlesIds) && !empty($articlesIds)) { foreach ($articlesIds as $articleId) { $articles[] = $this->articleDetailsFrontend($articleId, $langId); } } return $articles; } /** * Sprawdza czy artykul ma flage noindex (z Redis cache). */ public function articleNoindex(int $articleId, string $langId): bool { $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::articleNoindex:{$articleId}:{$langId}"; $objectData = $cacheHandler->get($cacheKey); if ($objectData) { return (bool)unserialize($objectData); } $noindex = $this->db->get('pp_articles_langs', 'noindex', [ 'AND' => ['article_id' => $articleId, 'lang_id' => $langId] ]); $cacheHandler->set($cacheKey, $noindex); return (bool)$noindex; } /** * Pobiera najpopularniejsze artykuly ze strony (wg views DESC, z Redis cache). */ public function topArticles(int $pageId, int $limit, string $langId): ?array { return $this->fetchArticlesByPage('topArticles', $pageId, $limit, $langId, 'views DESC'); } /** * Pobiera najnowsze artykuly ze strony (wg date_add DESC, z Redis cache). */ public function newsListArticles(int $pageId, int $limit, string $langId): ?array { return $this->fetchArticlesByPage('newsListArticles', $pageId, $limit, $langId, 'date_add DESC'); } /** * Wspolna logika dla topArticles/newsListArticles (z Redis cache). */ private function fetchArticlesByPage(string $cachePrefix, int $pageId, int $limit, string $langId, string $orderBy): ?array { $cacheHandler = new \Shared\Cache\CacheHandler(); $cacheKey = "ArticleRepository::{$cachePrefix}:{$pageId}:{$limit}:{$langId}"; $objectData = $cacheHandler->get($cacheKey); if (!$objectData) { $stmt = $this->db->query( 'SELECT * FROM ( ' . 'SELECT ' . 'a.id, date_add, views, ' . '( CASE ' . 'WHEN copy_from IS NULL THEN title ' . 'WHEN copy_from IS NOT NULL THEN ( ' . 'SELECT title FROM pp_articles_langs ' . 'WHERE lang_id = al.copy_from AND article_id = a.id ' . ') ' . 'END ) AS title ' . 'FROM ' . 'pp_articles_pages AS ap ' . 'INNER JOIN pp_articles AS a ON a.id = ap.article_id ' . 'INNER JOIN pp_articles_langs AS al ON al.article_id = ap.article_id ' . 'WHERE ' . 'status = 1 AND page_id = ' . (int)$pageId . ' AND lang_id = :lang_id ' . ') AS q1 ' . 'WHERE q1.title IS NOT NULL ' . 'ORDER BY q1.' . $orderBy . ' ' . 'LIMIT 0, ' . (int)$limit, [':lang_id' => $langId] ); $articlesData = $stmt ? $stmt->fetchAll(\PDO::FETCH_ASSOC) : []; $cacheHandler->set($cacheKey, $articlesData); } else { $articlesData = unserialize($objectData); } $articles = null; if (\Shared\Helpers\Helpers::is_array_fix($articlesData)) { foreach ($articlesData as $row) { $articles[] = $this->articleDetailsFrontend((int)$row['id'], $langId); } } return $articles; } }