Move article save/delete logic from monolithic factory to ArticleRepository with DI-based controller actions, following the established refactoring pattern. - ArticleRepository: add save() with 9 private helpers, archive() method - ArticlesController: add save() and delete() actions with DI - Factory methods delegate to repository (backward compatibility) - Router: add article_save/article_delete action mappings - Old controls methods marked @deprecated - 59 tests, 123 assertions passing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
366 lines
12 KiB
PHP
366 lines
12 KiB
PHP
<?php
|
|
namespace Domain\Article;
|
|
|
|
/**
|
|
* Repository odpowiedzialny za dostep do danych artykulow
|
|
*/
|
|
class ArticleRepository
|
|
{
|
|
private $db;
|
|
|
|
public function __construct($db)
|
|
{
|
|
$this->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;
|
|
}
|
|
|
|
/**
|
|
* 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]);
|
|
}
|
|
}
|