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'] ]); $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); \S::htacces(); \S::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->deleteMarkedImages($articleId); $this->deleteMarkedFiles($articleId); \S::htacces(); \S::delete_dir('../temp/'); return $articleId; } private function buildArticleRow(array $data, int $userId, bool $isNew): array { $row = [ 'show_title' => ($data['show_title'] ?? '') == 'on' ? 1 : 0, 'show_date_add' => ($data['show_date_add'] ?? '') == 'on' ? 1 : 0, 'show_date_modify' => ($data['show_date_modify'] ?? '') == 'on' ? 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' => ($data['status'] ?? '') == 'on' ? 1 : 0, 'repeat_entry' => ($data['repeat_entry'] ?? '') == 'on' ? 1 : 0, 'social_icons' => ($data['social_icons'] ?? '') == 'on' ? 1 : 0, 'show_table_of_contents' => ($data['show_table_of_contents'] ?? '') == 'on' ? 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' => \S::seo($data['seo_link'][$langId] ?? '') != '' ? \S::seo($data['seo_link'][$langId]) : null, 'noindex' => ($data['noindex'][$langId] ?? '') == 'on' ? 1 : 0, 'copy_from' => ($data['copy_from'][$langId] ?? '') != '' ? $data['copy_from'][$langId] : null, 'block_direct_access' => ($data['block_direct_access'][$langId] ?? '') == 'on' ? 1 : 0, ]; } 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) { if (file_exists('../' . $row['src'])) { unlink('../' . $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) { if (file_exists('../' . $row['src'])) { unlink('../' . $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]); return (bool)$result; } /** * 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, ]; } /** * 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, ], ]); } return true; } 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) { if (file_exists('../' . $row['src'])) { unlink('../' . $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) { if (file_exists('../' . $row['src'])) { unlink('../' . $row['src']); } } } $this->db->delete('pp_articles_images', ['article_id' => null]); } }