Articles: finish admin refactor, uploads hardening, and attachment sorting (0.262)

This commit is contained in:
2026-02-13 09:00:24 +01:00
parent e984548516
commit 3e1f417ef3
31 changed files with 1951 additions and 1512 deletions

View File

@@ -37,7 +37,15 @@ class ArticleRepository
'article_id' => $articleId,
'ORDER' => ['o' => 'ASC', 'id' => 'DESC']
]);
$article['files'] = $this->db->select('pp_articles_files', '*', ['article_id' => $articleId]);
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;
@@ -70,6 +78,8 @@ class ArticleRepository
$this->savePages($id, $data['pages'] ?? null, true);
$this->assignTempFiles($id);
$this->assignTempImages($id);
$this->applyGalleryOrderIfProvided($id, $data);
$this->applyFilesOrderIfProvided($id, $data);
\S::htacces();
\S::delete_dir('../temp/');
@@ -87,6 +97,8 @@ class ArticleRepository
$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);
@@ -99,16 +111,16 @@ class ArticleRepository
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,
'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' => ($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,
'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) {
@@ -131,12 +143,32 @@ class ArticleRepository
'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,
'noindex' => $this->isCheckedValue($data['noindex'][$langId] ?? null) ? 1 : 0,
'copy_from' => ($data['copy_from'][$langId] ?? '') != '' ? $data['copy_from'][$langId] : null,
'block_direct_access' => ($data['block_direct_access'][$langId] ?? '') == 'on' ? 1 : 0,
'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'] ?? [];
@@ -573,6 +605,182 @@ class ArticleRepository
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;
}
}
return true;
}
/**
* Zwraca mape: article_id => etykieta stron (np. " - Strona A / Strona B").
*
* @param array<int, int> $articleIds
* @return array<int, string>
*/
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,
]);
\S::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,

View File

@@ -4,6 +4,10 @@ namespace admin\Controllers;
use Domain\Article\ArticleRepository;
use Domain\Languages\LanguagesRepository;
use Domain\Layouts\LayoutsRepository;
use admin\ViewModels\Forms\FormAction;
use admin\ViewModels\Forms\FormEditViewModel;
use admin\ViewModels\Forms\FormField;
use admin\ViewModels\Forms\FormTab;
class ArticlesController
{
@@ -61,12 +65,21 @@ class ArticlesController
$listRequest['perPage']
);
$articleIds = [];
foreach ($result['items'] as $item) {
$id = (int)($item['id'] ?? 0);
if ($id > 0) {
$articleIds[] = $id;
}
}
$pagesSummary = $this->repository->pagesSummaryForArticles($articleIds);
$rows = [];
$lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1;
foreach ($result['items'] as $item) {
$id = (int)$item['id'];
$title = (string)($item['title'] ?? '');
$pages = (string)\admin\factory\Articles::article_pages($id);
$pages = (string)($pagesSummary[$id] ?? '');
$rows[] = [
'lp' => $lp++ . '.',
@@ -146,6 +159,18 @@ class ArticlesController
exit;
}
/**
* Zapis kolejnosci zalacznikow (AJAX)
*/
public function filesOrderSave(): void
{
if ($this->repository->saveFilesOrder((int)\S::get('article_id'), (string)\S::get('order'))) {
echo json_encode(['status' => 'ok', 'msg' => 'Artykul zostal zapisany.']);
}
exit;
}
/**
* Zapis artykulu (AJAX)
*/
@@ -153,11 +178,72 @@ class ArticlesController
{
global $user;
$values = json_decode(\S::get('values'), true);
$response = ['status' => 'error', 'msg' => 'Podczas zapisywania artykulu wystapil blad. Prosze sprobowac ponownie.'];
$values = $this->resolveSavePayload();
$articleId = (int)($values['id'] ?? \S::get('id') ?? 0);
$id = $this->repository->save($articleId, $values, (int)$user['id']);
if ($id = $this->repository->save((int)($values['id'] ?? 0), $values, (int)$user['id'])) {
$response = ['status' => 'ok', 'msg' => 'Artykul zostal zapisany.', 'id' => $id];
if ($id) {
echo json_encode([
'success' => true,
'status' => 'ok',
'message' => 'Artykul zostal zapisany.',
'msg' => 'Artykul zostal zapisany.',
'id' => (int)$id,
]);
exit;
}
echo json_encode([
'success' => false,
'status' => 'error',
'message' => 'Podczas zapisywania artykulu wystapil blad. Prosze sprobowac ponownie.',
'msg' => 'Podczas zapisywania artykulu wystapil blad. Prosze sprobowac ponownie.',
]);
exit;
}
public function imageAltChange(): void
{
$response = ['status' => 'error', 'msg' => 'Podczas zmiany atrybutu alt zdjecia wystapil blad. Prosze sprobowac ponownie.'];
if ($this->repository->updateImageAlt((int)\S::get('image_id'), (string)\S::get('image_alt'))) {
$response = ['status' => 'ok'];
}
echo json_encode($response);
exit;
}
public function fileNameChange(): void
{
$response = ['status' => 'error', 'msg' => 'Podczas zmiany nazwy zalacznika wystapil blad. Prosze sprobowac ponownie.'];
if ($this->repository->updateFileName((int)\S::get('file_id'), (string)\S::get('file_name'))) {
$response = ['status' => 'ok'];
}
echo json_encode($response);
exit;
}
public function imageDelete(): void
{
$response = ['status' => 'error', 'msg' => 'Podczas usuwania zdjecia wystapil blad. Prosze sprobowac ponownie.'];
if ($this->repository->markImageToDelete((int)\S::get('image_id'))) {
$response = ['status' => 'ok'];
}
echo json_encode($response);
exit;
}
public function fileDelete(): void
{
$response = ['status' => 'error', 'msg' => 'Podczas usuwania zalacznika wystapil blad. Prosze sprobowac ponownie.'];
if ($this->repository->markFileToDelete((int)\S::get('file_id'))) {
$response = ['status' => 'ok'];
}
echo json_encode($response);
@@ -192,12 +278,226 @@ class ArticlesController
$this->repository->deleteNonassignedImages();
$this->repository->deleteNonassignedFiles();
$article = $this->repository->find((int)\S::get('id')) ?: ['id' => 0, 'languages' => [], 'images' => [], 'files' => [], 'pages' => []];
$languages = $this->languagesRepository->languagesList();
$menus = \admin\factory\Pages::menus_list();
$layouts = $this->layoutsRepository->listAll();
$viewModel = $this->buildFormViewModel($article, $languages, $menus, $layouts);
return \Tpl::view('articles/article-edit', [
'article' => $this->repository->find((int)\S::get('id')),
'menus' => \admin\factory\Pages::menus_list(),
'languages' => $this->languagesRepository->languagesList(),
'layouts' => $this->layoutsRepository->listAll(),
'user' => $user
'form' => $viewModel,
'article' => $article,
'user' => $user,
]);
}
private function resolveSavePayload(): array
{
$legacyValuesRaw = \S::get('values');
if ($legacyValuesRaw !== null && $legacyValuesRaw !== '') {
$legacyValues = json_decode((string)$legacyValuesRaw, true);
if (is_array($legacyValues)) {
return $legacyValues;
}
}
$payload = $_POST;
unset($payload['_form_id']);
return is_array($payload) ? $payload : [];
}
private function buildFormViewModel(array $article, array $languages, array $menus, array $layouts): FormEditViewModel
{
$articleId = (int)($article['id'] ?? 0);
$defaultLanguageId = (string)($this->languagesRepository->defaultLanguageId() ?? 'pl');
$title = $articleId > 0
? 'Edycja artykulu: <u>' . $this->escapeHtml((string)($article['languages'][$defaultLanguageId]['title'] ?? '')) . '</u>'
: 'Edycja artykulu';
$layoutOptions = ['' => '---- szablon domyslny ----'];
foreach ($layouts as $layout) {
$layoutOptions[(string)$layout['id']] = (string)$layout['name'];
}
$copyFromOptions = ['' => '---- wersja jezykowa ----'];
foreach ($languages as $language) {
if (!empty($language['id'])) {
$copyFromOptions[(string)$language['id']] = (string)$language['name'];
}
}
$tabs = [
new FormTab('content', 'Tresc', 'fa-file'),
new FormTab('settings', 'Ustawienia', 'fa-wrench'),
new FormTab('seo', 'SEO', 'fa-globe'),
new FormTab('display', 'Wyswietlanie', 'fa-share-alt'),
new FormTab('gallery', 'Galeria', 'fa-file-image-o'),
new FormTab('files', 'Zalaczniki', 'fa-file-archive-o'),
];
$fields = [
FormField::hidden('id', $articleId),
FormField::langSection('article_content', 'content', [
FormField::select('copy_from', [
'label' => 'Wyswietlaj tresc z wersji',
'options' => $copyFromOptions,
]),
FormField::text('title', [
'label' => 'Tytul',
'required' => true,
'attributes' => ['id' => 'title'],
]),
FormField::image('main_image', [
'label' => 'Zdjecie tytulowe',
'filemanager' => true,
'attributes' => ['id' => 'main_image'],
]),
FormField::editor('entry', [
'label' => 'Wstep',
'toolbar' => 'MyToolbar',
'height' => 250,
'attributes' => ['id' => 'entry'],
]),
FormField::editor('text', [
'label' => 'Tresc',
'toolbar' => 'MyToolbar',
'height' => 250,
'attributes' => ['id' => 'text'],
]),
FormField::editor('table_of_contents', [
'label' => 'Spis tresci',
'toolbar' => 'MyToolbar',
'height' => 250,
'attributes' => ['id' => 'table_of_contents'],
]),
]),
FormField::switch('status', ['label' => 'Opublikowany', 'tab' => 'settings', 'value' => ((int)($article['status'] ?? 0) === 1) || $articleId === 0]),
FormField::switch('show_title', ['label' => 'Pokaz tytul', 'tab' => 'settings', 'value' => (int)($article['show_title'] ?? 0) === 1]),
FormField::switch('show_table_of_contents', ['label' => 'Pokaz spis tresci', 'tab' => 'settings', 'value' => (int)($article['show_table_of_contents'] ?? 0) === 1]),
FormField::switch('show_date_add', ['label' => 'Pokaz date dodania', 'tab' => 'settings', 'value' => (int)($article['show_date_add'] ?? 0) === 1]),
FormField::switch('show_date_modify', ['label' => 'Pokaz date modyfikacji', 'tab' => 'settings', 'value' => (int)($article['show_date_modify'] ?? 0) === 1]),
FormField::switch('repeat_entry', ['label' => 'Powtorz wprowadzenie', 'tab' => 'settings', 'value' => (int)($article['repeat_entry'] ?? 0) === 1]),
FormField::switch('social_icons', ['label' => 'Linki do portali spolecznosciowych', 'tab' => 'settings', 'value' => (int)($article['social_icons'] ?? 0) === 1]),
FormField::langSection('article_seo', 'seo', [
FormField::text('seo_link', ['label' => 'Link SEO', 'attributes' => ['id' => 'seo_link']]),
FormField::text('meta_title', ['label' => 'Meta title']),
FormField::textarea('meta_description', ['label' => 'Meta description', 'rows' => 4]),
FormField::textarea('meta_keywords', ['label' => 'Meta keywords', 'rows' => 4]),
FormField::switch('noindex', ['label' => 'Blokuj indeksacje']),
FormField::switch('block_direct_access', ['label' => 'Blokuj bezposredni dostep']),
]),
FormField::select('layout_id', [
'label' => 'Szablon',
'tab' => 'display',
'options' => $layoutOptions,
'value' => $article['layout_id'] ?? '',
]),
FormField::custom('pages_tree', $this->renderPagesTree($menus, $article), ['tab' => 'display']),
FormField::custom('images_box', $this->renderImagesBox($article), ['tab' => 'gallery']),
FormField::custom('files_box', $this->renderFilesBox($article), ['tab' => 'files']),
];
$actions = [
FormAction::save('/admin/articles/article_save/' . ($articleId > 0 ? 'id=' . $articleId : ''), '/admin/articles/view_list/'),
FormAction::cancel('/admin/articles/view_list/'),
];
return new FormEditViewModel(
'article-edit',
$title,
$article,
$fields,
$tabs,
$actions,
'POST',
'/admin/articles/article_save/' . ($articleId > 0 ? 'id=' . $articleId : ''),
'/admin/articles/view_list/',
true,
['id' => $articleId],
$languages
);
}
private function renderPagesTree(array $menus, array $article): string
{
$html = '<div class="form-group row">';
$html .= '<label class="col-lg-4 control-label">Wyswietlaj na:</label>';
$html .= '<div class="col-lg-8">';
foreach ($menus as $menu) {
$menuId = (int)($menu['id'] ?? 0);
$menuName = $this->escapeHtml((string)($menu['name'] ?? ''));
$menuStatus = (int)($menu['status'] ?? 0);
$menuPages = \admin\factory\Pages::menu_pages($menuId);
$html .= '<div class="menu_sortable">';
$html .= '<ol class="sortable" id="sortable_' . $menuId . '">';
$html .= '<li id="list_' . $menuId . '" class="menu_' . $menuId . '" menu="' . $menuId . '">';
$html .= '<div class="context_0 content content_menu"' . ($menuStatus ? '' : ' style="color: #cc0000;"') . '>';
$html .= '<span class="disclose"><span></span></span>Menu: <b>' . $menuName . '</b>';
$html .= '</div>';
$html .= \Tpl::view('articles/subpages-list', [
'pages' => $menuPages,
'article_pages' => $article['pages'] ?? [],
'parent_id' => $menuId,
'step' => 1,
]);
$html .= '</li></ol></div>';
}
$html .= '</div><div class="clear"></div></div>';
return $html;
}
private function renderImagesBox(array $article): string
{
$html = '<ul id="images-list">';
$images = is_array($article['images'] ?? null) ? $article['images'] : [];
foreach ($images as $img) {
$id = (int)($img['id'] ?? 0);
$src = $this->escapeHtml((string)($img['src'] ?? ''));
$alt = $this->escapeHtml((string)($img['alt'] ?? ''));
$html .= '<li id="image-' . $id . '">';
$html .= '<img class="article-image lozad" data-src="/libraries/thumb.php?img=' . $src . '&w=300&h=300">';
$html .= '<a href="#" class="input-group-addon btn btn-danger article_image_delete" image-id="' . $id . '"><i class="fa fa-trash"></i></a>';
$html .= '<input type="text" class="form-control image-alt" value="' . $alt . '" image-id="' . $id . '" placeholder="atrybut alt...">';
$html .= '</li>';
}
$html .= '</ul><div id="images-uploader">You browser doesn\'t have Flash installed.</div>';
return $html;
}
private function renderFilesBox(array $article): string
{
$html = '<ul id="files-list">';
$files = is_array($article['files'] ?? null) ? $article['files'] : [];
foreach ($files as $file) {
$id = (int)($file['id'] ?? 0);
$src = (string)($file['src'] ?? '');
$name = trim((string)($file['name'] ?? ''));
if ($name === '') {
$parts = explode('/', $src);
$name = (string)end($parts);
}
$name = $this->escapeHtml($name);
$html .= '<li id="file-' . $id . '"><div class="input-group">';
$html .= '<input type="text" class="article_file_edit form-control" file_id="' . $id . '" value="' . $name . '" />';
$html .= '<a href="#" class="input-group-addon btn btn-info article_file_delete" file_id="' . $id . '"><i class="fa fa-trash"></i></a>';
$html .= '</div></li>';
}
$html .= '</ul><div id="files-uploader">You browser doesn\'t have Flash installed.</div>';
return $html;
}
private function escapeHtml(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
}

View File

@@ -201,7 +201,7 @@ class FormFieldRenderer
'name' => $field->name,
'id' => $field->id,
'value' => $value ?? '',
'options' => $field->options,
'values' => $field->options,
'class' => ($field->required ? 'require ' : '') . ($field->attributes['class'] ?? ''),
];
@@ -308,6 +308,11 @@ class FormFieldRenderer
'value="' . htmlspecialchars($value ?? '') . '">';
}
public function renderCustom(FormField $field): string
{
return (string)($field->customHtml ?? '');
}
/**
* Renderuje sekcję językową
*/
@@ -388,6 +393,16 @@ class FormFieldRenderer
'id' => $id,
'checked' => (bool) $value,
]);
case FormFieldType::SELECT:
return $this->wrapWithError(\Html::select([
'label' => $field->label,
'name' => $name,
'id' => $id,
'value' => $value ?? '',
'values' => $field->options,
'class' => ($field->required ? 'require ' : '') . ($field->attributes['class'] ?? ''),
]), $error);
default: // TEXT, URL, etc.
return $this->wrapWithError(\Html::input([

View File

@@ -29,6 +29,7 @@ class FormField
// Specyficzne dla lang_section
public ?array $langFields;
public ?string $langSectionParentTab;
public ?string $customHtml;
/**
* @param string $name Nazwa pola (name)
@@ -64,7 +65,8 @@ class FormField
string $editorToolbar = 'MyTool',
int $editorHeight = 300,
?array $langFields = null,
?string $langSectionParentTab = null
?string $langSectionParentTab = null,
?string $customHtml = null
) {
$this->name = $name;
$this->type = $type;
@@ -82,6 +84,7 @@ class FormField
$this->editorHeight = $editorHeight;
$this->langFields = $langFields;
$this->langSectionParentTab = $langSectionParentTab;
$this->customHtml = $customHtml;
$this->id = $attributes['id'] ?? $name;
}
@@ -276,6 +279,29 @@ class FormField
);
}
public static function custom(string $name, string $html, array $config = []): self
{
return new self(
$name,
FormFieldType::CUSTOM,
$config['label'] ?? '',
null,
$config['tab'] ?? 'default',
false,
$config['attributes'] ?? [],
[],
null,
null,
false,
null,
'MyTool',
300,
null,
null,
$html
);
}
/**
* Sekcja językowa - grupa pól powtarzana dla każdego języka
*

View File

@@ -20,4 +20,5 @@ class FormFieldType
public const FILE = 'file';
public const HIDDEN = 'hidden';
public const LANG_SECTION = 'lang_section';
public const CUSTOM = 'custom';
}

View File

@@ -330,11 +330,16 @@ class Site
*/
private static $actionMap = [
'gallery_order_save' => 'galleryOrderSave',
'files_order_save' => 'filesOrderSave',
'view_list' => 'list',
'article_edit' => 'edit',
'article_save' => 'save',
'article_delete' => 'delete',
'article_restore' => 'restore',
'article_image_alt_change' => 'imageAltChange',
'article_file_name_change' => 'fileNameChange',
'article_image_delete' => 'imageDelete',
'article_file_delete' => 'fileDelete',
'banner_edit' => 'edit',
'banner_save' => 'save',
'banner_delete' => 'delete',

View File

@@ -1,22 +0,0 @@
<?php
namespace admin\view;
class Articles
{
public static function subpages_list( $pages, $article_pages, $parent_id = 0, $step = 1 )
{
$tpl = new \Tpl();
$tpl -> pages = $pages;
$tpl -> parent_id = $parent_id;
$tpl -> step = $step;
$tpl -> article_pages = $article_pages;
return $tpl -> render( 'articles/subpages-list' );
}
public static function articles_list()
{
$tpl = new \Tpl;
return $tpl -> render( 'articles/articles-list' );
}
}
?>

View File

@@ -3,7 +3,8 @@ class ImageManipulator
{
protected int $width;
protected int $height;
protected \GdImage $image;
/** @var resource|\GdImage */
protected $image;
protected ?string $file = null;
/**
@@ -37,7 +38,7 @@ class ImageManipulator
throw new InvalidArgumentException("Image file $file is not readable");
}
if (isset($this->image) && $this->image instanceof \GdImage) {
if (isset($this->image) && $this->isValidImageResource($this->image)) {
imagedestroy($this->image);
}
@@ -66,7 +67,7 @@ class ImageManipulator
throw new InvalidArgumentException("Image type $type not supported");
}
if (!$this->image instanceof \GdImage) {
if (!$this->isValidImageResource($this->image)) {
throw new InvalidArgumentException("Failed to create image from $file");
}
@@ -91,12 +92,12 @@ class ImageManipulator
*/
public function setImageString(string $data): self
{
if (isset($this->image) && $this->image instanceof \GdImage) {
if (isset($this->image) && $this->isValidImageResource($this->image)) {
imagedestroy($this->image);
}
$image = imagecreatefromstring($data);
if (!$image instanceof \GdImage) {
if (!$this->isValidImageResource($image)) {
throw new RuntimeException('Cannot create image from data string');
}
@@ -124,7 +125,7 @@ class ImageManipulator
*/
public function resample(int $width, int $height, bool $constrainProportions = true): self
{
if (!isset($this->image) || !$this->image instanceof \GdImage) {
if (!isset($this->image) || !$this->isValidImageResource($this->image)) {
throw new RuntimeException('No image set');
}
@@ -169,7 +170,7 @@ class ImageManipulator
*/
public function enlargeCanvas(int $width, int $height, array $rgb = [], ?int $xpos = null, ?int $ypos = null): self
{
if (!isset($this->image) || !$this->image instanceof \GdImage) {
if (!isset($this->image) || !$this->isValidImageResource($this->image)) {
throw new RuntimeException('No image set');
}
@@ -177,7 +178,7 @@ class ImageManipulator
$height = max($height, $this->height);
$temp = imagecreatetruecolor($width, $height);
if (!$temp instanceof \GdImage) {
if (!$this->isValidImageResource($temp)) {
throw new RuntimeException('Failed to create a new image for enlarging canvas');
}
@@ -233,7 +234,7 @@ class ImageManipulator
*/
public function crop($x1, int $y1 = 0, int $x2 = 0, int $y2 = 0): self
{
if (!isset($this->image) || !$this->image instanceof \GdImage) {
if (!isset($this->image) || !$this->isValidImageResource($this->image)) {
throw new RuntimeException('No image set');
}
@@ -257,7 +258,7 @@ class ImageManipulator
}
$temp = imagecreatetruecolor($cropWidth, $cropHeight);
if (!$temp instanceof \GdImage) {
if (!$this->isValidImageResource($temp)) {
throw new RuntimeException('Failed to create a new image for cropping');
}
@@ -286,17 +287,17 @@ class ImageManipulator
/**
* Replace current image resource with a new one
*
* @param \GdImage $res New image resource
* @param resource|\GdImage $res New image resource
* @return self
* @throws UnexpectedValueException
*/
protected function _replace(\GdImage $res): self
protected function _replace($res): self
{
if (!$res instanceof \GdImage) {
if (!$this->isValidImageResource($res)) {
throw new UnexpectedValueException('Invalid image resource');
}
if (isset($this->image) && $this->image instanceof \GdImage) {
if (isset($this->image) && $this->isValidImageResource($this->image)) {
imagedestroy($this->image);
}
@@ -386,9 +387,9 @@ class ImageManipulator
/**
* Returns the GD image resource
*
* @return \GdImage
* @return resource|\GdImage
*/
public function getResource(): \GdImage
public function getResource()
{
return $this->image;
}
@@ -418,8 +419,22 @@ class ImageManipulator
*/
public function __destruct()
{
if (isset($this->image) && $this->image instanceof \GdImage) {
if (isset($this->image) && $this->isValidImageResource($this->image)) {
imagedestroy($this->image);
}
}
/**
* Compatibility helper for PHP 7.4 (resource) and PHP 8+ (GdImage).
*
* @param mixed $image
*/
private function isValidImageResource($image): bool
{
if (is_resource($image)) {
return true;
}
return class_exists('GdImage', false) && $image instanceof \GdImage;
}
}