From b45d5b9fafba7267cbe4bc3d3c37d04d6d82f22d Mon Sep 17 00:00:00 2001 From: Gregor Harlan Date: Sun, 12 Apr 2026 20:25:45 +0200 Subject: [PATCH 1/2] refactor: remove backend pages for template/module/action management Templates, modules, and actions are no longer managed via the backend UI. They will be defined as PHP classes in the project and addon code instead. Co-Authored-By: Claude Opus 4.6 (1M context) --- .tools/psalm/baseline.xml | 146 --------- boot/core.php | 14 - lang/de_de.lang | 94 ------ lang/en_gb.lang | 94 ------ pages/module/actions.php | 528 -------------------------------- pages/module/index.php | 9 - pages/module/modules.php | 477 ----------------------------- pages/template/index.php | 602 ------------------------------------- src/Backend/Controller.php | 16 - 9 files changed, 1980 deletions(-) delete mode 100644 pages/module/actions.php delete mode 100644 pages/module/index.php delete mode 100644 pages/module/modules.php delete mode 100644 pages/template/index.php diff --git a/.tools/psalm/baseline.xml b/.tools/psalm/baseline.xml index fb867fb2a4..e72bd57530 100644 --- a/.tools/psalm/baseline.xml +++ b/.tools/psalm/baseline.xml @@ -185,7 +185,6 @@ - getParam('file')]]> @@ -1200,117 +1199,6 @@ - - - - $sql->getValue('id'), 'function' => 'delete'] + $csrfToken->getUrlParams()]]> - $sql->getValue('id'), 'function' => 'edit']]]> - $sql->getValue('id'), 'function' => 'edit']]]> - $sql->getValue('id'), 'function' => 'edit']]]> - 'edit', 'module_id' => $del->getValue('ma.module_id')]]]> - - - getValue('postsavemode')]]> - getValue('presavemode')]]> - getValue('previewmode')]]> - - - - - - - - - - - - - - - - - - - - - getValue('m.name'))]]> - - - - - getValue('name'))]]> - getValue('name'))]]> - - - - - - - - - - - - - - - - - getValue('postsavemode')]]> - getValue('presavemode')]]> - getValue('previewmode')]]> - - - - - - - - getValue('id')]]> - getValue('name')]]> - getValue(Core::getTablePrefix() . 'article.id')]]> - getValue('name')]]> - getValue('name')]]> - - $actionId, 'function' => 'edit']]]> - $aid, 'clang' => $clangId, 'ctype' => $ctype]]]> - $moduleId, 'function_action' => 'delete', 'function' => 'edit', 'iaction_id' => $iactionId] + $csrfToken->getUrlParams()]]> - - - getValue('id')]]]> - - - - - - - - - - - - - - - - getValue('id')]]> - getValue('name')]]> - getValue(Core::getTablePrefix() . 'article.id')]]> - getValue('name')]]> - getValue('name')]]> - - - - - - - - - - - - - @@ -1603,40 +1491,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/boot/core.php b/boot/core.php index ba21db9799..776dbc28bc 100644 --- a/boot/core.php +++ b/boot/core.php @@ -160,20 +160,6 @@ Core::setProperty('article_id', $articleId); } - Extension::register('EDITOR_URL', static function (ExtensionPoint $ep) { - $urls = [ - 'template' => ['templates', 'template_id'], - 'module' => ['modules/modules', 'module_id'], - 'action' => ['modules/actions', 'action_id'], - ]; - - if (preg_match('@^rex:///(template|module|action)/(\d+)@', $ep->getParam('file'), $match)) { - return Url::backendPage($urls[$match[1]][0], ['function' => 'edit', $urls[$match[1]][1] => $match[2]]); - } - - return null; - }); - if (Core::getConfig('article_history', false)) { Extension::register( ['ART_SLICES_COPY', 'SLICE_ADD', 'SLICE_UPDATE', 'SLICE_MOVE', 'SLICE_DELETE'], diff --git a/lang/de_de.lang b/lang/de_de.lang index c3d1d87942..90f1a207f4 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -1395,106 +1395,12 @@ perm_options_moveSlice[] = Blöcke verschieben perm_options_publishSlice[] = Blöcke veröffentlichen templates = Templates - -templates_not_found = Es wurden keine Templates gefunden -title_templates = Templates -cant_delete_template_because_its_in_use = Template {0} kann nicht gelöscht werden, da es noch von folgenden Artikeln genutzt wird: -cant_delete_template_because_its_default_template = Template {0} kann nicht gelöscht werden, da es als Standardtemplate festgelegt ist. -cant_inactivate_template_because_its_in_use = Template {0} kann nicht inaktiv gesetzt werden, da es noch von folgenden Artikeln genutzt wird: -cant_inactivate_template_because_its_default_template = Template {0} kann nicht inaktiv gesetzt werden, da es als Standardtemplate festgelegt ist. -template_deleted = Template wurde gelöscht. -template_added = Template wurde hinzugefügt -template_updated = Template wurde aktualisiert -template_categories = Kategorieberechtigungen -template_categories_all = In allen Kategorien verfügbar -template_categories_custom = Nur in folgenden Kategorien verfügbar -edit_template = Template editieren -create_template = Neues Template erstellen -template_name = Name -template_key = Schlüssel -template_key_notice = Der Schlüssel sollte eindeutig vergeben oder nicht ausgefüllt werden. -template_key_exists = Der Schlüssel existiert bereits. -checkbox_template_active = Aktiv -checkbox_template_active_info = [Erscheint bei den Artikeln als Auswahl] -save_template_and_quit = Template speichern -save_template_and_continue = Template übernehmen header_template = Template -header_template_description = Templatebezeichnung -header_template_key = Schlüssel -header_template_active = Aktiv -header_template_functions = Funktionen -delete_template = Template löschen -header_template_caption = Liste der verfügbaren Templates -modules_available = Nur folgende Module sind verfügbar -modules_available_all = Alle Module verfügbar -confirm_delete_template = Template löschen? modules = Module module = Modul module_not_found = Modul wurde nicht gefunden -modules_not_found = Es wurden keine Module gefunden -module_cannot_be_deleted = Modul {0} kann nicht gelöscht werden, da es noch von folgenden Artikeln genutzt wird: -module_deleted = Modul wurde gelöscht! -module_added = Modul wurde hinzugefügt -module_updated = Modul wurde aktualisiert -module_caption = Liste der angelegten Module -module_in_use = In Verwendung -articel_updated = Artikel wurden aktualisiert -create_module = Neues Modul erstellen -input = Eingabe -output = Ausgabe -module_name = Name -module_key = Schlüssel -module_key_notice = Der Schlüssel sollte eindeutig vergeben oder nicht ausgefüllt werden. -module_key_exists = Der Schlüssel existiert bereits. -module_actions_notice = Tipp: Mit Aktionen kann zusätzlich PHP-Code ausgeführt werden. Zum Beispiel, um Moduleingaben zu validieren. -save_module_and_quit = Modul speichern -save_module_and_continue = Modul übernehmen -module_description = Modulbezeichnung -module_functions = Funktionen -delete_module = Modul löschen -module_edit = Modul editieren -action_taken = Aktion wurde übernommen -action_deleted_from_modul = Aktion wurde aus dem Modul gelöscht! -action = Aktion -actions = Aktionen -action_cannot_be_deleted = Aktion {0} kann nicht gelöscht werden, da es noch von folgenden Modulen genutzt wird: -action_deleted = Aktion wurde gelöscht -action_added = Aktion wurde hinzugefügt -action_updated = Aktion wurde aktualisiert -action_create = Aktion erstellen -action_name = Aktionsname -action_key = Schlüssel -action_key_notice = Der Schlüssel sollte eindeutig vergeben oder nicht ausgefüllt werden. -action_key_exists = Der Schlüssel existiert bereits. -header_action_key = Schlüssel -save_action_and_quit = Aktion speichern -save_action_and_continue = Aktion übernehmen -action_functions = Funktionen -action_event_all = Für alle Events aktiviert -action_event_edit = Beim Bearbeiten des Moduls -action_event_delete = Beim Löschen des Moduls -action_event_add = Beim Hinzufügen des Moduls -action_event = zugewiesene(s) Event(s) -action_mode_preview = Wird vor dem Anzeigen des Moduls ausgeführt -action_mode_presave = Wird vor dem Speichern des Moduls ausgeführt -action_mode_postsave = Wird nach dem Speichern des Moduls ausgeführt -action_delete = Aktion löschen -action_edit = Aktion bearbeiten -action_add = Aktion hinzufügen -action_hint = Alle Werte sind in {0} verfügbar -action_caption = Liste der angelegten Aktionen -actions_added_caption = Liste der zugewiesenen Aktionen -actions_not_found = Es wurden keine Aktionen gefunden all_modules = Alle Module -confirm_delete_module = Modul löschen? -confirm_delete_action = Aktion löschen? -action_header_preview = Preview-Event(s) -action_header_presave = Presave-Event(s) -action_header_postsave = Postsave-Event(s) -action_heading_preview = Preview-Action -action_heading_presave = Presave-Action -action_heading_postsave = Postsave-Action diff --git a/lang/en_gb.lang b/lang/en_gb.lang index 57885bbf14..7c56188bb3 100644 --- a/lang/en_gb.lang +++ b/lang/en_gb.lang @@ -1358,106 +1358,12 @@ perm_options_moveSlice[] = Move block perm_options_publishSlice[] = Publish block templates = Templates - -templates_not_found = Templates not found -title_templates = Templates -cant_delete_template_because_its_in_use = Template '{0}' cannot be deleted because it is used by following articles: -cant_delete_template_because_its_default_template = Template '{0}' cannot be deleted because it its the default template. -cant_inactivate_template_because_its_in_use = Template '{0}' cannot be inactivated because it is used by following articles: -cant_inactivate_template_because_its_default_template = Template '{0}' cannot be inactivated because it its the default template. -template_deleted = Template deleted. -template_added = Template added. -template_updated = Template updated. -template_categories = Category rights -template_categories_all = Availiable in all categories -template_categories_custom = Only available in the following categories -edit_template = Edit template -create_template = Create template -template_name = Name -template_key = Key -template_key_notice = The key should be uniquely assigned or not filled in. -template_key_exists = The key already exists. -checkbox_template_active = Active -checkbox_template_active_info = [Selectable for articles] -save_template_and_quit = Save template -save_template_and_continue = Update template header_template = Template -header_template_description = Template description -header_template_key = Key -header_template_active = Active -header_template_functions = Functions -delete_template = Delete template -header_template_caption = List of available templates -modules_available = Only following Modules are available -modules_available_all = All Modules are available -confirm_delete_template = Delete template? modules = Modules module = Modules module_not_found = Error! Module not found. -modules_not_found = Modules not found -module_cannot_be_deleted = Module '{0}' not deleted, because it is used by following articles: -module_deleted = Module deleted! -module_added = Module added -module_updated = Module updated -module_caption = List of available modules -module_in_use = In use -articel_updated = Articles updated -create_module = Create module -input = Input -output = Output -module_name = Name -module_key = Key -module_key_notice = Keys should be unique or left empty -module_key_exists = Key already exists. -module_actions_notice = Tip: Using Actions you might validate user input or trigger custom php scripts -save_module_and_quit = Save module -save_module_and_continue = Update module -module_description = Module description -module_functions = Functions -delete_module = Delete module -module_edit = Edit module -action_taken = Action taken -action_deleted_from_modul = Action deleted from module! -action = Action -actions = Actions -action_cannot_be_deleted = Action '{0}' cannot be deleted, following modules use it: -action_deleted = Action deleted -action_added = Action added -action_updated = Action updated -action_create = Create action -action_name = Action name -action_key = Key -action_key_notice = The key should be unique or left empty. -action_key_exists = The key already exists. -header_action_key = Key -save_action_and_quit = Save action -save_action_and_continue = Update action -action_functions = Functions -action_event_all = Active for all events -action_event_edit = editing the module -action_event_delete = deleting the module -action_event_add = adding the module -action_event = assigned event(s) -action_mode_preview = Executed before previewing the module -action_mode_presave = Executed before saving the module -action_mode_postsave = Executed after saving the module -action_delete = Delete action -action_edit = Edit action -action_add = Add action -action_hint = All values are available in {0} -action_caption = List of available actions -actions_added_caption = List of assigned actions -actions_not_found = No actions found all_modules = All modules -confirm_delete_module = Delete module? -confirm_delete_action = Delete action? -action_header_preview = Preview-Event(s) -action_header_presave = Presave-Event(s) -action_header_postsave = Postsave-Event(s) -action_heading_preview = Preview-Action -action_heading_presave = Presave-Action -action_heading_postsave = Postsave-Action structure_history = History diff --git a/pages/module/actions.php b/pages/module/actions.php deleted file mode 100644 index 48cbadf067..0000000000 --- a/pages/module/actions.php +++ /dev/null @@ -1,528 +0,0 @@ -isValid()) { - $error = I18n::msg('csrf_token_invalid'); -} elseif ('delete' == $function) { - $del = Sql::factory(); - // $del->setDebug(); - $qry = 'SELECT - * - FROM - ' . Core::getTablePrefix() . 'action a, - ' . Core::getTablePrefix() . 'module_action ma - LEFT JOIN - ' . Core::getTablePrefix() . 'module m - ON - ma.module_id = m.id - WHERE - ma.action_id = a.id AND - ma.action_id=?'; - $del->setQuery($qry, [$actionId]); // module mit dieser aktion vorhanden ? - if ($del->getRows() > 0) { - $actionInUseMsg = ''; - $actionName = $del->getValue('a.name'); - for ($i = 0; $i < $del->getRows(); ++$i) { - $actionInUseMsg .= '
  • ' . escape($del->getValue('m.name')) . ' [' . (int) $del->getValue('ma.module_id') . ']
  • '; - $del->next(); - } - - $actionInUseMsg = '
      ' . $actionInUseMsg . '
    '; - $error = I18n::msg('action_cannot_be_deleted', $actionName) . $actionInUseMsg; - } else { - $del->setQuery('DELETE FROM ' . Core::getTablePrefix() . 'action WHERE id=? LIMIT 1', [$actionId]); - $success = I18n::msg('action_deleted'); - } -} - -if ('add' == $function || 'edit' == $function) { - $name = Request::post('name', 'string'); - $key = trim(Request::post('key', 'string')); - $key = '' === $key ? null : $key; - $previewaction = Request::post('previewaction', 'string'); - $presaveaction = Request::post('presaveaction', 'string'); - $postsaveaction = Request::post('postsaveaction', 'string'); - - $previewstatus = 255; - $presavestatus = 255; - $postsavestatus = 255; - - if ($save && !$csrfToken->isValid()) { - $error = I18n::msg('csrf_token_invalid'); - $save = false; - } elseif ($save) { - $faction = Sql::factory(); - - $previewstatus = Request::post('preview_allevents', 'bool') ? [1, 2] : Request::post('previewstatus', 'array'); - $presavestatus = Request::post('presave_allevents', 'bool') ? [1, 2, 4] : Request::post('presavestatus', 'array'); - $postsavestatus = Request::post('postsave_allevents', 'bool') ? [1, 2, 4] : Request::post('postsavestatus', 'array'); - - $previewmode = 0; - foreach ($previewstatus as $status) { - $previewmode |= $status; - } - - $presavemode = 0; - foreach ($presavestatus as $status) { - $presavemode |= $status; - } - - $postsavemode = 0; - foreach ($postsavestatus as $status) { - $postsavemode |= $status; - } - - try { - $faction->setTable(Core::getTablePrefix() . 'action'); - $faction->setValue('name', $name); - $faction->setValue('key', $key); - $faction->setValue('preview', $previewaction); - $faction->setValue('presave', $presaveaction); - $faction->setValue('postsave', $postsaveaction); - $faction->setValue('previewmode', $previewmode); - $faction->setValue('presavemode', $presavemode); - $faction->setValue('postsavemode', $postsavemode); - $faction->addGlobalUpdateFields(); - - if ('add' == $function) { - $faction->addGlobalCreateFields(); - - $faction->insert(); - $success = I18n::msg('action_added'); - } else { - $faction->setWhere(['id' => $actionId]); - - $faction->update(); - $success = I18n::msg('action_updated'); - } - - if ('' != $goon) { - $save = false; - } else { - $function = ''; - } - } catch (SqlException $e) { - if (Sql::ERROR_VIOLATE_UNIQUE_KEY === $e->getErrorCode()) { - $error = I18n::msg('action_key_exists'); - $save = false; - } else { - throw $e; - } - } - } - - if (!$save) { - if ('edit' == $function) { - $legend = I18n::msg('action_edit') . ' ' . I18n::msg('id') . '=' . $actionId . ''; - - $action = Sql::factory(); - $action->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'action WHERE id=?', [$actionId]); - - $name = $action->getValue('name'); - $key = $action->getValue('key'); - $previewaction = $action->getValue('preview'); - $presaveaction = $action->getValue('presave'); - $postsaveaction = $action->getValue('postsave'); - $previewstatus = $action->getValue('previewmode'); - $presavestatus = $action->getValue('presavemode'); - $postsavestatus = $action->getValue('postsavemode'); - } else { - $legend = I18n::msg('action_create'); - } - - // PreView action macht nur bei add und edit Sinn da, - // - beim Delete kommt keine View - $options = [ - 1 => $ASTATUS[0] . ' - ' . I18n::msg('action_event_add'), - 2 => $ASTATUS[1] . ' - ' . I18n::msg('action_event_edit'), - ]; - - $selPreviewStatus = new ActionEventSelect($options); - $selPreviewStatus->setName('previewstatus[]'); - $selPreviewStatus->setId('previewstatus'); - $selPreviewStatus->setStyle('class="form-control"'); - - $options = [ - 1 => $ASTATUS[0] . ' - ' . I18n::msg('action_event_add'), - 2 => $ASTATUS[1] . ' - ' . I18n::msg('action_event_edit'), - 4 => $ASTATUS[2] . ' - ' . I18n::msg('action_event_delete'), - ]; - - $selPresaveStatus = new ActionEventSelect($options); - $selPresaveStatus->setName('presavestatus[]'); - $selPresaveStatus->setId('presavestatus'); - $selPresaveStatus->setStyle('class="form-control"'); - - $selPostsaveStatus = new ActionEventSelect($options); - $selPostsaveStatus->setName('postsavestatus[]'); - $selPostsaveStatus->setId('postsavestatus'); - $selPostsaveStatus->setStyle('class="form-control"'); - - $allPreviewChecked = 3 == $previewstatus ? ' checked="checked"' : ''; - foreach ([1, 2, 4] as $var) { - if (($previewstatus & $var) == $var) { - $selPreviewStatus->setSelected($var); - } - } - - $allPresaveChecked = 7 == $presavestatus ? ' checked="checked"' : ''; - foreach ([1, 2, 4] as $var) { - if (($presavestatus & $var) == $var) { - $selPresaveStatus->setSelected($var); - } - } - - $allPostsaveChecked = 7 == $postsavestatus ? ' checked="checked"' : ''; - foreach ([1, 2, 4] as $var) { - if (($postsavestatus & $var) == $var) { - $selPostsaveStatus->setSelected($var); - } - } - - $btnUpdate = ''; - if ('add' != $function) { - $btnUpdate = ''; - } - - if ('' != $success) { - $message .= Message::success($success); - } - - if ('' != $error) { - $message .= Message::error($error); - } - - $panel = ''; - $panel .= '
    - - - '; - - $formElements = []; - - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('translatable'); - $formElements[] = $n; - - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('action_key_notice'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $panel .= ' -
    - -
    - ' . I18n::msg('action_heading_preview') . ' ' . I18n::msg('action_mode_preview') . ''; - - $formElements = []; - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('action_hint', 'ArticleSliceAction $this'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $formElements = []; - $n = []; - $n['reverse'] = true; - $n['label'] = ''; - $n['field'] = ''; - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/checkbox.php'); - - $formElements = []; - $n = []; - $n['id'] = 'rex-js-preview-events'; - $n['label'] = ''; - $n['field'] = $selPreviewStatus->get(); - $n['note'] = I18n::msg('ctrl'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $panel .= ' -
    - -
    - ' . I18n::msg('action_heading_presave') . ' ' . I18n::msg('action_mode_presave') . ''; - - $formElements = []; - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('action_hint', 'ArticleSliceAction $this'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $formElements = []; - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/checkbox.php'); - - $formElements = []; - $n = []; - $n['id'] = 'rex-js-presave-events'; - $n['label'] = ''; - $n['field'] = $selPresaveStatus->get(); - $n['note'] = I18n::msg('ctrl'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $panel .= ' -
    - - -
    - ' . I18n::msg('action_heading_postsave') . ' ' . I18n::msg('action_mode_postsave') . ''; - - $formElements = []; - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('action_hint', 'ArticleSliceAction $this'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $formElements = []; - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/checkbox.php'); - - $formElements = []; - $n = []; - $n['id'] = 'rex-js-postsave-events'; - $n['label'] = ''; - $n['field'] = $selPostsaveStatus->get(); - $n['note'] = I18n::msg('ctrl'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $panel .= '
    '; - - $formElements = []; - - $fragment = new Fragment(); - - $n = []; - $n['field'] = '' . I18n::msg('form_abort') . ''; - $formElements[] = $n; - - $n = []; - $n['field'] = ''; - $formElements[] = $n; - - if ('' != $btnUpdate) { - $n = []; - $n['field'] = $btnUpdate; - $formElements[] = $n; - } - - $fragment->setVar('elements', $formElements, false); - $buttons = $fragment->parse('core/form/submit.php'); - - $fragment = new Fragment(); - $fragment->setVar('class', 'edit', false); - $fragment->setVar('title', $legend, false); - $fragment->setVar('body', $panel, false); - $fragment->setVar('buttons', $buttons, false); - $content = $fragment->parse('core/page/section.php'); - - $content = ' -
    - ' . $csrfToken->getHiddenField() . ' - ' . $content . ' -
    - - '; - - echo $content; - - $OUT = false; - } -} - -if ($OUT) { - if ('' != $success) { - $message .= Message::success($success); - } - - if ('' != $error) { - $message .= Message::error($error); - } - - // ausgabe actionsliste ! - $content .= ' - - - - - - - - - - - - - - '; - - $sql = Sql::factory(); - $sql->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'action ORDER BY name'); - $rows = $sql->getRows(); - - if ($rows > 0) { - $content .= '' . "\n"; - - for ($i = 0; $i < $rows; ++$i) { - $previewmode = []; - $presavemode = []; - $postsavemode = []; - - foreach ([1 => 'ADD', 2 => 'EDIT', 4 => 'DELETE'] as $var => $value) { - if (($sql->getValue('previewmode') & $var) == $var) { - $previewmode[] = $value; - } - } - - foreach ([1 => 'ADD', 2 => 'EDIT', 4 => 'DELETE'] as $var => $value) { - if (($sql->getValue('presavemode') & $var) == $var) { - $presavemode[] = $value; - } - } - - foreach ([1 => 'ADD', 2 => 'EDIT', 4 => 'DELETE'] as $var => $value) { - if (($sql->getValue('postsavemode') & $var) == $var) { - $postsavemode[] = $value; - } - } - - $content .= ' - - - - - - - - - - - - '; - - $sql->next(); - } - - $content .= '' . "\n"; - } - - $content .= ' -
    ' . I18n::msg('id') . '' . I18n::msg('header_action_key') . '' . I18n::msg('action_name') . '' . I18n::msg('action_header_preview') . '' . I18n::msg('action_header_presave') . '' . I18n::msg('action_header_postsave') . '' . I18n::msg('action_functions') . '
    ' . (int) $sql->getValue('id') . '' . escape((string) ($sql->getValue('key') ?? '')) . '' . escape($sql->getValue('name')) . '' . implode('/', $previewmode) . '' . implode('/', $presavemode) . '' . implode('/', $postsavemode) . ' ' . I18n::msg('change') . ' ' . I18n::msg('delete') . '
    '; - - if ($rows < 1) { - $content .= Message::info(I18n::msg('actions_not_found')); - } - - echo $message; - - $fragment = new Fragment(); - $fragment->setVar('title', I18n::msg('action_caption'), false); - $fragment->setVar('content', $content, false); - echo $fragment->parse('core/page/section.php'); -} diff --git a/pages/module/index.php b/pages/module/index.php deleted file mode 100644 index 23d39bcd48..0000000000 --- a/pages/module/index.php +++ /dev/null @@ -1,9 +0,0 @@ -isValid()) { - $error = I18n::msg('csrf_token_invalid'); -} elseif ('' != $addAction) { - $action = Sql::factory(); - $action->setTable(Core::getTablePrefix() . 'module_action'); - $action->setValue('module_id', $moduleId); - $action->setValue('action_id', $actionId); - - $action->insert(); - $success = I18n::msg('action_taken'); - $goon = '1'; -} elseif ('delete' == $functionAction) { - $action = Sql::factory(); - $action->setTable(Core::getTablePrefix() . 'module_action'); - $action->setWhere(['id' => $iactionId]); - $action->delete(); - - if ($action->getRows() > 0) { - $success = I18n::msg('action_deleted_from_modul'); - } else { - $error = $action->getError(); - } -} - -// ---------------------------- FUNKTIONEN FUER MODULE - -if ('delete' == $function && !$csrfToken->isValid()) { - $error = I18n::msg('csrf_token_invalid'); -} elseif ('delete' == $function) { - $del = Sql::factory(); - $del->setQuery(' - SELECT slice.article_id, slice.clang_id, slice.ctype_id, module.name - FROM ' . Core::getTable('article_slice') . ' slice - LEFT JOIN ' . Core::getTable('module') . ' module ON slice.module_id=module.id - WHERE slice.module_id=? - GROUP BY slice.article_id, slice.clang_id - ORDER BY slice.article_id, slice.clang_id - LIMIT 20 - ', [$moduleId]); - - if ($del->getRows() > 0) { - $moduleInUseMessage = ''; - $modulname = $del->getValue('module.name'); - for ($i = 0; $i < $del->getRows(); ++$i) { - $aid = $del->getValue('article_id'); - $clangId = $del->getValue('clang_id'); - $ctype = $del->getValue('ctype_id'); - $OOArt = Article::get($aid, $clangId); - - $label = $OOArt->getName() . ' [' . $aid . ']'; - if (Language::count() > 1) { - $label .= ' [' . Language::get($clangId)->getCode() . ']'; - } - - $moduleInUseMessage .= '
  • ' . escape($label) . '
  • '; - $del->next(); - } - - $error = I18n::msg('module_cannot_be_deleted', $modulname); - $error .= '
      ' . $moduleInUseMessage . '
    '; - } else { - $del = Sql::factory(); - $del->setQuery('DELETE FROM ' . Core::getTablePrefix() . 'module WHERE id=?', [$moduleId]); - - if ($del->getRows() > 0) { - $del = Sql::factory(); - $del->setQuery('DELETE FROM ' . Core::getTablePrefix() . 'module_action WHERE module_id=?', [$moduleId]); - ModuleCache::delete($moduleId); - $success = I18n::msg('module_deleted'); - $success = Extension::registerPoint(new ExtensionPoint('MODULE_DELETED', $success, [ - 'id' => $moduleId, - ])); - } else { - $error = I18n::msg('module_not_found'); - } - } -} - -if ('add' == $function || 'edit' == $function) { - if ('1' == $save && !$csrfToken->isValid()) { - $error = I18n::msg('csrf_token_invalid'); - $save = '0'; - } elseif ('1' == $save) { - $module = Sql::factory(); - - try { - if ('add' == $function) { - $IMOD = Sql::factory(); - $IMOD->setTable(Core::getTablePrefix() . 'module'); - $IMOD->setValue('name', $mname); - $IMOD->setValue('key', $mkey); - $IMOD->setValue('input', $eingabe); - $IMOD->setValue('output', $ausgabe); - $IMOD->addGlobalCreateFields(); - $IMOD->addGlobalUpdateFields(); - - $IMOD->insert(); - $moduleId = $IMOD->getLastId(); - ModuleCache::delete($moduleId); - $success = I18n::msg('module_added'); - $success = Extension::registerPoint(new ExtensionPoint('MODULE_ADDED', $success, [ - 'id' => $moduleId, - 'name' => $mname, - 'key' => $mkey, - 'input' => $eingabe, - 'output' => $ausgabe, - ])); - } else { - $module->setQuery('select * from ' . Core::getTablePrefix() . 'module where id=?', [$moduleId]); - if (1 == $module->getRows()) { - $oldAusgabe = $module->getValue('output'); - - $UMOD = Sql::factory(); - $UMOD->setTable(Core::getTablePrefix() . 'module'); - $UMOD->setWhere(['id' => $moduleId]); - $UMOD->setValue('name', $mname); - $UMOD->setValue('key', $mkey); - $UMOD->setValue('input', $eingabe); - $UMOD->setValue('output', $ausgabe); - $UMOD->addGlobalUpdateFields(); - - $UMOD->update(); - ModuleCache::delete($moduleId); - $success = I18n::msg('module_updated') . ' | ' . I18n::msg('articel_updated'); - $success = Extension::registerPoint(new ExtensionPoint('MODULE_UPDATED', $success, [ - 'id' => $moduleId, - 'name' => $mname, - 'key' => $mkey, - 'input' => $eingabe, - 'output' => $ausgabe, - ])); - - $newAusgabe = $ausgabe; - - if ($oldAusgabe != $newAusgabe) { - // article updaten - nur wenn ausgabe sich veraendert hat - $gc = Sql::factory(); - $gc->setQuery('SELECT DISTINCT(' . Core::getTablePrefix() . 'article.id) FROM ' . Core::getTablePrefix() . 'article - LEFT JOIN ' . Core::getTablePrefix() . 'article_slice ON ' . Core::getTablePrefix() . 'article.id=' . Core::getTablePrefix() . 'article_slice.article_id - WHERE ' . Core::getTablePrefix() . 'article_slice.module_id=?', [$moduleId]); - for ($i = 0; $i < $gc->getRows(); ++$i) { - ArticleCache::delete($gc->getValue(Core::getTablePrefix() . 'article.id')); - $gc->next(); - } - } - } - } - } catch (SqlException $e) { - if (Sql::ERROR_VIOLATE_UNIQUE_KEY === $e->getErrorCode()) { - $error = I18n::msg('module_key_exists'); - $save = '0'; - } else { - $error = $e->getMessage(); - } - } - - if ('' != $goon) { - $save = '0'; - } else { - $function = ''; - } - } - - if ('1' != $save) { - if ('edit' == $function) { - $legend = I18n::msg('module_edit') . ' ' . I18n::msg('id') . '=' . $moduleId . ''; - - $hole = Sql::factory(); - $hole->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'module WHERE id=?', [$moduleId]); - $mname = $hole->getValue('name'); - $mkey = $hole->getValue('key'); - $ausgabe = $hole->getValue('output'); - $eingabe = $hole->getValue('input'); - } else { - $legend = I18n::msg('create_module'); - } - - $btnUpdate = ''; - if ('add' != $function) { - $btnUpdate = ''; - } - - if ('' != $success) { - $message .= Message::success($success); - } - - if ('' != $error) { - $message .= Message::error($error); - } - - $content = ''; - $panel = ''; - $panel .= ' -
    - - - - '; - - $formElements = []; - - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('translatable'); - $formElements[] = $n; - - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('module_key_notice'); - $formElements[] = $n; - - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $formElements[] = $n; - - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('module_actions_notice'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $panel .= '
    '; - - $formElements = []; - - $n = []; - $n['field'] = '' . I18n::msg('form_abort') . ''; - $formElements[] = $n; - - $n = []; - $n['field'] = ''; - $formElements[] = $n; - - if ('' != $btnUpdate) { - $n = []; - $n['field'] = $btnUpdate; - $formElements[] = $n; - } - - $fragment = new Fragment(); - $fragment->setVar('elements', $formElements, false); - $buttons = $fragment->parse('core/form/submit.php'); - - $fragment = new Fragment(); - $fragment->setVar('class', 'edit', false); - $fragment->setVar('title', $legend, false); - $fragment->setVar('body', $panel, false); - $fragment->setVar('buttons', $buttons, false); - $content .= $fragment->parse('core/page/section.php'); - - if ('edit' == $function) { - // Im Edit Mode Aktionen bearbeiten - - $gaa = Sql::factory(); - $gaa->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'action ORDER BY name'); - - if ($gaa->getRows() > 0) { - $gma = Sql::factory(); - $gma->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'module_action, ' . Core::getTablePrefix() . 'action WHERE ' . Core::getTablePrefix() . 'module_action.action_id=' . Core::getTablePrefix() . 'action.id and ' . Core::getTablePrefix() . 'module_action.module_id=?', [$moduleId]); - - $actions = ''; - for ($i = 0; $i < $gma->getRows(); ++$i) { - $iactionId = $gma->getValue(Core::getTablePrefix() . 'module_action.id'); - $actionId = $gma->getValue(Core::getTablePrefix() . 'module_action.action_id'); - $actionEditUrl = Url::backendPage('modules/actions', ['action_id' => $actionId, 'function' => 'edit']); - $actionName = I18n::translate($gma->getValue('name')); - - $actions .= ' - - ' . (int) $gma->getValue('id') . ' - ' . $actionName . ' - ' . I18n::msg('edit') . ' - ' . I18n::msg('delete') . ' - '; - - $gma->next(); - } - - if ('' != $actions) { - $panel = ' - - - - - - - - - - - ' . $actions . ' - -
     ' . I18n::msg('id') . '' . I18n::msg('action_name') . '' . I18n::msg('action_functions') . '
    - '; - - $fragment = new Fragment(); - $fragment->setVar('title', I18n::msg('actions_added_caption'), false); - $fragment->setVar('content', $panel, false); - $content .= $fragment->parse('core/page/section.php'); - } - - $gaaSel = new Select(); - $gaaSel->setName('action_id'); - $gaaSel->setId('action_id'); - $gaaSel->setSize(1); - $gaaSel->setAttribute('class', 'form-control selectpicker'); - - for ($i = 0; $i < $gaa->getRows(); ++$i) { - $gaaSel->addOption(I18n::translate($gaa->getValue('name'), false), $gaa->getValue('id')); - $gaa->next(); - } - - $panel = ''; - $panel .= '
    '; - - $formElements = []; - - $n = []; - $n['label'] = ''; - $n['field'] = $gaaSel->get(); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $panel .= '
    '; - - $formElements = []; - - $n = []; - $n['field'] = ''; - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('elements', $formElements, false); - $buttons = $fragment->parse('core/form/submit.php'); - - $fragment = new Fragment(); - $fragment->setVar('title', I18n::msg('action_add'), false); - $fragment->setVar('body', $panel, false); - $fragment->setVar('buttons', $buttons, false); - $content .= $fragment->parse('core/page/section.php'); - } - } - - $content = ' -
    - ' . $csrfToken->getHiddenField() . ' - ' . $content . ' -
    '; - - echo $message; - - echo $content; - - $OUT = false; - } -} - -if ($OUT) { - if ('' != $success) { - $message .= Message::success($success); - } - - if ('' != $error) { - $message .= Message::error($error); - } - - $list = DataList::factory('SELECT id, `key`, name FROM ' . Core::getTablePrefix() . 'module ORDER BY name', 100); - $list->addParam('start', Request::request('start', 'int')); - $list->addTableAttribute('class', 'table-striped table-hover'); - - $tdIcon = ''; - $thIcon = ''; - $list->addColumn($thIcon, $tdIcon, 0, ['###VALUE###', '###VALUE###']); - $list->setColumnParams($thIcon, ['function' => 'edit', 'module_id' => '###id###']); - - $list->setColumnLabel('id', I18n::msg('id')); - $list->setColumnLayout('id', ['###VALUE###', '###VALUE###']); - - $list->setColumnLabel('key', I18n::msg('module_key')); - - $list->setColumnLabel('name', I18n::msg('module_description')); - $list->setColumnParams('name', ['function' => 'edit', 'module_id' => '###id###']); - $list->setColumnFormat('name', 'custom', static function () use ($list) { - return $list->getColumnLink('name', I18n::translate($list->getValue('name'))); - }); - - $slices = Sql::factory()->getArray('SELECT `module_id` FROM ' . Core::getTable('article_slice') . ' GROUP BY `module_id`'); - if (count($slices) > 0) { - $usedIds = array_flip(array_map(static function ($slice) { - return $slice['module_id']; - }, $slices)); - - $list->addColumn('use', ''); - $list->setColumnLabel('use', I18n::msg('module_in_use')); - $list->setColumnFormat('use', 'custom', static function () use ($list, $usedIds) { - return isset($usedIds[$list->getValue('id')]) ? ' ' . I18n::msg('yes') : ' ' . I18n::msg('no'); - }); - } - - $list->addColumn(I18n::msg('module_functions'), ' ' . I18n::msg('edit')); - $list->setColumnLayout(I18n::msg('module_functions'), ['###VALUE###', '###VALUE###']); - $list->setColumnParams(I18n::msg('module_functions'), ['function' => 'edit', 'module_id' => '###id###']); - - $list->addColumn(I18n::msg('delete_module'), ' ' . I18n::msg('delete')); - $list->setColumnLayout(I18n::msg('delete_module'), ['', '###VALUE###']); - $list->setColumnParams(I18n::msg('delete_module'), ['function' => 'delete', 'module_id' => '###id###'] + $csrfToken->getUrlParams()); - $list->addLinkAttribute(I18n::msg('delete_module'), 'data-confirm', I18n::msg('confirm_delete_module')); - - $list->setNoRowsMessage(I18n::msg('modules_not_found')); - - $content .= $list->get(); - - echo $message; - - $fragment = new Fragment(); - $fragment->setVar('title', I18n::msg('module_caption'), false); - $fragment->setVar('content', $content, false); - echo $fragment->parse('core/page/section.php'); -} diff --git a/pages/template/index.php b/pages/template/index.php deleted file mode 100644 index f3924f43f6..0000000000 --- a/pages/template/index.php +++ /dev/null @@ -1,602 +0,0 @@ -isValid()) { - $error = I18n::msg('csrf_token_invalid'); - } else { - $del = Sql::factory(); - $templateIsInUseError = Template::templateIsInUse($templateId, 'cant_delete_template_because_its_in_use'); - if (false !== $templateIsInUseError) { - $error .= $templateIsInUseError; - } - - if (Template::getDefaultId() == $templateId) { - $del = Sql::factory(); - $del->setQuery('SELECT name FROM ' . Core::getTable('template') . ' WHERE id = ' . $templateId); - $templatename = $del->getValue('name'); - - $error .= I18n::msg('cant_delete_template_because_its_default_template', $templatename); - } - if ('' == $error) { - $del->setQuery('DELETE FROM ' . Core::getTablePrefix() . 'template WHERE id = "' . $templateId . '" LIMIT 1'); // max. ein Datensatz darf loeschbar sein - TemplateCache::delete($templateId); - $success = I18n::msg('template_deleted'); - $success = Extension::registerPoint(new ExtensionPoint('TEMPLATE_DELETED', $success, [ - 'id' => $templateId, - ])); - } - } -} elseif ('edit' == $function) { - $hole = Sql::factory(); - $hole->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'template WHERE id = "' . $templateId . '"'); - if (1 == $hole->getRows()) { - $templatekey = $hole->getValue('key'); - $templatename = $hole->getValue('name'); - $template = $hole->getValue('content'); - $active = $hole->getValue('active'); - $attributes = $hole->getArrayValue('attributes'); - } else { - $function = ''; - } -} else { - $templateId = 0; -} - -if ('add' == $function || 'edit' == $function) { - if ('ja' == $save && !$csrfToken->isValid()) { - echo Message::error(I18n::msg('csrf_token_invalid')); - $save = 'nein'; - } - - if ('ja' == $save) { - $previousActive = $active; - $active = Request::post('active', 'int'); - $templatename = Request::post('templatename', 'string'); - $template = Request::post('content', 'string'); - - $templatekey = trim(Request::post('templatekey', 'string')); - $templatekey = '' === $templatekey ? null : $templatekey; - - $ctypes = Request::post('ctype', 'array'); - - $numCtypes = count($ctypes); - if ('' == $ctypes[$numCtypes]) { - unset($ctypes[$numCtypes]); - if (isset($ctypes[$numCtypes - 1]) && '' == $ctypes[$numCtypes - 1]) { - unset($ctypes[$numCtypes - 1]); - } - } - - $categories = Request::post('categories', 'array'); - // leerer eintrag = 0 - if (0 == count($categories) || !isset($categories['all']) || 1 != $categories['all']) { - $categories['all'] = 0; - } - - $modules = Request::post('modules', 'array'); - // leerer eintrag = 0 - if (0 == count($modules)) { - $modules[1]['all'] = 0; - } - - for ($k = 1; $k <= $numCtypes; ++$k) { - if (!isset($modules[$k]['all']) || 1 != $modules[$k]['all']) { - $modules[$k]['all'] = 0; - } - } - - $attributes['ctype'] = $ctypes; - $attributes['modules'] = $modules; - $attributes['categories'] = $categories; - $TPL = Sql::factory(); - $TPL->setTable(Core::getTablePrefix() . 'template'); - $TPL->setValue('key', $templatekey); - $TPL->setValue('name', $templatename); - $TPL->setValue('active', $active); - $TPL->setValue('content', $template); - $TPL->addGlobalUpdateFields(); - - $TPL->setArrayValue('attributes', $attributes); - - if ('add' == $function) { - $TPL->addGlobalCreateFields(); - - try { - $TPL->insert(); - $templateId = $TPL->getLastId(); - TemplateCache::delete($templateId); - $success = I18n::msg('template_added'); - $success = Extension::registerPoint(new ExtensionPoint('TEMPLATE_ADDED', $success, [ - 'id' => $templateId, - 'key' => $templatekey, - 'name' => $templatename, - 'content' => $template, - 'active' => $active, - 'ctype' => $ctypes, - 'modules' => $modules, - 'categories' => $categories, - ])); - } catch (SqlException $e) { - if (Sql::ERROR_VIOLATE_UNIQUE_KEY == $e->getErrorCode()) { - $error = I18n::msg('template_key_exists'); - $save = 'nein'; - } else { - $error = $e->getMessage(); - } - } - } else { - if ($previousActive && !$active) { - if (Template::getDefaultId() == $templateId) { - $error .= I18n::msg('cant_inactivate_template_because_its_default_template', $templatename); - } - - $templateIsInUseError = Template::templateIsInUse($templateId, 'cant_inactivate_template_because_its_in_use'); - if (false !== $templateIsInUseError) { - $error .= ($error ? '

    ' : '') . $templateIsInUseError; - } - } - - if ('' == $error) { - $TPL->setWhere(['id' => $templateId]); - - try { - $TPL->update(); - TemplateCache::delete($templateId); - $success = I18n::msg('template_updated'); - $success = Extension::registerPoint(new ExtensionPoint('TEMPLATE_UPDATED', $success, [ - 'id' => $templateId, - 'key' => $templatekey, - 'name' => $templatename, - 'content' => $template, - 'active' => $active, - 'ctype' => $ctypes, - 'modules' => $modules, - 'categories' => $categories, - ])); - } catch (SqlException $e) { - if (Sql::ERROR_VIOLATE_UNIQUE_KEY == $e->getErrorCode()) { - $error = I18n::msg('template_key_exists'); - $save = 'nein'; - } else { - $error = $e->getMessage(); - } - } - } - } - - if ('' != $goon) { - $function = 'edit'; - $save = 'nein'; - } else { - $function = ''; - } - } - - if ('ja' != $save) { - // Ctype Handling - $ctypes = $attributes['ctype'] ?? []; - $modules = $attributes['modules'] ?? []; - $categories = $attributes['categories'] ?? []; - - if (!is_array($modules)) { - $modules = []; - } - - if (!is_array($categories)) { - $categories = []; - } - - // modules[ctype_id][module_id]; - // modules[ctype_id]['all']; - - // Module ... - $modulSelect = new Select(); - $modulSelect->setMultiple(true); - $modulSelect->setSize(10); - $modulSelect->setAttribute('class', 'form-control'); - $mSql = Sql::factory(); - foreach ($mSql->getArray('SELECT id, name FROM ' . Core::getTablePrefix() . 'module ORDER BY name') as $m) { - $modulSelect->addOption(I18n::translate((string) $m['name']), (int) $m['id']); - } - - // Kategorien - $catSelect = new CategorySelect(false, false, false, false); - $catSelect->setMultiple(true); - $catSelect->setSize(10); - $catSelect->setName('categories[]'); - $catSelect->setId('rex-id-categories-select'); - $catSelect->setAttribute('class', 'form-control'); - - foreach ($categories as $c => $cc) { - // typsicherer vergleich, weil (0 != "all") => false - if ('all' !== $c) { - $catSelect->setSelected($cc); - } - } - - $ctypesOut = ''; - $i = 1; - $ctypes[] = ''; // Extra, fuer Neue Spalte - - if (is_array($ctypes)) { - foreach ($ctypes as $name) { - $modulSelect->setName('modules[' . $i . '][]'); - $modulSelect->setId('rex-id-modules-' . $i . '-select'); - $modulSelect->resetSelected(); - if (isset($modules[$i]) && count($modules[$i]) > 0) { - foreach ($modules[$i] as $j => $jj) { - // typsicherer vergleich, weil (0 != "all") => false - if ('all' !== $j) { - $modulSelect->setSelected($jj); - } - } - } - - $ctypesOut .= '
    ' . I18n::msg('content_type') . ' ' . I18n::msg('id') . '=' . $i . ''; - - $formElements = []; - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $ctypesOut .= $fragment->parse('core/form/form.php'); - - $field = ''; - $field .= ''; - $n['field'] = $field; - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('elements', $formElements, false); - $ctypesOut .= $fragment->parse('core/form/checkbox.php'); - - $formElements = []; - $n = []; - $n['id'] = 'rex-js-modules' . $i; - $n['label'] = ''; - $n['field'] = $modulSelect->get(); - $n['note'] = I18n::msg('ctrl'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $ctypesOut .= $fragment->parse('core/form/form.php'); - - $ctypesOut .= '
    '; - - ++$i; - } - } - - $ctypesOut .= ' - '; - - $tmplActiveChecked = 1 == $active ? ' checked="checked"' : ''; - - if ('' != $success) { - $message .= Message::success($success); - } - - if ('' != $error) { - $message .= Message::error($error); - } - - $panel = ''; - - $panel .= ' -
    -
    -
    - - - - '; - - $formElements = []; - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('translatable'); - $formElements[] = $n; - - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('template_key_notice'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $formElements = []; - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $n['note'] = I18n::msg('checkbox_template_active_info'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/checkbox.php'); - - $formElements = []; - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $panel .= ' -
    -
    -
    - ' . $ctypesOut . ' -
    - -
    -
    - ' . I18n::msg('template_categories') . ''; - - $field = ''; - $field .= ''; - $n['field'] = $field; - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/checkbox.php'); - - $formElements = []; - $n = []; - $n['id'] = 'rex-id-categories'; - $n['label'] = ''; - $n['field'] = $catSelect->get(); - $n['note'] = I18n::msg('ctrl'); - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('flush', true); - $fragment->setVar('elements', $formElements, false); - $panel .= $fragment->parse('core/form/form.php'); - - $panel .= ' -
    -
    -
    '; - - $formElements = []; - - $n = []; - $n['field'] = '' . I18n::msg('form_abort') . ''; - $formElements[] = $n; - - $n = []; - $n['field'] = ''; - $formElements[] = $n; - - $n = []; - $n['field'] = ''; - $formElements[] = $n; - - $fragment = new Fragment(); - $fragment->setVar('elements', $formElements, false); - $buttons = $fragment->parse('core/form/submit.php'); - - $activeTab = Request::request('template_tab', 'string', 'rex-form-template-default'); - $optionTabs = [ - 'rex-form-template-default' => I18n::msg('header_template'), - 'rex-form-template-ctype' => I18n::msg('content_types'), - 'rex-form-template-categories' => I18n::msg('template_categories'), - ]; - $options = ''; - - if ('edit' === $function) { - $legend = I18n::msg('edit_template') . ' ' . I18n::msg('id') . ' = ' . $templateId . ''; - } else { - $legend = I18n::msg('create_template'); - } - - $fragment = new Fragment(); - $fragment->setVar('class', 'edit', false); - $fragment->setVar('title', $legend, false); - $fragment->setVar('options', $options, false); - $fragment->setVar('body', $panel, false); - $fragment->setVar('buttons', $buttons, false); - $content = $fragment->parse('core/page/section.php'); - - $content = ' -
    - ' . $csrfToken->getHiddenField() . ' - ' . $content . ' -
    - - '; - - echo $message; - echo $content; - - $OUT = false; - } -} - -if ($OUT) { - if ('' != $success) { - $message .= Message::success($success); - } - - if ('' != $error) { - $message .= Message::error($error); - } - - $list = DataList::factory('SELECT id, `key`, name, active FROM ' . Core::getTablePrefix() . 'template ORDER BY name', 100); - $list->addParam('start', Request::request('start', 'int')); - $list->addTableAttribute('class', 'table-striped table-hover'); - - $tdIcon = ''; - $thIcon = ''; - $list->addColumn($thIcon, $tdIcon, 0, ['###VALUE###', '###VALUE###']); - $list->setColumnParams($thIcon, ['function' => 'edit', 'template_id' => '###id###']); - - $list->setColumnLabel('id', I18n::msg('id')); - $list->setColumnLayout('id', ['###VALUE###', '###VALUE###']); - - $list->setColumnLabel('key', I18n::msg('header_template_key')); - - $list->setColumnLabel('name', I18n::msg('header_template_description')); - $list->setColumnParams('name', ['function' => 'edit', 'template_id' => '###id###']); - $list->setColumnFormat('name', 'custom', static function () use ($list) { - return $list->getColumnLink('name', I18n::translate((string) $list->getValue('name'))); - }); - - $list->setColumnLabel('active', I18n::msg('header_template_active')); - $list->setColumnFormat('active', 'custom', static function () use ($list) { - return 1 == $list->getValue('active') ? ' ' . I18n::msg('yes') : ' ' . I18n::msg('no'); - }); - - $list->addColumn(I18n::msg('header_template_functions'), ' ' . I18n::msg('edit')); - $list->setColumnLayout(I18n::msg('header_template_functions'), ['###VALUE###', '###VALUE###']); - $list->setColumnParams(I18n::msg('header_template_functions'), ['function' => 'edit', 'template_id' => '###id###']); - - $list->addColumn(I18n::msg('delete_template'), ' ' . I18n::msg('delete')); - $list->setColumnLayout(I18n::msg('delete_template'), ['', '###VALUE###']); - $list->setColumnParams(I18n::msg('delete_template'), ['function' => 'delete', 'template_id' => '###id###'] + $csrfToken->getUrlParams()); - $list->addLinkAttribute(I18n::msg('delete_template'), 'data-confirm', I18n::msg('confirm_delete_template')); - - $list->setNoRowsMessage(I18n::msg('templates_not_found')); - - $content .= $list->get(); - - echo $message; - - $fragment = new Fragment(); - $fragment->setVar('title', I18n::msg('header_template_caption'), false); - $fragment->setVar('content', $content, false); - echo $fragment->parse('core/page/section.php'); -} diff --git a/src/Backend/Controller.php b/src/Backend/Controller.php index c9b4c8b3a5..e07df63f05 100644 --- a/src/Backend/Controller.php +++ b/src/Backend/Controller.php @@ -188,22 +188,6 @@ public static function appendLoggedInPages(): void ->setPjax() ->setIcon('rex-icon rex-icon-open-category') ; - self::$pages['modules'] = new MainPage('system', 'modules', I18n::msg('modules')) - ->setPath(Path::core('pages/module/index.php')) - ->setRequiredPermissions('isAdmin') - ->setPrio(40) - ->setPjax() - ->setIcon('rex-icon rex-icon-module') - ->addSubpage(new Page('modules', I18n::msg('modules'))->setSubPath(Path::core('pages/module/modules.php'))) - ->addSubpage(new Page('actions', I18n::msg('actions'))->setSubPath(Path::core('pages/module/actions.php'))) - ; - self::$pages['templates'] = new MainPage('system', 'templates', I18n::msg('templates')) - ->setPath(Path::core('pages/template/index.php')) - ->setRequiredPermissions('isAdmin') - ->setPrio(30) - ->setPjax() - ->setIcon('rex-icon rex-icon-template') - ; self::$pages['content'] = new MainPage('system', 'content', I18n::msg('content')) ->setPath(Path::core('pages/structure/content.php')) ->setRequiredPermissions('structure/hasStructurePerm') From e9ecbf86a1dafab5a120d4cf68ed340dd919663a Mon Sep 17 00:00:00 2001 From: Gregor Harlan Date: Mon, 13 Apr 2026 00:54:52 +0200 Subject: [PATCH 2/2] feat!: replace DB-based templates/modules with native PHP classes Templates and modules are no longer stored as PHP code text in the database but as native PHP classes with attributes (#[AsTemplate], #[AsModule]). Classes are discovered automatically via ClassDiscovery. - Remove DB tables rex_template, rex_module, rex_action, rex_module_action - rex_article.template and rex_article_slice.module reference by string key - Template/module classes contain logic directly (render, input, output) - Lifecycle hooks (onPreview, onPresave, onPostsave) in module instead of separate actions - Remove backend management pages for templates/modules/actions - ContentSection as value object for template content areas - Locale-aware sorting via Collator --- .tools/phpstan/baseline/_loader.php | 2 +- .../baseline/missingType.iterableValue.php | 12 +- .../baseline/missingType.parameter.php | 17 +- .tools/psalm/baseline.xml | 119 +------- boot/frontend.php | 3 +- lang/de_de.lang | 4 +- lang/en_gb.lang | 4 +- pages/structure/content.edit.php | 2 - pages/structure/content.metainfo.php | 18 +- pages/structure/content.php | 74 ++--- pages/structure/index.php | 14 +- pages/system/settings.php | 27 +- rector.php | 9 +- setup/install.php | 66 +--- src/Content/ApiFunction/ArticleAdd.php | 2 +- src/Content/ApiFunction/ArticleEdit.php | 2 +- src/Content/ApiFunction/ArticleSliceMove.php | 11 +- src/Content/ArticleContent.php | 4 +- src/Content/ArticleContentBase.php | 143 ++++----- src/Content/ArticleContentEditor.php | 179 +++++------ src/Content/ArticleHandler.php | 24 +- src/Content/ArticleSlice.php | 22 +- src/Content/ArticleSliceAction.php | 79 ++--- src/Content/AsModule.php | 14 + src/Content/AsTemplate.php | 14 + src/Content/CategoryHandler.php | 21 +- src/Content/ContentHandler.php | 6 +- src/Content/ContentSection.php | 22 +- src/Content/ExtensionPoint/SliceMenu.php | 8 +- src/Content/Module.php | 94 +++--- src/Content/ModuleCache.php | 37 --- src/Content/ModulePermission.php | 19 +- src/Content/StructureElement.php | 16 +- src/Content/Template.php | 283 ++++++------------ src/Content/TemplateCache.php | 64 ---- src/Form/Select/TemplateSelect.php | 46 ++- src/MetaInfo/Handler/ArticleHandler.php | 2 +- 37 files changed, 510 insertions(+), 973 deletions(-) create mode 100644 src/Content/AsModule.php create mode 100644 src/Content/AsTemplate.php delete mode 100644 src/Content/ModuleCache.php delete mode 100644 src/Content/TemplateCache.php diff --git a/.tools/phpstan/baseline/_loader.php b/.tools/phpstan/baseline/_loader.php index 7e387b17ba..4739200a63 100644 --- a/.tools/phpstan/baseline/_loader.php +++ b/.tools/phpstan/baseline/_loader.php @@ -2,7 +2,7 @@ declare(strict_types=1); -// total 295 errors +// total 290 errors return ['includes' => [ __DIR__ . '/argument.templateType.php', diff --git a/.tools/phpstan/baseline/missingType.iterableValue.php b/.tools/phpstan/baseline/missingType.iterableValue.php index eb8f060aea..633b8d031a 100644 --- a/.tools/phpstan/baseline/missingType.iterableValue.php +++ b/.tools/phpstan/baseline/missingType.iterableValue.php @@ -2,7 +2,7 @@ declare(strict_types=1); -// total 186 errors +// total 184 errors $ignoreErrors = []; $ignoreErrors[] = [ @@ -45,11 +45,6 @@ 'count' => 1, 'path' => __DIR__ . '/../../../src/Backend/Controller.php', ]; -$ignoreErrors[] = [ - 'rawMessage' => 'Property Redaxo\\Core\\Content\\ArticleContentBase::$template_attributes type has no value type specified in iterable type array.', - 'count' => 1, - 'path' => __DIR__ . '/../../../src/Content/ArticleContentBase.php', -]; $ignoreErrors[] = [ 'rawMessage' => 'Method Redaxo\\Core\\Content\\ArticleHandler::addArticle() has parameter $data with no value type specified in iterable type array.', 'count' => 1, @@ -180,11 +175,6 @@ 'count' => 1, 'path' => __DIR__ . '/../../../src/Content/StructurePermission.php', ]; -$ignoreErrors[] = [ - 'rawMessage' => 'Method Redaxo\\Core\\Content\\Template::hasModule() has parameter $templateAttributes with no value type specified in iterable type array.', - 'count' => 1, - 'path' => __DIR__ . '/../../../src/Content/Template.php', -]; $ignoreErrors[] = [ 'rawMessage' => 'Method Redaxo\\Core\\Cronjob\\CronjobExecutor::tryExecute() has parameter $params with no value type specified in iterable type array.', 'count' => 1, diff --git a/.tools/phpstan/baseline/missingType.parameter.php b/.tools/phpstan/baseline/missingType.parameter.php index 7ee29d242a..afc3091f33 100644 --- a/.tools/phpstan/baseline/missingType.parameter.php +++ b/.tools/phpstan/baseline/missingType.parameter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -// total 83 errors +// total 80 errors $ignoreErrors = []; $ignoreErrors[] = [ @@ -145,21 +145,6 @@ 'count' => 1, 'path' => __DIR__ . '/../../../src/Content/Linkmap/CategoryTreeRenderer.php', ]; -$ignoreErrors[] = [ - 'rawMessage' => 'Method Redaxo\\Core\\Content\\Template::__construct() has parameter $templateId with no type specified.', - 'count' => 1, - 'path' => __DIR__ . '/../../../src/Content/Template.php', -]; -$ignoreErrors[] = [ - 'rawMessage' => 'Method Redaxo\\Core\\Content\\Template::hasModule() has parameter $ctype with no type specified.', - 'count' => 1, - 'path' => __DIR__ . '/../../../src/Content/Template.php', -]; -$ignoreErrors[] = [ - 'rawMessage' => 'Method Redaxo\\Core\\Content\\Template::hasModule() has parameter $moduleId with no type specified.', - 'count' => 1, - 'path' => __DIR__ . '/../../../src/Content/Template.php', -]; $ignoreErrors[] = [ 'rawMessage' => 'Method Redaxo\\Core\\Cronjob\\CronjobManager::setExecutionStart() has parameter $reset with no type specified.', 'count' => 1, diff --git a/.tools/psalm/baseline.xml b/.tools/psalm/baseline.xml index e72bd57530..054195b70d 100644 --- a/.tools/psalm/baseline.xml +++ b/.tools/psalm/baseline.xml @@ -1306,12 +1306,6 @@ getValue('createuser'))]]> getValue('updateuser'))]]> - - - - - - getValue('createuser'))]]> getValue('updateuser'))]]> @@ -1322,33 +1316,18 @@
    - - - - - - - - - - - - + - - - - @@ -1357,7 +1336,6 @@ - getValue('template_id')]]> getMountpoints()]]> getMountpoints()]]> $sql->getValue('id'), 'artstart' => $structureContext->getArtStart(), 'art_status' => $artStatusKey] + ArticleStatusChange::getUrlParams()]]> @@ -1367,9 +1345,6 @@ 'content/edit', 'article_id' => $sql->getValue('id'), 'mode' => 'edit']]]> 'content/edit', 'article_id' => $sql->getValue('id')]]]> - - getValue('template_id')]]]> - getValue('id')]]> @@ -1390,12 +1365,6 @@ getValue('priority'))]]> getValue('priority'))]]> - - getValue('template_id')]]> - - - - getValue('id')]]> @@ -1941,35 +1910,18 @@ - - - getTemplate()]]> - - getTemplate()]]> - - - - - - MODULESELECT[$ctId]]]> - - - - - - - - MODULESELECT]]> - + + ctype]]> + @@ -1995,15 +1947,15 @@ - - + + - - + + @@ -2034,8 +1986,8 @@ - - + + @@ -2086,7 +2038,7 @@ $ArtSql->getValue('id'), 'startarticle' => '0', 'clang_id' => $clang]]]> - + getValue('path')]]> @@ -2221,15 +2173,6 @@ - - - - - - - - - @@ -2263,34 +2206,9 @@
    - - - - - - - - - - - + getContentSections(), static fn (ContentSection $s) => $s->id === $id)]]> - - - - - - - - - - - getValue('content')]]> - - - getValue('content')]]> - @@ -2969,13 +2887,6 @@ - - templates]]> - - - templates]]> - ]]> - @@ -3818,12 +3729,6 @@ - - getValue('template_id')]]> - - - getValue('template_id')]]> - diff --git a/boot/frontend.php b/boot/frontend.php index 31b78e49cd..3a4135f1ec 100644 --- a/boot/frontend.php +++ b/boot/frontend.php @@ -112,10 +112,9 @@ $sliceDate = ' AND ' . Core::getTablePrefix() . 'article_slice.history_date = ' . $escapeSql->escape($historyDate); - return 'SELECT ' . Core::getTablePrefix() . 'module.id, ' . Core::getTablePrefix() . 'module.key,' . Core::getTablePrefix() . 'module.name, ' . Core::getTablePrefix() . 'module.output, ' . Core::getTablePrefix() . 'module.input, ' . Core::getTablePrefix() . 'article_slice.*, ' . Core::getTablePrefix() . 'article.parent_id + return 'SELECT ' . Core::getTablePrefix() . 'article_slice.*, ' . Core::getTablePrefix() . 'article.parent_id FROM ' . ArticleSliceHistory::getTable() . ' as ' . Core::getTablePrefix() . 'article_slice - LEFT JOIN ' . Core::getTablePrefix() . 'module ON ' . Core::getTablePrefix() . 'article_slice.module_id=' . Core::getTablePrefix() . 'module.id LEFT JOIN ' . Core::getTablePrefix() . 'article ON ' . Core::getTablePrefix() . 'article_slice.article_id=' . Core::getTablePrefix() . 'article.id WHERE ' . Core::getTablePrefix() . "article_slice.clang_id='" . $article->getClangId() . "' AND diff --git a/lang/de_de.lang b/lang/de_de.lang index 90f1a207f4..a84975ffef 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -1309,10 +1309,10 @@ perm_options_linkmap[all_categories] = Alle Kategorien in der Linkmap anzeigen system_setting_start_article_id = Startartikel system_setting_notfound_article_id = Fehlerartikel -system_setting_default_template_id = Standardtemplate +system_setting_default_template = Standardtemplate system_setting_start_article_id_invalid = Ungültiger Startartikel! system_setting_notfound_article_id_invalid = Ungültiger Fehlerartikel! -system_setting_default_template_id_invalid = Ungültiges Standardtemplate! +system_setting_default_template_invalid = Ungültiges Standardtemplate! linkmap = Linkmap linkmap_categories = Kategorien diff --git a/lang/en_gb.lang b/lang/en_gb.lang index 7c56188bb3..009d1e22f9 100644 --- a/lang/en_gb.lang +++ b/lang/en_gb.lang @@ -1272,10 +1272,10 @@ perm_options_linkmap[all_categories] = Show all categories in Linkmap system_setting_start_article_id = Start article system_setting_notfound_article_id = Not-found article -system_setting_default_template_id = Default template +system_setting_default_template = Default template system_setting_start_article_id_invalid = Invalid start article! system_setting_notfound_article_id_invalid = Invalid not-found article! -system_setting_default_template_id_invalid = Invalid default template! +system_setting_default_template_invalid = Invalid default template! linkmap = Linkmap linkmap_categories = Categories diff --git a/pages/structure/content.edit.php b/pages/structure/content.edit.php index 192ad4eab3..e74dcd279f 100644 --- a/pages/structure/content.edit.php +++ b/pages/structure/content.edit.php @@ -8,7 +8,6 @@ assert(isset($clang) && is_int($clang)); assert(isset($ctype) && is_int($ctype)); assert(isset($sliceId) && is_int($sliceId)); -assert(isset($templateAttributes) && is_array($templateAttributes)); assert(isset($sliceRevision) && is_int($sliceRevision)); assert(isset($function) && is_string($function)); assert(isset($info) && is_string($info)); @@ -27,7 +26,6 @@ $CONT->getContentAsQuery(); $CONT->info = $info; $CONT->warning = $warning; -$CONT->template_attributes = $templateAttributes; $CONT->setArticleId($articleId); $CONT->setSliceId($sliceId); $CONT->setMode('edit'); diff --git a/pages/structure/content.metainfo.php b/pages/structure/content.metainfo.php index 7dd1c27c3d..87ca78ab8d 100644 --- a/pages/structure/content.metainfo.php +++ b/pages/structure/content.metainfo.php @@ -5,6 +5,7 @@ use Redaxo\Core\Content\Article; use Redaxo\Core\Content\ArticleHandler; use Redaxo\Core\Content\StructureContext; +use Redaxo\Core\Content\Template; use Redaxo\Core\Core; use Redaxo\Core\Database\Sql; use Redaxo\Core\ExtensionPoint\ExtensionPoint; @@ -80,27 +81,20 @@ $article = Sql::factory(); $article->setQuery(' - SELECT - article.*, template.attributes as template_attributes - FROM - ' . Core::getTablePrefix() . 'article as article - LEFT JOIN ' . Core::getTablePrefix() . "template as template - ON template.id=article.template_id + SELECT article.* + FROM ' . Core::getTablePrefix() . "article as article WHERE article.id='$articleId' AND clang_id=$clang", ); if (1 == $article->getRows()) { - // ----- ctype holen - $templateAttributes = $article->getArrayValue('template_attributes'); - - $ctypes = $templateAttributes['ctype'] ?? []; // ctypes - aus dem template + $template = Template::get((string) $article->getValue('template')); $ctype = Request::request('ctype', 'int', 1); - if (!array_key_exists($ctype, $ctypes)) { + if ($ctype < 1 || !$template?->hasContentSection($ctype)) { $ctype = 1; - } // default = 1 + } $context = new Context([ 'page' => Controller::getCurrentPage(), diff --git a/pages/structure/content.php b/pages/structure/content.php index 1151b0407a..463526d24e 100644 --- a/pages/structure/content.php +++ b/pages/structure/content.php @@ -10,6 +10,7 @@ use Redaxo\Core\Content\ArticleSliceAction; use Redaxo\Core\Content\ContentHandler; use Redaxo\Core\Content\ExtensionPoint\ArticleContentUpdated; +use Redaxo\Core\Content\Module; use Redaxo\Core\Content\Template; use Redaxo\Core\Core; use Redaxo\Core\Database\Sql; @@ -36,7 +37,6 @@ $articleRevision = 0; $sliceRevision = 0; -$templateAttributes = []; $warning = ''; $globalWarning = ''; @@ -45,12 +45,8 @@ $article = Sql::factory(); $article->setQuery(' - SELECT - article.*, template.attributes as template_attributes - FROM - ' . Core::getTablePrefix() . 'article as article - LEFT JOIN ' . Core::getTablePrefix() . 'template as template - ON template.id=article.template_id + SELECT article.* + FROM ' . Core::getTablePrefix() . 'article as article WHERE article.id=? AND clang_id=?', [$articleId, $clang]); @@ -61,13 +57,12 @@ return; } -// ----- ctype holen -$templateAttributes = $article->getArrayValue('template_attributes'); - -$ctypes = $templateAttributes['ctype'] ?? []; // ctypes - aus dem template +$templateKey = (string) $article->getValue('template'); +$template = Template::get($templateKey); +$contentSections = $template?->getContentSections() ?? []; $ctype = Request::request('ctype', 'int', 1); -if (!array_key_exists($ctype, $ctypes)) { +if ($ctype < 2 || !$template?->hasContentSection($ctype)) { $ctype = 1; } @@ -125,20 +120,21 @@ // ----- check module $CM = Sql::factory(); - $moduleId = null; + $moduleKey = null; if ('edit' == $function || 'delete' == $function) { // edit/ delete - $CM->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'article_slice LEFT JOIN ' . Core::getTablePrefix() . 'module ON ' . Core::getTablePrefix() . 'article_slice.module_id=' . Core::getTablePrefix() . 'module.id WHERE ' . Core::getTablePrefix() . 'article_slice.id=? AND clang_id=?', [$sliceId, $clang]); + $CM->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'article_slice WHERE id=? AND clang_id=?', [$sliceId, $clang]); if (1 == $CM->getRows()) { - $moduleId = $CM->getValue('' . Core::getTablePrefix() . 'article_slice.module_id'); + $moduleKey = (string) $CM->getValue('module'); } } else { // add - $moduleId = Request::post('module_id', 'int'); - $CM->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'module WHERE id=?', [$moduleId]); + $moduleKey = Request::post('module', 'string'); } - if (1 != $CM->getRows()) { + $module = $moduleKey ? Module::get($moduleKey) : null; + + if (null === $module || null === $moduleKey) { // ------------- MODUL IST NICHT VORHANDEN $globalWarning = I18n::msg('module_not_found'); $sliceId = 0; @@ -147,11 +143,11 @@ // ------------- MODUL IST VORHANDEN // ----- RECHTE AM MODUL ? - if ('delete' != $function && !Template::hasModule($templateAttributes, $ctype, $moduleId)) { + if ('delete' != $function && !Template::checkModuleAllowed($templateKey, $ctype, $moduleKey)) { $globalWarning = I18n::msg('no_rights_to_this_function'); $sliceId = 0; $function = ''; - } elseif (!$user->getComplexPerm('modules')->hasPerm($moduleId)) { + } elseif (!$user->getComplexPerm('modules')->hasPerm($moduleKey)) { // ----- RECHTE AM MODUL: NEIN $globalWarning = I18n::msg('no_rights_to_this_function'); $sliceId = 0; @@ -165,9 +161,16 @@ // $newsql->setDebug(); // ----- PRE SAVE ACTION [ADD/EDIT/DELETE] - $action = new ArticleSliceAction($moduleId, $function, $newsql); + $mode = match ($function) { + 'edit' => ArticleSliceAction::EDIT, + 'delete' => ArticleSliceAction::DELETE, + default => ArticleSliceAction::ADD, + }; + $action = new ArticleSliceAction($mode, $articleId, $clang, $ctype, $sliceId, $newsql); + $action->setRequestValues(); - $action->exec(ArticleSliceAction::PRESAVE); + + $module->onPresave($action); $actionMessage = implode('
    ', $action->messages); // ----- / PRE SAVE ACTION @@ -187,7 +190,7 @@ } // clone sql object to preserve values in sql object given to ArticleSliceAction - // otherwise the POSTSAVE action did not have access to values + // otherwise the postsave hook did not have access to values $newsql = clone $newsql; // ----- SAVE/UPDATE SLICE @@ -210,7 +213,7 @@ $priority = $prevSlice->getValue('priority'); $newsql->setValue('article_id', $articleId); - $newsql->setValue('module_id', $moduleId); + $newsql->setValue('module', $moduleKey); $newsql->setValue('clang_id', $clang); $newsql->setValue('ctype_id', $ctype); $newsql->setValue('revision', $sliceRevision); @@ -237,7 +240,7 @@ 'page' => Controller::getCurrentPage(), 'ctype' => $ctype, 'category_id' => $categoryId, - 'module_id' => $moduleId, + 'module_key' => $moduleKey, 'article_revision' => &$articleRevision, 'slice_revision' => &$sliceRevision, ]; @@ -275,7 +278,7 @@ 'page' => Controller::getCurrentPage(), 'ctype' => $ctype, 'category_id' => $categoryId, - 'module_id' => $moduleId, + 'module_key' => $moduleKey, 'article_revision' => &$articleRevision, 'slice_revision' => &$sliceRevision, ]; @@ -297,7 +300,7 @@ 'page' => Controller::getCurrentPage(), 'ctype' => $ctype, 'category_id' => $categoryId, - 'module_id' => $moduleId, + 'module_key' => $moduleKey, 'article_revision' => &$articleRevision, 'slice_revision' => &$sliceRevision, ]; @@ -325,7 +328,7 @@ ])); // ----- POST SAVE ACTION [ADD/EDIT/DELETE] - $action->exec(ArticleSliceAction::POSTSAVE); + $module->onPostsave($action); if ($messages = $action->messages) { $info .= '
    ' . implode('
    ', $messages); } @@ -345,15 +348,14 @@ $editPage = Controller::getPageObject('content/edit'); - foreach ($ctypes as $key => $val) { - $key = (int) $key; + foreach (count($contentSections) > 1 ? $contentSections : [] as $section) { $hasSlice = true; - if ($ctype != $key) { - $hasSlice = null !== ArticleSlice::getFirstSliceForCtype($key, $articleId, $clang); + if ($ctype != $section->id) { + $hasSlice = null !== ArticleSlice::getFirstSliceForCtype($section->id, $articleId, $clang); } - $editPage->addSubpage(new Page('ctype' . $key, I18n::translate($val)) - ->setHref(['page' => 'content/edit', 'article_id' => $articleId, 'clang' => $clang, 'ctype' => $key]) - ->setIsActive($ctype == $key) + $editPage->addSubpage(new Page('ctype' . $section->id, $section->name) + ->setHref(['page' => 'content/edit', 'article_id' => $articleId, 'clang' => $clang, 'ctype' => $section->id]) + ->setIsActive($ctype == $section->id) ->setItemAttr('class', $hasSlice ? '' : 'rex-empty'), ); } @@ -435,7 +437,7 @@ ])); // ------------------------------------------ START: MODULE EDITIEREN/ADDEN ETC. - $contentMain .= Controller::includeCurrentPageSubPath(compact('info', 'warning', 'templateAttributes', 'article', 'articleId', 'categoryId', 'clang', 'sliceId', 'sliceRevision', 'function', 'ctype', 'context')); + $contentMain .= Controller::includeCurrentPageSubPath(compact('info', 'warning', 'article', 'articleId', 'categoryId', 'clang', 'sliceId', 'sliceRevision', 'function', 'ctype', 'context')); // ------------------------------------------ END: AUSGABE // ----- EXTENSION POINT diff --git a/pages/structure/index.php b/pages/structure/index.php index 04ef2e056b..927cf9cad9 100644 --- a/pages/structure/index.php +++ b/pages/structure/index.php @@ -14,6 +14,7 @@ use Redaxo\Core\Content\Category; use Redaxo\Core\Content\CategoryHandler; use Redaxo\Core\Content\StructureContext; +use Redaxo\Core\Content\Template; use Redaxo\Core\Core; use Redaxo\Core\Database\Sql; use Redaxo\Core\ExtensionPoint\Extension; @@ -345,12 +346,10 @@ $templateSelect = new TemplateSelect($categoryId, $clang); if ($structureContext->getCategoryId() > 0 || (0 == $structureContext->getCategoryId() && !$user->getComplexPerm('structure')->hasMountpoints())) { - $templateSelect->setName('template_id'); + $templateSelect->setName('template'); $templateSelect->setSize(1); $templateSelect->setStyle('class="form-control selectpicker"'); - $templateNames = $templateSelect->getTemplates(); - $templateNames[0] = I18n::msg('template_default_name'); // --------------------- ARTIKEL LIST $artAddLink = ''; @@ -479,7 +478,7 @@ // --------------------- ARTIKEL EDIT FORM if ($canEdit && 'edit_art' == $structureContext->getFunction() && $sql->getValue('id') == $structureContext->getArticleId() && $structureContext->hasCategoryPermission()) { - $templateSelect->setSelected($sql->getValue('template_id')); + $templateSelect->setSelected((string) $sql->getValue('template')); $tmplTd = '' . $templateSelect->get() . ''; $echo .= ' @@ -539,8 +538,8 @@ $editModeUrl = $structureContext->getContext()->getUrl(['page' => 'content/edit', 'article_id' => $sql->getValue('id'), 'mode' => 'edit']); - $tmplTd = ''; - $tmpl = escape($templateNames[(int) $sql->getValue('template_id')] ?? ''); + $tmplKey = (string) $sql->getValue('template'); + $tmpl = escape(null !== ($t = Template::get($tmplKey)) ? I18n::translate($t->name) : ''); $tmplTd = '
    ' . $tmpl . '
    '; @@ -562,7 +561,8 @@ $artStatusClass = $artStatusTypes[$status][1]; $artStatusIcon = $artStatusTypes[$status][2]; - $tmpl = escape($templateNames[$sql->getValue('template_id')] ?? ''); + $tmplKey = (string) $sql->getValue('template'); + $tmpl = escape(null !== ($t = Template::get($tmplKey)) ? I18n::translate($t->name) : ''); $tmplTd = '
    ' . $tmpl . '
    '; diff --git a/pages/system/settings.php b/pages/system/settings.php index 336cacc597..b4dda1c477 100644 --- a/pages/system/settings.php +++ b/pages/system/settings.php @@ -108,14 +108,12 @@ Core::setConfig($key, $value); break; - case 'default_template_id': - $value = (int) $value; - $sql = Sql::factory(); - $sql->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'template WHERE id=? AND active=1', [$value]); - if (1 != $sql->getRows() && 0 != $value) { - $error[] = I18n::msg('system_setting_default_template_id_invalid'); + case 'default_template': + $value = (string) $value; + if ('' !== $value && !Template::exists($value)) { + $error[] = I18n::msg('system_setting_default_template_invalid'); } - Core::setConfig('default_template_id', $value); + Core::setConfig('default_template', '' !== $value ? $value : null); break; case 'article_history': @@ -342,17 +340,22 @@ $field = new SelectField(); $field->setAttribute('class', 'form-control selectpicker'); -$field->setAttribute('name', 'settings[default_template_id]'); -$field->setLabel(I18n::msg('system_setting_default_template_id')); +$field->setAttribute('name', 'settings[default_template]'); +$field->setLabel(I18n::msg('system_setting_default_template')); $select = $field->getSelect(); $select->setSize(1); -$select->setSelected(Template::getDefaultId()); +$defaultTemplate = Template::getDefaultKey(); +if (null !== $defaultTemplate) { + $select->setSelected($defaultTemplate); +} $templates = Template::getTemplatesForCategory(0); if (empty($templates)) { - $select->addOption(I18n::msg('option_no_template'), 0); + $select->addOption(I18n::msg('option_no_template'), ''); } else { - $select->addArrayOptions(array_map(I18n::translate(...), $templates)); + foreach ($templates as $key => $template) { + $select->addOption(I18n::translate($template->name), $key); + } } $content .= $field->get(); diff --git a/rector.php b/rector.php index c48ba7ac5a..18c94996cb 100644 --- a/rector.php +++ b/rector.php @@ -365,13 +365,11 @@ 'rex_linkmap_category_tree' => Content\Linkmap\CategoryTree::class, 'rex_linkmap_tree_renderer' => Content\Linkmap\CategoryTreeRenderer::class, 'rex_module' => Content\Module::class, - 'rex_module_cache' => Content\ModuleCache::class, 'rex_module_perm' => Content\ModulePermission::class, 'rex_structure_context' => Content\StructureContext::class, 'rex_structure_element' => Content\StructureElement::class, 'rex_structure_perm' => Content\StructurePermission::class, 'rex_template' => Content\Template::class, - 'rex_template_cache' => Content\TemplateCache::class, 'rex_backend_login' => Security\BackendLogin::class, 'rex_backend_password_policy' => Security\BackendPasswordPolicy::class, 'rex_complex_perm' => Security\ComplexPermission::class, @@ -425,6 +423,11 @@ new MethodCallRename(Content\ArticleContentBase::class, 'getClang', 'getClangId'), new MethodCallRename(Content\StructureElement::class, 'getClang', 'getClangId'), + new MethodCallRename(Content\StructureElement::class, 'getTemplateId', 'getTemplateKey'), + new MethodCallRename(Content\Template::class, 'forKey', 'get'), + new MethodCallRename(Content\Template::class, 'getCtypes', 'getContentSections'), + new MethodCallRename(Content\Template::class, 'getDefaultId', 'getDefaultKey'), + new MethodCallRename(Content\Module::class, 'forKey', 'get'), new MethodCallRename(MediaManager\ManagedMedia::class, 'getImageWidth', 'getWidth'), new MethodCallRename(MediaManager\ManagedMedia::class, 'getImageHeight', 'getHeight'), @@ -519,7 +522,7 @@ new MethodCallToPropertyFetch(Content\ArticleSlice::class, 'getRevision', 'revision'), new MethodCallToPropertyFetch(Content\ArticleSlice::class, 'getPriority', 'priority'), - new MethodCallToPropertyFetch(Content\ArticleSliceAction::class, 'getEvent', 'event'), + new MethodCallToPropertyFetch(Content\ArticleSliceAction::class, 'getEvent', 'mode'), new MethodCallToPropertyFetch(Content\ArticleSliceAction::class, 'getSave', 'save'), // todo setter new MethodCallToPropertyFetch(Content\ArticleSliceAction::class, 'getMessages', 'messages'), diff --git a/setup/install.php b/setup/install.php index 3f59f25eee..a06e52ba51 100644 --- a/setup/install.php +++ b/setup/install.php @@ -33,21 +33,6 @@ ->setPrimaryKey(['namespace', 'key']) ->ensure(); -Table::get(Core::getTable('action')) - ->ensureColumn(new Column('id', 'int(10) unsigned', false, null, 'AUTO_INCREMENT')) - ->ensureColumn(new Column('key', 'varchar(191)', true)) - ->ensureColumn(new Column('name', 'varchar(255)')) - ->ensureColumn(new Column('preview', 'text', true)) - ->ensureColumn(new Column('presave', 'text', true)) - ->ensureColumn(new Column('postsave', 'text', true)) - ->ensureColumn(new Column('previewmode', 'tinyint(4)', true)) - ->ensureColumn(new Column('presavemode', 'tinyint(4)', true)) - ->ensureColumn(new Column('postsavemode', 'tinyint(4)', true)) - ->ensureGlobalColumns() - ->setPrimaryKey('id') - ->ensureIndex(new Index('key', ['key'], Index::UNIQUE)) - ->ensure(); - Table::get(Core::getTable('article')) ->ensureColumn(new Column('pid', 'int(10) unsigned', false, null, 'AUTO_INCREMENT')) ->ensureColumn(new Column('id', 'int(10) unsigned')) @@ -59,7 +44,7 @@ ->ensureColumn(new Column('priority', 'int(10) unsigned')) ->ensureColumn(new Column('path', 'varchar(255)')) ->ensureColumn(new Column('status', 'tinyint(1)')) - ->ensureColumn(new Column('template_id', 'int(10) unsigned')) + ->ensureColumn(new Column('template', 'varchar(191)', true)) ->ensureColumn(new Column('clang_id', 'int(10) unsigned')) ->ensureGlobalColumns() ->setPrimaryKey('pid') @@ -74,7 +59,7 @@ ->ensureColumn(new Column('article_id', 'int(10) unsigned')) ->ensureColumn(new Column('clang_id', 'int(10) unsigned')) ->ensureColumn(new Column('ctype_id', 'int(10) unsigned')) - ->ensureColumn(new Column('module_id', 'int(10) unsigned')) + ->ensureColumn(new Column('module', 'varchar(191)')) ->ensureColumn(new Column('revision', 'int(11)')) ->ensureColumn(new Column('priority', 'int(10) unsigned')) ->ensureColumn(new Column('status', 'tinyint(1)', false, '1')) @@ -140,7 +125,7 @@ ->ensureColumn(new Column('linklist10', 'text', true)) ->ensureGlobalColumns() ->setPrimaryKey('id') - ->ensureIndex(new Index('slice_priority', ['article_id', 'priority', 'module_id'])) + ->ensureIndex(new Index('slice_priority', ['article_id', 'priority', 'module'])) ->ensureIndex(new Index('find_slices', ['clang_id', 'article_id'])) ->removeIndex('clang_id') ->removeIndex('article_id') @@ -216,58 +201,15 @@ ->ensureColumn(new Column('linklist9', 'text', true)) ->ensureColumn(new Column('linklist10', 'text', true)) ->ensureColumn(new Column('article_id', 'int(10) unsigned')) - ->ensureColumn(new Column('module_id', 'int(10) unsigned')) + ->ensureColumn(new Column('module', 'varchar(191)')) ->ensureGlobalColumns() ->ensureColumn(new Column('revision', 'int(11)')) ->setPrimaryKey('id') ->ensureIndex(new Index('snapshot', ['article_id', 'clang_id', 'revision', 'history_date'])) ->ensure(); -Table::get(Core::getTable('module')) - ->ensureColumn(new Column('id', 'int(10) unsigned', false, null, 'AUTO_INCREMENT')) - ->ensureColumn(new Column('key', 'varchar(191)', true)) - ->ensureColumn(new Column('name', 'varchar(255)')) - ->ensureColumn(new Column('output', 'mediumtext')) - ->ensureColumn(new Column('input', 'mediumtext')) - ->ensureGlobalColumns() - ->setPrimaryKey('id') - ->ensureIndex(new Index('key', ['key'], Index::UNIQUE)) - ->ensure(); - -Table::get(Core::getTable('module_action')) - ->ensureColumn(new Column('id', 'int(10) unsigned', false, null, 'AUTO_INCREMENT')) - ->ensureColumn(new Column('module_id', 'int(10) unsigned')) - ->ensureColumn(new Column('action_id', 'int(10) unsigned')) - ->setPrimaryKey('id') - ->ensure(); - -Table::get(Core::getTable('template')) - ->ensureColumn(new Column('id', 'int(10) unsigned', false, null, 'AUTO_INCREMENT')) - ->ensureColumn(new Column('key', 'varchar(191)', true)) - ->ensureColumn(new Column('name', 'varchar(255)', true)) - ->ensureColumn(new Column('content', 'mediumtext', true)) - ->ensureColumn(new Column('active', 'tinyint(1)', true)) - ->ensureGlobalColumns() - ->ensureColumn(new Column('attributes', 'text', true)) - ->setPrimaryKey('id') - ->ensureIndex(new Index('key', ['key'], Index::UNIQUE)) - ->ensure(); - $sql = Sql::factory(); $sql->setQuery('UPDATE ' . Core::getTablePrefix() . 'article_slice set revision=0 where revision<1 or revision IS NULL'); -$sql->setQuery('SELECT 1 FROM ' . Core::getTable('template') . ' LIMIT 1'); -if (!$sql->getRows()) { - $sql - ->setTable(Core::getTable('template')) - ->setValue('id', 1) - ->setValue('name', 'Default') - ->setValue('content', 'getArticle() ?>') - ->setValue('active', 1) - ->setValue('attributes', '{"ctype":[],"modules":{"1":{"all":"1"}},"categories":{"all":"1"}}') - ->addGlobalCreateFields() - ->addGlobalUpdateFields() - ->insert(); -} Table::get(Core::getTable('cronjob')) ->ensurePrimaryIdColumn() diff --git a/src/Content/ApiFunction/ArticleAdd.php b/src/Content/ApiFunction/ArticleAdd.php index 70e82306f3..362b79e95c 100644 --- a/src/Content/ApiFunction/ArticleAdd.php +++ b/src/Content/ApiFunction/ArticleAdd.php @@ -32,7 +32,7 @@ public function execute() $data = []; $data['name'] = Request::post('article-name', 'string'); $data['priority'] = Request::post('article-position', 'int'); - $data['template_id'] = Request::post('template_id', 'int'); + $data['template'] = Request::post('template', 'string'); $data['category_id'] = $categoryId; return new Result(true, ArticleHandler::addArticle($data)); } diff --git a/src/Content/ApiFunction/ArticleEdit.php b/src/Content/ApiFunction/ArticleEdit.php index 898bef5033..7bee7060d8 100644 --- a/src/Content/ApiFunction/ArticleEdit.php +++ b/src/Content/ApiFunction/ArticleEdit.php @@ -35,7 +35,7 @@ public function execute() $data = []; $data['priority'] = Request::post('article-position', 'int'); $data['name'] = Request::post('article-name', 'string'); - $data['template_id'] = Request::post('template_id', 'int'); + $data['template'] = Request::post('template', 'string'); return new Result(true, ArticleHandler::editArticle($articleId, $clang, $data)); } diff --git a/src/Content/ApiFunction/ArticleSliceMove.php b/src/Content/ApiFunction/ArticleSliceMove.php index 651260b2c4..d008ec93dd 100644 --- a/src/Content/ApiFunction/ArticleSliceMove.php +++ b/src/Content/ApiFunction/ArticleSliceMove.php @@ -8,6 +8,7 @@ use Redaxo\Core\ApiFunction\Result; use Redaxo\Core\Content\Article; use Redaxo\Core\Content\ContentHandler; +use Redaxo\Core\Content\Module; use Redaxo\Core\Core; use Redaxo\Core\Database\Sql; use Redaxo\Core\Http\Request; @@ -45,14 +46,18 @@ public function execute() // modul und rechte vorhanden ? $CM = Sql::factory(); - $CM->setQuery('select * from ' . Core::getTablePrefix() . 'article_slice left join ' . Core::getTablePrefix() . 'module on ' . Core::getTablePrefix() . 'article_slice.module_id=' . Core::getTablePrefix() . 'module.id where ' . Core::getTablePrefix() . 'article_slice.id=? and clang_id=?', [$sliceId, $clang]); + $CM->setQuery('select * from ' . Core::getTablePrefix() . 'article_slice where id=? and clang_id=?', [$sliceId, $clang]); if (1 != $CM->getRows()) { throw new ApiFunctionException(I18n::msg('module_not_found')); } - $moduleId = (int) $CM->getValue(Core::getTablePrefix() . 'article_slice.module_id'); + + $moduleKey = (string) $CM->getValue(Core::getTablePrefix() . 'article_slice.module'); + if (!Module::exists($moduleKey)) { + throw new ApiFunctionException(I18n::msg('module_not_found')); + } // ----- RECHTE AM MODUL ? - if ($user->getComplexPerm('modules')->hasPerm($moduleId)) { + if ($user->getComplexPerm('modules')->hasPerm($moduleKey)) { $message = ContentHandler::moveSlice($sliceId, $clang, $direction); } else { throw new ApiFunctionException(I18n::msg('no_rights_to_this_function')); diff --git a/src/Content/ArticleContent.php b/src/Content/ArticleContent.php index 5869b6d559..80bf08fe2f 100644 --- a/src/Content/ArticleContent.php +++ b/src/Content/ArticleContent.php @@ -62,12 +62,12 @@ public function setArticleId($articleId) $rexArticle = Article::get($articleId, $this->clang); if ($rexArticle instanceof Article) { $this->category_id = $rexArticle->getCategoryId(); - $this->template_id = $rexArticle->getTemplateId(); + $this->template = $rexArticle->getTemplateKey(); return true; } $this->article_id = 0; - $this->template_id = 0; + $this->template = null; $this->category_id = 0; return false; } diff --git a/src/Content/ArticleContentBase.php b/src/Content/ArticleContentBase.php index 4a5609b963..fc71592a62 100644 --- a/src/Content/ArticleContentBase.php +++ b/src/Content/ArticleContentBase.php @@ -12,8 +12,8 @@ use Redaxo\Core\Http\Request; use Redaxo\Core\Language\Language; use Redaxo\Core\Translation\I18n; -use Redaxo\Core\Util\Stream; use Redaxo\Core\Util\Timer; +use Redaxo\Core\Util\Type; use function assert; use function in_array; @@ -34,10 +34,7 @@ class ArticleContentBase /** @var bool */ public $debug = false; - /** @var int */ - public $template_id = 0; - /** @var array */ - public $template_attributes; + public ?string $template = null; /** @var int */ protected $category_id; @@ -163,30 +160,26 @@ public function setArticleId($articleId) $sql->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'article WHERE ' . Core::getTablePrefix() . 'article.id=? AND clang_id=?', [$articleId, $this->clang]); if (1 == $sql->getRows()) { - $this->template_id = (int) $this->getValue('template_id'); + $template = $this->getValue('template'); + $this->template = $template ? (string) $template : null; $this->category_id = (int) $this->getValue('category_id'); return true; } $this->article_id = 0; - $this->template_id = 0; + $this->template = null; $this->category_id = 0; return false; } - /** - * @param int $templateId - * @return void - */ - public function setTemplateId($templateId) + public function setTemplateKey(?string $templateKey): void { - $this->template_id = $templateId; + $this->template = $templateKey; } - /** @return int */ - public function getTemplateId() + public function getTemplateKey(): ?string { - return $this->template_id; + return $this->template; } /** @@ -305,15 +298,24 @@ private function _hasValue($value) * Outputs a slice. * * @param Sql $artDataSql A Sql instance containing all slice and module data - * @param int $moduleIdToAdd The id of the module, which was selected using the ModuleSelect - * - * @return string + * @param string $moduleKeyToAdd The key of the module, which was selected using the ModuleSelect */ - protected function outputSlice(Sql $artDataSql, $moduleIdToAdd) + protected function outputSlice(Sql $artDataSql, string $moduleKeyToAdd): string { + $moduleKey = (string) $artDataSql->getValue(Core::getTablePrefix() . 'article_slice.module'); + $slice = ArticleSlice::fromSql($artDataSql); + + $module = Module::get($moduleKey); + + if (null === $module) { + return ''; + } + + $output = $module->output($slice); + $output = Extension::registerPoint(new ExtensionPoint( 'SLICE_OUTPUT', - (string) $artDataSql->getValue(Core::getTablePrefix() . 'module.output'), + $output, [ 'article_id' => $this->article_id, 'clang' => $this->clang, @@ -321,9 +323,7 @@ protected function outputSlice(Sql $artDataSql, $moduleIdToAdd) ], )); - $moduleId = (int) $artDataSql->getValue(Core::getTablePrefix() . 'module.id'); - - return $this->getStreamOutput('module/' . $moduleId . '/output', $output); + return $output; } public function getCurrentSlice(): ArticleSlice @@ -397,29 +397,15 @@ public function getArticle($curctype = -1) return $CONTENT; } - /** - * Method which gets called, before the slices of the article are processed. - * - * @param string $articleContent The content of the article - * @param int $moduleId A module id - * - * @return string - */ - protected function preArticle($articleContent, $moduleId) + /** Method which gets called, before the slices of the article are processed. */ + protected function preArticle(string $articleContent, string $moduleKey): string { // nichts tun return $articleContent; } - /** - * Method which gets called, after all slices have been processed. - * - * @param string $articleContent The content of the article - * @param int $moduleId A module id - * - * @return string - */ - protected function postArticle($articleContent, $moduleId) + /** Method which gets called, after all slices have been processed. */ + protected function postArticle(string $articleContent, string $moduleKey): string { // nichts tun return $articleContent; @@ -433,15 +419,21 @@ protected function postArticle($articleContent, $moduleId) */ public function getArticleTemplate() { - if (0 != $this->template_id && 0 != $this->article_id) { + if (null !== $this->template && 0 != $this->article_id) { + $template = Template::get($this->template); + + if (null === $template) { + return 'no template'; + } + ob_start(); try { ob_implicit_flush(false); - $TEMPLATE = new Template($this->template_id); + Timer::measure('Template: ' . $template->key, function () use ($template) { + Type::instanceOf($this, ArticleContent::class); - Timer::measure('Template: ' . ($TEMPLATE->getKey() ?? $TEMPLATE->getId()), function () use ($TEMPLATE) { - require Stream::factory('template/' . $this->template_id, $TEMPLATE->getTemplate()); + echo $template->render($this); }); } finally { $CONTENT = ob_get_clean(); @@ -453,35 +445,6 @@ public function getArticleTemplate() return 'no template'; } - /** - * @param string $path - * @param string $content - * @return string - */ - protected function getStreamOutput($path, $content) - { - if (!$this->eval) { - $key = 'EOD_' . strtoupper(sha1((string) time())); - return "require \\Redaxo\\Core\\Util\\Stream::factory('$path', <<<'$key'\n$content\n$key);\n"; - } - - ob_start(); - try { - ob_implicit_flush(false); - - $__stream = Stream::factory($path, $content); - - $sandbox = function () use ($__stream) { - require $__stream; - }; - $sandbox(); - } finally { - $CONTENT = ob_get_clean(); - } - - return $CONTENT; - } - /** * @param string $content * @return string @@ -505,17 +468,15 @@ function (array $matches) { private function renderSlices(string $articleLimit, string $sliceLimit): void { - $moduleId = Request::request('module_id', 'int'); + $moduleKey = Request::request('module', 'string', ''); // ---------- alle teile/slices eines artikels auswaehlen $prefix = Core::getTablePrefix(); $query = <<clang} AND @@ -534,7 +495,7 @@ private function renderSlices(string $articleLimit, string $sliceLimit): void // pre hook $articleContent = ''; - $articleContent = $this->preArticle($articleContent, $moduleId); + $articleContent = $this->preArticle($articleContent, $moduleKey); // ---------- SLICES AUSGEBEN @@ -547,7 +508,12 @@ private function renderSlices(string $articleLimit, string $sliceLimit): void for ($i = 0; $i < $rows; ++$i) { $sliceId = (int) $artDataSql->getValue($prefix . 'article_slice.id'); $sliceCtypeId = (int) $artDataSql->getValue($prefix . 'article_slice.ctype_id'); - $sliceModuleId = (int) $artDataSql->getValue($prefix . 'module.id'); + /** + * Module key from internal DB table, safe to embed in generated cache code. + * @psalm-taint-escape html + * @psalm-taint-escape has_quotes + */ + $sliceModuleKey = (string) $artDataSql->getValue($prefix . 'article_slice.module'); // ----- ctype unterscheidung if ('edit' != $this->mode && !$this->eval) { @@ -560,13 +526,18 @@ private function renderSlices(string $articleLimit, string $sliceLimit): void $slice = ArticleSlice::fromSql($artDataSql); $articleContent .= '$this->currentSlice = ' . var_export($slice, true) . ";\n"; + $articleContent .= 'echo \\' . Module::class . '::get(' . var_export($sliceModuleKey, true) . ')?->output($this->getCurrentSlice()) ?? \'\';' . "\n"; } // ------------- EINZELNER SLICE - AUSGABE - $sliceContent = $this->outputSlice( - $artDataSql, - $moduleId, - ); + if ('edit' == $this->mode || $this->eval) { + $sliceContent = $this->outputSlice( + $artDataSql, + $moduleKey, + ); + } else { + $sliceContent = ''; + } // --------------- ENDE EINZELNER SLICE // --------------- EP: SLICE_SHOW @@ -578,7 +549,7 @@ private function renderSlices(string $articleLimit, string $sliceLimit): void 'article_id' => $this->article_id, 'clang' => $this->clang, 'ctype' => $sliceCtypeId, - 'module_id' => $sliceModuleId, + 'module_key' => $sliceModuleKey, 'slice_id' => $sliceId, 'function' => $this->function, 'function_slice_id' => $this->slice_id, @@ -607,7 +578,7 @@ private function renderSlices(string $articleLimit, string $sliceLimit): void } // ----- post hook - $articleContent = $this->postArticle($articleContent, $moduleId); + $articleContent = $this->postArticle($articleContent, $moduleKey); // -------------------------- schreibe content echo $articleContent; diff --git a/src/Content/ArticleContentEditor.php b/src/Content/ArticleContentEditor.php index 5b41b6b942..14c21c633c 100644 --- a/src/Content/ArticleContentEditor.php +++ b/src/Content/ArticleContentEditor.php @@ -27,7 +27,7 @@ */ class ArticleContentEditor extends ArticleContent { - /** @var array> */ + /** @var array> */ private $MODULESELECT; /** @var int */ @@ -44,13 +44,13 @@ public function __construct($articleId = null, $clang = null) parent::__construct($articleId, $clang); } - protected function outputSlice(Sql $artDataSql, $moduleIdToAdd) + protected function outputSlice(Sql $artDataSql, string $moduleKeyToAdd): string { if ('edit' != $this->mode) { // ----- wenn mode nicht edit $sliceContent = parent::outputSlice( $artDataSql, - $moduleIdToAdd, + $moduleKeyToAdd, ); } else { $sliceId = (int) $artDataSql->getValue(Core::getTablePrefix() . 'article_slice.id'); @@ -58,20 +58,19 @@ protected function outputSlice(Sql $artDataSql, $moduleIdToAdd) $sliceStatus = (int) $artDataSql->getValue(Core::getTablePrefix() . 'article_slice.status'); $sliceRevision = (int) $artDataSql->getValue(Core::getTablePrefix() . 'article_slice.revision'); - $moduleInput = (string) $artDataSql->getValue(Core::getTablePrefix() . 'module.input'); - $moduleOutput = (string) $artDataSql->getValue(Core::getTablePrefix() . 'module.output'); - $moduleId = (int) $artDataSql->getValue(Core::getTablePrefix() . 'module.id'); + $moduleKey = (string) $artDataSql->getValue(Core::getTablePrefix() . 'article_slice.module'); + $module = Module::get($moduleKey); // ----- add select box einbauen $sliceContent = $this->getModuleSelect($sliceId); if ('add' == $this->function && $this->slice_id == $sliceId) { - $sliceContent .= $this->addSlice($sliceId, $moduleIdToAdd); + $sliceContent .= $this->addSlice($sliceId, $moduleKeyToAdd); } $panel = ''; // ----- Display message at current slice - // if(rex::requireUser()->getComplexPerm('modules')->hasPerm($moduleId)) { + // if(rex::requireUser()->getComplexPerm('modules')->hasPerm($moduleKey)) { if ('add' != $this->function && $this->slice_id == $sliceId) { $msg = ''; if ('' != $this->warning) { @@ -85,36 +84,52 @@ protected function outputSlice(Sql $artDataSql, $moduleIdToAdd) // } // ----- EDIT/DELETE BLOCK - Wenn Rechte vorhanden - if (Core::requireUser()->getComplexPerm('modules')->hasPerm($moduleId)) { + if (Core::requireUser()->getComplexPerm('modules')->hasPerm($moduleKey)) { if ('edit' == $this->function && $this->slice_id == $sliceId) { // **************** Aktueller Slice // ----- PRE VIEW ACTION [EDIT] - $action = new ArticleSliceAction($moduleId, 'edit', $artDataSql); - if ('post' == Request::requestMethod() && 'edit' == Request::request('function', 'string')) { - $action->setRequestValues(); + if (null !== $module) { + $action = new ArticleSliceAction( + ArticleSliceAction::EDIT, + $this->article_id, + $this->clang, + $sliceCtype, + $sliceId, + $artDataSql, + ); + if ('post' == Request::requestMethod() && 'edit' == Request::request('function', 'string')) { + $action->setRequestValues(); + } + $module->onPreview($action); } - $action->exec(ArticleSliceAction::PREVIEW); // ----- / PRE VIEW ACTION - return $sliceContent . $this->editSlice($sliceId, $moduleInput, $sliceCtype, $moduleId, $artDataSql); + $slice = ArticleSlice::fromSql($artDataSql); + + return $sliceContent . $this->editSlice($sliceId, $slice, $sliceCtype, $moduleKey, $artDataSql); } } // Modulinhalt ausgeben - $content = $this->getWrappedModuleOutput($moduleId, $moduleOutput); + if (null !== $module) { + $slice = ArticleSlice::fromSql($artDataSql); + $content = $module->output($slice); + } else { + $content = ''; + } // EP for changing the module preview $panel .= Extension::registerPoint(new ExtensionPoint('SLICE_BE_PREVIEW', $content, [ 'article_id' => $this->article_id, 'clang' => $this->clang, 'ctype' => $this->ctype, - 'module_id' => $moduleId, + 'module_key' => $moduleKey, 'slice_id' => $sliceId, 'revision' => $sliceRevision, ])); $fragment = new Fragment(); - $fragment->setVar('title', $this->getSliceHeading($artDataSql), false); + $fragment->setVar('title', $this->getSliceHeading($moduleKey), false); $fragment->setVar('options', $this->getSliceMenu($artDataSql), false); $fragment->setVar('body', $panel, false); $section = $fragment->parse('core/page/section.php'); @@ -130,16 +145,11 @@ protected function outputSlice(Sql $artDataSql, $moduleIdToAdd) return $sliceContent; } - /** - * Returns the slice heading. - * - * @param Sql $artDataSql Sql instance containing all the slice and module information - * - * @return string - */ - private function getSliceHeading(Sql $artDataSql) + /** Returns the slice heading. */ + private function getSliceHeading(string $moduleKey): string { - return I18n::translate((string) $artDataSql->getValue(Core::getTablePrefix() . 'module.name')); + $module = Module::get($moduleKey); + return null !== $module ? I18n::translate($module->name) : $moduleKey; } /** @@ -155,8 +165,8 @@ private function getSliceMenu(Sql $artDataSql) $sliceCtype = (int) $artDataSql->getValue(Core::getTablePrefix() . 'article_slice.ctype_id'); $sliceStatus = (int) $artDataSql->getValue(Core::getTablePrefix() . 'article_slice.status'); - $moduleId = (int) $artDataSql->getValue(Core::getTablePrefix() . 'module.id'); - $moduleName = I18n::translate((string) $artDataSql->getValue(Core::getTablePrefix() . 'module.name')); + $moduleKey = (string) $artDataSql->getValue(Core::getTablePrefix() . 'article_slice.module'); + $moduleName = $this->getSliceHeading($moduleKey); $context = new Context([ 'page' => Controller::getCurrentPage(), @@ -174,8 +184,8 @@ private function getSliceMenu(Sql $artDataSql) $menuStatusAction = []; $menuMoveupAction = []; $menuMovedownAction = []; - if (Core::requireUser()->getComplexPerm('modules')->hasPerm($moduleId)) { - $templateHasModule = Template::hasModule($this->template_attributes, $this->ctype, $moduleId); + if (Core::requireUser()->getComplexPerm('modules')->hasPerm($moduleKey)) { + $templateHasModule = !$this->template || Template::checkModuleAllowed($this->template, $this->ctype, $moduleKey); if ($templateHasModule) { // edit $item = []; @@ -245,9 +255,9 @@ private function getSliceMenu(Sql $artDataSql) $this->article_id, $this->clang, $sliceCtype, - $moduleId, + $moduleKey, $sliceId, - Core::requireUser()->getComplexPerm('modules')->hasPerm($moduleId), + Core::requireUser()->getComplexPerm('modules')->hasPerm($moduleKey), )); $actionItems = []; @@ -288,24 +298,9 @@ private function getSliceMenu(Sql $artDataSql) $headerRight .= $fragment->parse('core/structure/content/slice_menu_move.php'); } - // $header_right = $header_right != '' ? '
    ' . $header_right . '
    ' : ''; - return $headerRight; } - /** - * Wraps the output of a module. - * - * @param int $moduleId The id of the module - * @param string $moduleOutput The output of the module - * - * @return string - */ - private function getWrappedModuleOutput($moduleId, $moduleOutput) - { - return $this->getStreamOutput('module/' . (int) $moduleId . '/output', $moduleOutput); - } - /** * @param int $sliceId * @return string @@ -328,10 +323,9 @@ private function getModuleSelect($sliceId) if (isset($this->MODULESELECT[$this->ctype])) { foreach ($this->MODULESELECT[$this->ctype] as $module) { $item = []; - $item['id'] = (int) $module['id']; $item['key'] = $module['key']; $item['title'] = escape($module['name']); - $item['href'] = $context->getUrl(['module_id' => $module['id']]) . '#slice-add-pos-' . $position; + $item['href'] = $context->getUrl(['module' => $module['key']]) . '#slice-add-pos-' . $position; /** * It is intended to pass raw values to fragment here. * @psalm-taint-escape html @@ -364,36 +358,29 @@ private function getModuleSelect($sliceId) return $fragment->parse('core/structure/content/slice_list_item.php'); } - protected function preArticle($articleContent, $moduleId) + protected function preArticle(string $articleContent, string $moduleKey): string { // ---------- moduleselect: nur module nehmen auf die der user rechte hat if ('edit' == $this->mode) { - $MODULE = Sql::factory(); - $modules = $MODULE->getArray('select * from ' . Core::getTablePrefix() . 'module order by name'); - - $templateCtypes = $this->template_attributes['ctype'] ?? []; - // wenn keine ctyes definiert sind, gibt es immer den CTYPE=1 - if (0 == count($templateCtypes)) { - $templateCtypes = [1 => 'default']; - } + $template = $this->template ? Template::get($this->template) : null; + $contentSections = $template?->getContentSections() ?? [new ContentSection(1, 'Content')]; $this->MODULESELECT = []; - foreach ($templateCtypes as $ctId => $ctName) { - foreach ($modules as $m) { - $id = (int) $m['id']; - if (Core::requireUser()->getComplexPerm('modules')->hasPerm($id)) { - if (Template::hasModule($this->template_attributes, $ctId, $id)) { - $this->MODULESELECT[$ctId][] = ['name' => I18n::translate((string) $m['name'], false), 'id' => $id, 'key' => (string) $m['key']]; + foreach ($contentSections as $section) { + foreach (Module::getAll() as $module) { + if (Core::requireUser()->getComplexPerm('modules')->hasPerm($module->key)) { + if (!$template || $template->isModuleAllowed($section, $module)) { + $this->MODULESELECT[$section->id][] = ['name' => I18n::translate($module->name), 'key' => $module->key]; } } } } } - return parent::preArticle($articleContent, $moduleId); + return parent::preArticle($articleContent, $moduleKey); } - protected function postArticle($articleContent, $moduleId) + protected function postArticle(string $articleContent, string $moduleKey): string { // special identifier for the slot behind the last slice $behindlastSliceId = -1; @@ -402,7 +389,7 @@ protected function postArticle($articleContent, $moduleId) if ('edit' == $this->mode) { if ('add' == $this->function && $this->slice_id == $behindlastSliceId) { ++$this->sliceAddPosition; - $sliceContent = $this->addSlice($behindlastSliceId, $moduleId); + $sliceContent = $this->addSlice($behindlastSliceId, $moduleKey); } else { // ----- BLOCKAUSWAHL - SELECT $sliceContent = $this->getModuleSelect($behindlastSliceId); @@ -413,39 +400,35 @@ protected function postArticle($articleContent, $moduleId) return $articleContent; } - // ----- ADD Slice - - /** - * @param int $sliceId - * @param int $moduleId - * @return string - */ - protected function addSlice($sliceId, $moduleId) + protected function addSlice(int $sliceId, string $moduleKey): string { - $sliceId = (int) $sliceId; - $moduleId = (int) $moduleId; + $module = Module::get($moduleKey); - $MOD = Sql::factory(); - $MOD->setQuery('SELECT * FROM ' . Core::getTablePrefix() . 'module WHERE id="' . $moduleId . '"'); - - if (1 != $MOD->getRows()) { + if (null === $module) { return Message::error(I18n::msg('module_doesnt_exist')); } $initDataSql = Sql::factory(); $initDataSql - ->setValue('module_id', $moduleId) + ->setValue('module', $moduleKey) ->setValue('ctype_id', $this->ctype); // ----- PRE VIEW ACTION [ADD] - $action = new ArticleSliceAction($moduleId, 'add', $initDataSql); + $action = new ArticleSliceAction( + ArticleSliceAction::ADD, + $this->article_id, + $this->clang, + $this->ctype, + 0, + $initDataSql, + ); $action->setRequestValues(); - $action->exec(ArticleSliceAction::PREVIEW); + $module->onPreview($action); // ----- / PRE VIEW ACTION - $this->currentSlice = ArticleSlice::forNewSlice($this->article_id, $this->clang, $this->ctype, $moduleId, $this->sliceAddPosition, $this->slice_revision); + $this->currentSlice = ArticleSlice::forNewSlice($this->article_id, $this->clang, $this->ctype, $moduleKey, $this->sliceAddPosition, $this->slice_revision); - $moduleInput = $this->getStreamOutput('module/' . $moduleId . '/input', (string) $MOD->getValue('input')); + $moduleInput = $module->input($this->currentSlice); $this->currentSlice = null; @@ -475,7 +458,7 @@ protected function addSlice($sliceId, $moduleId)
    ' . I18n::msg('add_block') . ' - +
    @@ -487,7 +470,7 @@ protected function addSlice($sliceId, $moduleId) $fragment = new Fragment(); $fragment->setVar('before', $msg, false); $fragment->setVar('class', 'add', false); - $fragment->setVar('title', I18n::msg('module') . ': ' . I18n::translate((string) $MOD->getValue('name')), false); + $fragment->setVar('title', I18n::msg('module') . ': ' . I18n::translate($module->name), false); $fragment->setVar('body', $panel, false); $fragment->setVar('footer', $sliceFooter, false); $sliceContent = $fragment->parse('core/page/section.php'); @@ -499,16 +482,7 @@ protected function addSlice($sliceId, $moduleId) return $fragment->parse('core/structure/content/slice_list_item.php'); } - // ----- EDIT Slice - /** - * @param int $sliceId - * @param string $moduleInput - * @param int $ctypeId - * @param int $moduleId - * @param Sql $artDataSql - * @return string - */ - protected function editSlice($sliceId, $moduleInput, $ctypeId, $moduleId, $artDataSql) + protected function editSlice(int $sliceId, ArticleSlice $slice, int $ctypeId, string $moduleKey, Sql $artDataSql): string { $msg = ''; if ($this->slice_id == $sliceId) { @@ -538,21 +512,24 @@ protected function editSlice($sliceId, $moduleInput, $ctypeId, $moduleId, $artDa $fragment->setVar('elements', $formElements, false); $sliceFooter = $fragment->parse('core/form/submit.php'); + $module = Module::get($moduleKey); + $moduleInput = $module ? $module->input($slice) : ''; + $panel = '
    ' . I18n::msg('edit_block') . ' - +
    - ' . $msg . $this->getStreamOutput('module/' . $moduleId . '/input', $moduleInput) . ' + ' . $msg . $moduleInput . '
    '; $fragment = new Fragment(); $fragment->setVar('class', 'edit', false); - $fragment->setVar('title', $this->getSliceHeading($artDataSql), false); + $fragment->setVar('title', $this->getSliceHeading($moduleKey), false); $fragment->setVar('options', $this->getSliceMenu($artDataSql), false); $fragment->setVar('body', $panel, false); $fragment->setVar('footer', $sliceFooter, false); diff --git a/src/Content/ArticleHandler.php b/src/Content/ArticleHandler.php index f8308b8936..41f25b7fb0 100644 --- a/src/Content/ArticleHandler.php +++ b/src/Content/ArticleHandler.php @@ -51,10 +51,10 @@ public static function addArticle(array $data): string // Wenn Template nicht vorhanden, dann entweder erlaubtes nehmen // oder leer setzen. - if (!isset($templates[$data['template_id']])) { - $data['template_id'] = 0; + if (!isset($templates[$data['template']])) { + $data['template'] = null; if (count($templates) > 0) { - $data['template_id'] = key($templates); + $data['template'] = key($templates); } } @@ -86,7 +86,7 @@ public static function addArticle(array $data): string $AART->setValue('path', $path); $AART->setValue('startarticle', 0); $AART->setValue('status', 0); - $AART->setValue('template_id', $data['template_id']); + $AART->setValue('template', $data['template']); $AART->addGlobalCreateFields($user); $AART->addGlobalUpdateFields($user); @@ -105,7 +105,7 @@ public static function addArticle(array $data): string 'parent_id' => $data['category_id'], 'priority' => $data['priority'], 'path' => $path, - 'template_id' => $data['template_id'], + 'template_key' => $data['template'], 'data' => $data, ])); } @@ -143,10 +143,10 @@ public static function editArticle(int $articleId, int $clang, array $data): str // Wenn Template nicht vorhanden, dann entweder erlaubtes nehmen // oder leer setzen. - if (!isset($templates[$data['template_id']])) { - $data['template_id'] = 0; + if (!isset($templates[$data['template']])) { + $data['template'] = null; if (count($templates) > 0) { - $data['template_id'] = key($templates); + $data['template'] = key($templates); } } @@ -164,7 +164,7 @@ public static function editArticle(int $articleId, int $clang, array $data): str $EA->setTable(Core::getTablePrefix() . 'article'); $EA->setWhere(['id' => $articleId, 'clang_id' => $clang]); $EA->setValue('name', $data['name']); - $EA->setValue('template_id', $data['template_id']); + $EA->setValue('template', $data['template']); $EA->setValue('priority', $data['priority']); $EA->addGlobalUpdateFields(self::getUser()); @@ -200,7 +200,7 @@ public static function editArticle(int $articleId, int $clang, array $data): str 'parent_id' => $data['category_id'], 'priority' => $data['priority'], 'path' => $data['path'], - 'template_id' => $data['template_id'], + 'template_key' => $data['template'], 'data' => $data, ])); @@ -238,7 +238,7 @@ public static function deleteArticle($articleId) 'status' => $Art->getValue('status'), 'priority' => $Art->getValue('priority'), 'path' => $Art->getValue('path'), - 'template_id' => $Art->getValue('template_id'), + 'template_key' => $Art->getValue('template'), ])); $Art->next(); @@ -294,7 +294,7 @@ public static function _deleteArticle($id) 'status' => $ART->getValue('status'), 'priority' => $ART->getValue('priority'), 'path' => $ART->getValue('path'), - 'template_id' => $ART->getValue('template_id'), + 'template_key' => $ART->getValue('template'), ])); if (1 == $ART->getValue('startarticle')) { diff --git a/src/Content/ArticleSlice.php b/src/Content/ArticleSlice.php index 9f084cde33..b7e315f76a 100644 --- a/src/Content/ArticleSlice.php +++ b/src/Content/ArticleSlice.php @@ -28,7 +28,7 @@ private function __construct( public int $articleId, public int $clangId, public int $contentSectionId, - public int $moduleId, + public string $moduleKey, public int $priority, public int $status, public int $createdate, @@ -48,7 +48,7 @@ public static function forNewSlice( int $articleId, int $clangId, int $ctype, - int $moduleId, + string $moduleKey, int $priority, int $revision, ): self { @@ -57,7 +57,7 @@ public static function forNewSlice( $articleId, $clangId, $ctype, - $moduleId, + $moduleKey, $priority, 1, $time = time(), @@ -91,7 +91,7 @@ public static function fromSql(Sql $sql): self (int) $sql->getValue($table . '.article_id'), (int) $sql->getValue($table . '.clang_id'), (int) $sql->getValue($table . '.ctype_id'), - (int) $sql->getValue($table . '.module_id'), + (string) $sql->getValue($table . '.module'), (int) $sql->getValue($table . '.priority'), (int) $sql->getValue($table . '.status'), (int) $sql->getDateTimeValue($table . '.createdate'), @@ -167,13 +167,13 @@ public static function getSlicesForArticle(int $articleId, ?int $clang = null, i * * @return list */ - public static function getSlicesForArticleOfType(int $articleId, int $moduleId, ?int $clang = null, int $revision = 0, bool $ignoreOfflines = false): array + public static function getSlicesForArticleOfType(int $articleId, string $moduleKey, ?int $clang = null, int $revision = 0, bool $ignoreOfflines = false): array { $clang ??= Language::getCurrentId(); return self::getSlicesWhere( - 'article_id=? AND clang_id=? AND module_id=? AND revision=?' . ($ignoreOfflines ? ' AND status = 1' : ''), - [$articleId, $clang, $moduleId, $revision], + 'article_id=? AND clang_id=? AND module=? AND revision=?' . ($ignoreOfflines ? ' AND status = 1' : ''), + [$articleId, $clang, $moduleKey, $revision], ); } @@ -373,16 +373,16 @@ public function isOnline(): bool /** * @internal - * @param array{id: int, articleId: int, clang: int, ctype: int, moduleId: int, priority: int, status: int, createdate: int, updatedate: int, createuser: string, updateuser: string, revision: int, values: array, media: array, medialists: array, links: array, linklists: array} $data + * @param array{id: int, articleId: int, clangId: int, contentSectionId: int, moduleKey: string, priority: int, status: int, createdate: int, updatedate: int, createuser: string, updateuser: string, revision: int, values: array, media: array, medialists: array, links: array, linklists: array} $data */ public static function __set_state(array $data): self { return new self( $data['id'], $data['articleId'], - $data['clang'], - $data['ctype'], - $data['moduleId'], + $data['clangId'], + $data['contentSectionId'], + $data['moduleKey'], $data['priority'], $data['status'], $data['createdate'], diff --git a/src/Content/ArticleSliceAction.php b/src/Content/ArticleSliceAction.php index 3f515da6ec..c3f3899709 100644 --- a/src/Content/ArticleSliceAction.php +++ b/src/Content/ArticleSliceAction.php @@ -2,57 +2,34 @@ namespace Redaxo\Core\Content; -use Redaxo\Core\Core; use Redaxo\Core\Database\Sql; -use Redaxo\Core\Exception\InvalidArgumentException; use Redaxo\Core\Http\Request; -use Redaxo\Core\Util\Stream; -use function in_array; use function is_array; final class ArticleSliceAction { - public const int ADD = 1; - public const int EDIT = 2; - public const int DELETE = 4; - - public const string PREVIEW = 'preview'; - public const string PRESAVE = 'presave'; - public const string POSTSAVE = 'postsave'; - - public readonly int $articleId; - public readonly int $clangId; - public readonly int $ctypeId; - public readonly int $sliceId; - - /** @param self::ADD|self::EDIT|self::DELETE $mode */ - public readonly int $mode; + public const string ADD = 'add'; + public const string EDIT = 'edit'; + public const string DELETE = 'delete'; public bool $save = true; /** @var list */ public private(set) array $messages = []; - /** @internal */ + /** + * @param self::ADD|self::EDIT|self::DELETE $mode + * @internal + */ public function __construct( - public readonly int $moduleId, - public readonly string $event, + public readonly string $mode, + public readonly int $articleId, + public readonly int $clangId, + public readonly int $ctypeId, + public readonly int $sliceId, private readonly Sql $sql, - ) { - if ('edit' == $event) { - $this->mode = self::EDIT; - } elseif ('delete' == $event) { - $this->mode = self::DELETE; - } else { - $this->mode = self::ADD; - } - - $this->articleId = Request::request('article_id', 'int'); - $this->clangId = Request::request('clang', 'int'); - $this->ctypeId = Request::request('ctype', 'int'); - $this->sliceId = self::ADD === $this->mode ? 0 : Request::request('slice_id', 'int'); - } + ) {} /** @internal */ public function setRequestValues(): void @@ -74,57 +51,37 @@ public function setRequestValues(): void } } - /** @param self::PREVIEW|self::PRESAVE|self::POSTSAVE $type */ - public function exec(string $type): void - { - if (!in_array($type, [self::PREVIEW, self::PRESAVE, self::POSTSAVE])) { - throw new InvalidArgumentException('$type must be ArticleSliceAction::PREVIEW, ::PRESAVE or ::POSTSAVE.'); - } - - $this->messages = []; - $this->save = true; - - $ga = Sql::factory(); - $ga->setQuery('SELECT a.id, `' . $type . '` as code FROM ' . Core::getTable('module_action') . ' ma,' . Core::getTable('action') . ' a WHERE `' . $type . '` != "" AND ma.action_id=a.id AND module_id=? AND (a.' . $type . 'mode & ?)', [$this->moduleId, $this->mode]); - - foreach ($ga as $row) { - $action = (string) $row->getValue('code'); - $articleId = (int) $row->getValue('id'); - require Stream::factory('action/' . $articleId . '/' . $type, $action); - } - } - public function addMessage(string $message): void { $this->messages[] = $message; } /** @param int<1, 20> $index */ - public function setValue(int $index, string $value): void + public function setValue(int $index, ?string $value): void { $this->sql->setValue('value' . $index, $value); } /** @param int<1, 10> $index */ - public function setMedia(int $index, string $value): void + public function setMedia(int $index, ?string $value): void { $this->sql->setValue('media' . $index, $value); } /** @param int<1, 10> $index */ - public function setMediaList(int $index, string $value): void + public function setMediaList(int $index, ?string $value): void { $this->sql->setValue('medialist' . $index, $value); } /** @param int<1, 10> $index */ - public function setLink(int $index, int $value): void + public function setLink(int $index, ?int $value): void { $this->sql->setValue('link' . $index, $value); } /** @param int<1, 10> $index */ - public function setLinkList(int $index, string $value): void + public function setLinkList(int $index, ?string $value): void { $this->sql->setValue('linklist' . $index, $value); } diff --git a/src/Content/AsModule.php b/src/Content/AsModule.php new file mode 100644 index 0000000000..28f20a2b09 --- /dev/null +++ b/src/Content/AsModule.php @@ -0,0 +1,14 @@ +setDebug(); - $sql->setQuery('select clang_id,template_id from ' . Core::getTablePrefix() . 'article where id=? and startarticle=1', [$categoryId]); + $sql->setQuery('select clang_id,template from ' . Core::getTablePrefix() . 'article where id=? and startarticle=1', [$categoryId]); for ($i = 0; $i < $sql->getRows(); $i++, $sql->next()) { - $startpageTemplates[(int) $sql->getValue('clang_id')] = $sql->getValue('template_id'); + $startpageTemplates[(int) $sql->getValue('clang_id')] = $sql->getValue('template'); } } @@ -79,22 +79,15 @@ public static function addCategory($categoryId, array $data) // Kategorie in allen Sprachen anlegen $AART = Sql::factory(); foreach (Language::getAllIds() as $key) { - $templateId = Template::getDefaultId(); + $templateKey = Template::getDefaultKey(); if (isset($startpageTemplates[$key]) && '' != $startpageTemplates[$key]) { - $templateId = $startpageTemplates[$key]; + $templateKey = $startpageTemplates[$key]; } // Wenn Template nicht vorhanden, dann entweder erlaubtes nehmen // oder leer setzen. - if (!isset($templates[$templateId])) { - $templateId = 0; - if (count($templates) > 0) { - $templateId = key($templates); - } - } - - if (!isset($templateId)) { - $templateId = 0; + if (null === $templateKey || !isset($templates[$templateKey])) { + $templateKey = count($templates) > 0 ? key($templates) : null; } $AART->setTable(Core::getTablePrefix() . 'article'); @@ -105,7 +98,7 @@ public static function addCategory($categoryId, array $data) } $AART->setValue('clang_id', $key); - $AART->setValue('template_id', $templateId); + $AART->setValue('template', $templateKey); $AART->setValue('name', $data['name']); $AART->setValue('catname', $data['catname']); $AART->setValue('catpriority', $data['catpriority']); diff --git a/src/Content/ContentHandler.php b/src/Content/ContentHandler.php index 976dee6845..ec246376c8 100644 --- a/src/Content/ContentHandler.php +++ b/src/Content/ContentHandler.php @@ -23,7 +23,7 @@ class ContentHandler { /** @throws ApiFunctionException */ - public static function addSlice(int $articleId, int $clangId, int $ctypeId, int $moduleId, array $data = []): string + public static function addSlice(int $articleId, int $clangId, int $ctypeId, string $moduleKey, array $data = []): string { $data['revision'] ??= 0; @@ -43,7 +43,7 @@ public static function addSlice(int $articleId, int $clangId, int $ctypeId, int $sql->setValue('article_id', $articleId); $sql->setValue('clang_id', $clangId); $sql->setValue('ctype_id', $ctypeId); - $sql->setValue('module_id', $moduleId); + $sql->setValue('module', $moduleKey); foreach ($data as $key => $value) { $sql->setValue($key, $value); @@ -77,7 +77,7 @@ public static function addSlice(int $articleId, int $clangId, int $ctypeId, int 'page' => Controller::getCurrentPage(), 'ctype' => $ctypeId, 'category_id' => $article->getCategoryId(), - 'module_id' => $moduleId, + 'module_key' => $moduleKey, 'article_revision' => 0, 'slice_revision' => $data['revision'], ])); diff --git a/src/Content/ContentSection.php b/src/Content/ContentSection.php index 157f474594..03654c5e5f 100644 --- a/src/Content/ContentSection.php +++ b/src/Content/ContentSection.php @@ -2,31 +2,11 @@ namespace Redaxo\Core\Content; -use Redaxo\Core\Core; -use Redaxo\Core\Database\Sql; - final readonly class ContentSection { - private function __construct( + public function __construct( /** @var positive-int */ public int $id, public string $name, ) {} - - /** @return list */ - public static function forTemplate(int $templateId): array - { - $sql = Sql::factory(); - $sql->setQuery('SELECT attributes FROM ' . Core::getTable('template') . ' WHERE id = ?', [$templateId]); - $attributes = $sql->getArrayValue('attributes'); - - /** @var array $ctypesData */ - $ctypesData = $attributes['ctype'] ?? []; - - $ctypes = []; - foreach ($ctypesData as $id => $name) { - $ctypes[] = new self($id, $name); - } - return $ctypes; - } } diff --git a/src/Content/ExtensionPoint/SliceMenu.php b/src/Content/ExtensionPoint/SliceMenu.php index 65b980e064..e76ab1ad26 100644 --- a/src/Content/ExtensionPoint/SliceMenu.php +++ b/src/Content/ExtensionPoint/SliceMenu.php @@ -33,7 +33,7 @@ public function __construct( private int $articleId, private int $clang, private int $ctype, - private int $moduleId, + private string $moduleKey, private int $sliceId, private bool $hasPerm, ) { @@ -112,7 +112,7 @@ public function getAdditionalActions(): array 'article_id' => $this->articleId, 'clang' => $this->clang, 'ctype' => $this->ctype, - 'module_id' => $this->moduleId, + 'module_key' => $this->moduleKey, 'slice_id' => $this->sliceId, 'perm' => $this->hasPerm, ], @@ -156,9 +156,9 @@ public function getCtypeId(): int return $this->ctype; } - public function getModuleId(): int + public function getModuleKey(): string { - return $this->moduleId; + return $this->moduleKey; } public function getSliceId(): int diff --git a/src/Content/Module.php b/src/Content/Module.php index 7bf8ef4fd7..d86b04df5c 100644 --- a/src/Content/Module.php +++ b/src/Content/Module.php @@ -2,68 +2,74 @@ namespace Redaxo\Core\Content; -use Redaxo\Core\Filesystem\File; +use Redaxo\Core\ClassDiscovery; -use function assert; - -class Module +abstract class Module { - private int $id; - private ?string $key = ''; - - public function __construct(int $moduleId) + /** @var array|null */ + private static ?array $instances = null; + + public function __construct( + /** Unique key, used as DB reference in rex_article_slice.module. */ + public readonly string $key, + public readonly string $name, + ) {} + + /** + * Get a module by key or FQCN. + * + * @param string|class-string $keyOrClass + */ + public static function get(string $keyOrClass): ?self { - $this->id = $moduleId; - } + $all = self::getAll(); - public static function forKey(string $moduleKey): ?self - { - $mapping = self::getKeyMapping(); - - if (false !== $id = array_search($moduleKey, $mapping, true)) { - $module = new self($id); - $module->key = $moduleKey; - - return $module; + if (isset($all[$keyOrClass])) { + return $all[$keyOrClass]; } - return null; + // FQCN lookup + /** @var ?self */ + return array_find($all, static fn (self $module) => $module instanceof $keyOrClass); } - public function getId(): int + /** + * Check if a module exists by key or FQCN. + * + * @param string|class-string $keyOrClass + */ + public static function exists(string $keyOrClass): bool { - return $this->id; + return null !== self::get($keyOrClass); } - public function getKey(): ?string + /** @return array */ + public static function getAll(): array { - // key will never be empty string in the db - if ('' === $this->key) { - $this->key = self::getKeyMapping()[$this->id] ?? null; - assert('' !== $this->key); + if (null !== self::$instances) { + return self::$instances; } - return $this->key; - } + $instances = []; + foreach (ClassDiscovery::getInstance()->discoverByAttribute(AsModule::class, self::class) as $class => $attribute) { + $instances[$attribute->key] = new $class($attribute->key, $attribute->name); + } - /** @return array */ - private static function getKeyMapping(): array - { - static $mapping; + return self::$instances = $instances; + } - if (null !== $mapping) { - return $mapping; - } + /** Render the backend edit form. */ + abstract public function input(ArticleSlice $slice): string; - $file = ModuleCache::getKeyMappingPath(); - $mapping = File::getCache($file, null); + /** Render the frontend output. */ + abstract public function output(ArticleSlice $slice): string; - if (null !== $mapping) { - return $mapping; - } + /** Called before the edit form is displayed. */ + public function onPreview(ArticleSliceAction $action): void {} - ModuleCache::generateKeyMapping(); + /** Called before slice data is saved to DB. */ + public function onPresave(ArticleSliceAction $action): void {} - return $mapping = File::getCache($file); - } + /** Called after slice data is saved to DB. */ + public function onPostsave(ArticleSliceAction $action): void {} } diff --git a/src/Content/ModuleCache.php b/src/Content/ModuleCache.php deleted file mode 100644 index 7904c7cbab..0000000000 --- a/src/Content/ModuleCache.php +++ /dev/null @@ -1,37 +0,0 @@ -getArray('SELECT id, `key` FROM ' . Core::getTable('module') . ' WHERE `key` IS NOT NULL'); - $mapping = array_column($data, 'key', 'id'); - - if (!File::putCache(self::getKeyMappingPath(), $mapping)) { - throw new RuntimeException('Unable to generate module key mapping.'); - } - } - - public static function getKeyMappingPath(): string - { - return Path::coreCache('structure/module_key_mapping.cache'); - } -} diff --git a/src/Content/ModulePermission.php b/src/Content/ModulePermission.php index a779a531f9..639ea82ab9 100644 --- a/src/Content/ModulePermission.php +++ b/src/Content/ModulePermission.php @@ -2,7 +2,8 @@ namespace Redaxo\Core\Content; -use Redaxo\Core\Core; +use Collator; +use Locale; use Redaxo\Core\Security\ComplexPermission; use Redaxo\Core\Translation\I18n; @@ -10,21 +11,23 @@ class ModulePermission extends ComplexPermission { - /** - * @param int $moduleId - * @return bool - */ - public function hasPerm($moduleId) + public function hasPerm(string $moduleKey): bool { - return $this->hasAll() || in_array($moduleId, $this->perms); + return $this->hasAll() || in_array($moduleKey, $this->perms); } public static function getFieldParams() { + $options = []; + foreach (Module::getAll() as $module) { + $options[$module->key] = I18n::translate($module->name); + } + new Collator(Locale::getDefault())->asort($options); + return [ 'label' => I18n::msg('modules'), 'all_label' => I18n::msg('all_modules'), - 'sql_options' => 'select name, id from ' . Core::getTablePrefix() . 'module order by name', + 'options' => $options, ]; } } diff --git a/src/Content/StructureElement.php b/src/Content/StructureElement.php index 06f0054797..4b1cf29914 100644 --- a/src/Content/StructureElement.php +++ b/src/Content/StructureElement.php @@ -41,8 +41,7 @@ abstract class StructureElement /** @var string */ protected $catname = ''; - /** @var int */ - protected $template_id = 0; + protected ?string $template = null; /** @var string */ protected $path = ''; @@ -81,7 +80,7 @@ protected function __construct(array $params) continue; } - if (in_array($var, ['id', 'parent_id', 'clang_id', 'template_id', 'priority', 'catpriority', 'status', 'createdate', 'updatedate'], true)) { + if (in_array($var, ['id', 'parent_id', 'clang_id', 'priority', 'catpriority', 'status', 'createdate', 'updatedate'], true)) { $this->$var = (int) $params[$var]; } elseif ('startarticle' === $var) { $this->$var = (bool) $params[$var]; @@ -391,14 +390,9 @@ public function isOnline() return 1 == $this->status; } - /** - * Returns the template id. - * - * @return int - */ - public function getTemplateId() + public function getTemplateKey(): ?string { - return $this->template_id; + return $this->template; } /** @@ -408,7 +402,7 @@ public function getTemplateId() */ public function hasTemplate() { - return $this->template_id > 0; + return null !== $this->template; } /** diff --git a/src/Content/Template.php b/src/Content/Template.php index 4a6a8dfdc0..1e9180eb71 100644 --- a/src/Content/Template.php +++ b/src/Content/Template.php @@ -2,246 +2,151 @@ namespace Redaxo\Core\Content; +use Redaxo\Core\ClassDiscovery; use Redaxo\Core\Core; -use Redaxo\Core\Database\Sql; -use Redaxo\Core\Filesystem\File; -use Redaxo\Core\Filesystem\Url; -use Redaxo\Core\Language\Language; -use Redaxo\Core\Translation\I18n; -use Redaxo\Core\Util\Stream; -use Redaxo\Core\Util\Timer; - -use function assert; -use function in_array; -use function is_array; -use function Redaxo\Core\View\escape; - -class Template +use Redaxo\Core\Util\Type; + +abstract class Template { - private int $id; - private ?string $key = ''; + /** @var array|null */ + private static ?array $instances = null; - public function __construct($templateId) - { - $this->id = (int) $templateId; - } + public function __construct( + /** Unique key, used as DB reference in rex_article.template. */ + public readonly string $key, + public readonly string $name, + ) {} - /** @return int */ - public static function getDefaultId() + public static function getDefaultKey(): ?string { - return Core::getConfig('default_template_id', 1); + return Type::nullOrString(Core::getConfig('default_template')); } - public static function forKey(string $templateKey): ?self + /** + * Get a template by key or FQCN. + * + * @param string|class-string $keyOrClass + */ + public static function get(string $keyOrClass): ?self { - $mapping = self::getKeyMapping(); - - if (false !== $id = array_search($templateKey, $mapping, true)) { - $template = new self($id); - $template->key = $templateKey; + $all = self::getAll(); - return $template; + if (isset($all[$keyOrClass])) { + return $all[$keyOrClass]; } - return null; + // FQCN lookup + /** @var ?self */ + return array_find($all, static fn (self $template) => $template instanceof $keyOrClass); } - /** @return int */ - public function getId() - { - return $this->id; - } - - public function getKey(): ?string + /** + * Check if a template exists by key or FQCN. + * + * @param string|class-string $keyOrClass + */ + public static function exists(string $keyOrClass): bool { - // key will never be empty string in the db - if ('' === $this->key) { - $this->key = self::getKeyMapping()[$this->id] ?? null; - assert('' !== $this->key); - } - - return $this->key; + return null !== self::get($keyOrClass); } - /** @return false|string */ - public function getFile() + /** @return array */ + public static function getAll(): array { - if ($this->getId() < 1) { - return false; + if (null !== self::$instances) { + return self::$instances; } - $file = TemplateCache::getPath($this->id); - - if (!is_file($file)) { - TemplateCache::generate($this->id); + $instances = []; + foreach (ClassDiscovery::getInstance()->discoverByAttribute(AsTemplate::class, self::class) as $class => $attribute) { + $instances[$attribute->key] = new $class($attribute->key, $attribute->name); } - return $file; + return self::$instances = $instances; } - /** @return false|string|null */ - public function getTemplate() + /** + * Returns all templates available for the given category. + * + * @return array + */ + public static function getTemplatesForCategory(?int $categoryId): array { - $file = $this->getFile(); - if (!$file) { - return false; - } - - return File::get($file); - } + $category = $categoryId > 0 ? Category::get($categoryId) : null; - public function render(): string - { - return Timer::measure('Template: ' . ($this->getKey() ?? $this->id), function () { - return File::getOutput(Stream::factory('template/' . $this->id, $this->getTemplate() ?: '')); - }); + return array_filter( + self::getAll(), + static fn (self $template) => $template->isAllowedInCategory($category), + ); } /** - * Returns an array containing all templates which are available for the given category_id. - * if the category_id is non-positive all templates in the system are returned. - * if the category_id is invalid an empty array is returned. - * - * @param int $categoryId - * @param bool $ignoreInactive + * Check if a module is allowed in a given content section of a template (resolves keys to objects). * - * @return array + * @param positive-int $contentSectionId + * @internal */ - public static function getTemplatesForCategory($categoryId, $ignoreInactive = true) + public static function checkModuleAllowed(string $templateKey, int $contentSectionId, string $moduleKey): bool { - $templates = []; - $tSql = Sql::factory(); - $where = $ignoreInactive ? ' WHERE active=1' : ''; - $tSql->setQuery('select id,name,attributes from ' . Core::getTablePrefix() . 'template' . $where . ' order by name'); - - if ($categoryId < 1) { - // Alle globalen Templates - foreach ($tSql as $row) { - $attributes = $row->getArrayValue('attributes'); - $categories = $attributes['categories'] ?? []; - if (!is_array($categories) || (isset($categories['all']) && 1 == $categories['all'])) { - $templates[(int) $row->getValue('id')] = (string) $row->getValue('name'); - } - } - } else { - if ($c = Category::get($categoryId)) { - $path = $c->getPathAsArray(); - $path[] = $categoryId; - foreach ($tSql as $row) { - $attributes = $row->getArrayValue('attributes'); - $categories = $attributes['categories'] ?? []; - // template ist nicht kategoriespezifisch -> includen - if (!is_array($categories) || (isset($categories['all']) && 1 == $categories['all'])) { - $templates[(int) $row->getValue('id')] = (string) $row->getValue('name'); - } else { - // template ist auf kategorien beschraenkt.. - // nachschauen ob eine davon im pfad der aktuellen kategorie liegt - foreach ($path as $p) { - if (in_array($p, $categories)) { - $templates[(int) $row->getValue('id')] = (string) $row->getValue('name'); - break; - } - } - } - } - } - } - return $templates; - } + $template = self::get($templateKey); - /** @return bool */ - public static function hasModule(array $templateAttributes, $ctype, $moduleId) - { - $templateModules = $templateAttributes['modules'] ?? []; - if (!isset($templateModules[$ctype]['all']) || 1 == $templateModules[$ctype]['all']) { + if (null === $template) { return true; } - return is_array($templateModules[$ctype]) && in_array($moduleId, $templateModules[$ctype]); - } + $module = Module::get($moduleKey); - /** @return array */ - private static function getKeyMapping(): array - { - static $mapping; - - if (null !== $mapping) { - return $mapping; + if (null === $module) { + return false; } - $file = TemplateCache::getKeyMappingPath(); - $mapping = File::getCache($file, null); + $section = $template->getContentSection($contentSectionId); - if (null !== $mapping) { - return $mapping; + if (null === $section) { + return true; } - TemplateCache::generateKeyMapping(); - - return $mapping = File::getCache($file); + return $template->isModuleAllowed($section, $module); } - /** @return list */ - public function getCtypes(): array + /** @return non-empty-list */ + public function getContentSections(): array { - return ContentSection::forTemplate($this->id); + return [new ContentSection(1, 'Content')]; } - /** @return false|string */ - public static function templateIsInUse(int $templateId, string $msgKey) + /** + * Whether this template is available in the given category. + * `null` means root level (articles without a category). + * The default implementation allows the template everywhere. + * + * Override this to restrict to specific categories. + */ + public function isAllowedInCategory(?Category $category): bool { - $check = Sql::factory(); - $check->setQuery(' - SELECT article.id, article.clang_id, template.name - FROM ' . Core::getTable('article') . ' article - LEFT JOIN ' . Core::getTable('template') . ' template ON article.template_id=template.id - WHERE article.template_id=? - LIMIT 20 - ', [$templateId]); - - if (!$check->getRows()) { - return false; - } - $templateInUseMessage = ''; - $error = ''; - $templatename = $check->getRows() ? $check->getValue('template.name') : null; - while ($check->hasNext()) { - $aid = (int) $check->getValue('article.id'); - $clangId = (int) $check->getValue('article.clang_id'); - $article = Article::get($aid, $clangId); - if (null == $article) { - continue; - } - $label = $article->getName() . ' [' . $aid . ']'; - if (Language::count() > 1) { - $clang = Language::get($clangId); - if (null == $clang) { - continue; - } - $label .= ' [' . $clang->getCode() . ']'; - } - - $templateInUseMessage .= '
  • ' . escape($label) . '
  • '; - $check->next(); - } - - if (null == $templatename) { - $check->setQuery('SELECT name FROM ' . Core::getTable('template') . ' WHERE id = ' . $templateId); - $templatename = $check->getValue('name'); - } + return true; + } - if ('' != $templateInUseMessage && null != $templatename) { - $error .= I18n::msg($msgKey, (string) $templatename); - $error .= '
      ' . $templateInUseMessage . '
    '; - } + /** + * Whether the given module is allowed in the given content section of this template. + * The default implementation allows all modules everywhere. + */ + public function isModuleAllowed(ContentSection $section, Module $module): bool + { + return true; + } - return $error; + /** @param positive-int $id */ + final public function getContentSection(int $id): ?ContentSection + { + return array_find($this->getContentSections(), static fn (ContentSection $s) => $s->id === $id); } - public static function exists(int $templateId): bool + /** @param positive-int $id */ + final public function hasContentSection(int $id): bool { - $sql = Sql::factory(); - $sql->setQuery('SELECT 1 FROM ' . Core::getTable('template') . ' WHERE id = ?', [$templateId]); - return 1 === $sql->getRows(); + return null !== $this->getContentSection($id); } + + abstract public function render(ArticleContent $article): string; } diff --git a/src/Content/TemplateCache.php b/src/Content/TemplateCache.php deleted file mode 100644 index 5d5567639d..0000000000 --- a/src/Content/TemplateCache.php +++ /dev/null @@ -1,64 +0,0 @@ -setQuery('SELECT * FROM ' . Core::getTable('template') . ' WHERE id = ?', [$id]); - - if (1 !== $sql->getRows()) { - throw new RuntimeException('Template with id "' . $id . '" does not exist.'); - } - - $path = self::getPath($id); - if (!File::put($path, $sql->getValue('content'))) { - throw new RuntimeException('Unable to generate template "' . $id . '".'); - } - - if (function_exists('opcache_invalidate')) { - opcache_invalidate($path); - } - } - - public static function generateKeyMapping(): void - { - $data = Sql::factory()->getArray('SELECT id, `key` FROM ' . Core::getTable('template') . ' WHERE `key` IS NOT NULL'); - $mapping = array_column($data, 'key', 'id'); - - if (!File::putCache(self::getKeyMappingPath(), $mapping)) { - throw new RuntimeException('Unable to generate template key mapping.'); - } - } - - public static function getPath(int $id): string - { - return Path::coreCache('structure/templates/' . $id . '.template'); - } - - public static function getKeyMappingPath(): string - { - return Path::coreCache('structure/templates/template_key_mapping.cache'); - } -} diff --git a/src/Form/Select/TemplateSelect.php b/src/Form/Select/TemplateSelect.php index 2d21802639..9867ad5847 100644 --- a/src/Form/Select/TemplateSelect.php +++ b/src/Form/Select/TemplateSelect.php @@ -2,6 +2,8 @@ namespace Redaxo\Core\Form\Select; +use Collator; +use Locale; use Redaxo\Core\Content\Template; use Redaxo\Core\Core; use Redaxo\Core\Database\Sql; @@ -16,7 +18,7 @@ class TemplateSelect extends Select private $loaded = false; /** @var int|null */ private $categoryId; - /** @var array|null */ + /** @var array|null */ private $templates; /** @var int */ private $clangId; @@ -40,11 +42,14 @@ public function get() $templates = $this->getTemplates(); if (count($templates) > 0) { - foreach ($templates as $templateId => $templateName) { - $this->addOption($templateName, $templateId); + $templateNames = array_map(static fn (Template $template) => I18n::translate($template->name), $templates); + new Collator(Locale::getDefault())->asort($templateNames); + + foreach ($templateNames as $templateKey => $templateName) { + $this->addOption($templateName, $templateKey); } } else { - $this->addOption(I18n::msg('option_no_template'), '0'); + $this->addOption(I18n::msg('option_no_template'), ''); } $this->loaded = true; @@ -58,46 +63,39 @@ public function setSelectedFromStartArticle() { $selected = null; - // Inherit template_id from start article + // Inherit template from start article if ($this->categoryId > 0) { $sql = Sql::factory(); - $sql->setQuery('SELECT template_id FROM ' . Core::getTable('article') . ' WHERE id = ? AND clang_id = ? AND startarticle = 1', [ + $sql->setQuery('SELECT template FROM ' . Core::getTable('article') . ' WHERE id = ? AND clang_id = ? AND startarticle = 1', [ $this->categoryId, $this->clangId, ]); if (1 == $sql->getRows()) { - $selected = $sql->getValue('template_id'); + $selected = $sql->getValue('template'); } } $templates = $this->getTemplates(); if (!$selected || !isset($templates[$selected])) { - $selected = Template::getDefaultId(); + $selected = Template::getDefaultKey(); } - if ($selected && isset($templates[$selected])) { + if (null !== $selected && isset($templates[$selected])) { parent::setSelected($selected); } } - /** @return array */ - public function getTemplates() + /** @return array */ + public function getTemplates(): array { - if (null === $this->templates) { - $this->templates = []; - - if (null !== $this->categoryId) { - $templates = Template::getTemplatesForCategory($this->categoryId); - } else { - $templates = Sql::factory()->getArray('SELECT id, name FROM ' . Core::getTable('template') . ' WHERE active = 1 ORDER BY name'); - $templates = array_column($templates, 'name', 'id'); - } + if (null !== $this->templates) { + return $this->templates; + } - foreach ($templates as $templateId => $templateName) { - $this->templates[$templateId] = I18n::translate($templateName, false); - } + if (null === $this->categoryId) { + return $this->templates = Template::getAll(); } - return $this->templates; + return $this->templates = Template::getTemplatesForCategory($this->categoryId); } } diff --git a/src/MetaInfo/Handler/ArticleHandler.php b/src/MetaInfo/Handler/ArticleHandler.php index dd2d4bc7b6..50430d79d0 100644 --- a/src/MetaInfo/Handler/ArticleHandler.php +++ b/src/MetaInfo/Handler/ArticleHandler.php @@ -63,7 +63,7 @@ protected function buildFilterCondition(array $params) } } - $t = ' OR FIND_IN_SET(' . $OOArt->getValue('template_id') . ', `p`.`templates`)'; + $t = ' OR FIND_IN_SET(' . Sql::factory()->escape($OOArt->getTemplateKey() ?? '') . ', `p`.`templates`)'; $restrictionsCondition = 'AND (`p`.`restrictions` = "" OR `p`.`restrictions` IS NULL ' . $s . ') AND (`p`.`templates` = "" OR `p`.`templates` IS NULL ' . $t . ')'; }