From 5232a12ebd1e375b1cf2fb6944d4d5b3efcaff12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Nitzsche?= Date: Sat, 9 Dec 2023 12:16:09 +0100 Subject: [PATCH] Feature/support for content security policy header (#341) * #340 add support for security policy header * #340 change reload events for form input elements * #340 fix unit tests * #340 add flash message to DocumentTemplate * #340 createNewButton is alias of createNewLink * #340 improve button hover text * #340 fix tce links for older t3 versions --- CHANGELOG.md | 7 + Classes/Backend/Decorator/BaseDecorator.php | 5 +- .../Form/Element/EnhancedLinkButton.php | 127 ++++ Classes/Backend/Form/FormBuilder.php | 2 +- Classes/Backend/Form/ToolBox.php | 607 +++++++++++------- Classes/Backend/Module/BaseModule.php | 10 +- Classes/Backend/Template/ModuleTemplate.php | 18 - .../Template/Override/DocumentTemplate.php | 93 ++- Classes/Backend/Utility/BackendUtility.php | 37 +- Classes/Backend/Utility/IconMap.php | 213 ++++++ Classes/Backend/Utility/Icons.php | 19 +- Classes/Frontend/Request/Parameters.php | 14 +- Classes/Utility/Strings.php | 16 +- Classes/Utility/T3General.php | 22 + Classes/Utility/TYPO3.php | 10 + Configuration/JavaScriptModules.php | 10 + Resources/Public/JavaScript/Toolbox.js | 60 ++ Resources/Public/JavaScript/es6/toolbox.js | 59 ++ composer.json | 3 +- tests/Classes/Backend/Form/ToolBoxTest.php | 112 ++-- 20 files changed, 1080 insertions(+), 364 deletions(-) create mode 100644 Classes/Backend/Form/Element/EnhancedLinkButton.php create mode 100644 Classes/Backend/Utility/IconMap.php create mode 100644 Configuration/JavaScriptModules.php create mode 100644 Resources/Public/JavaScript/Toolbox.js create mode 100644 Resources/Public/JavaScript/es6/toolbox.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 48dd2a0a..9ce823e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ v1.18.0 (??.11.2023) * BC: Parameter type of exceptions changed in ExceptionHandlerInterface to \Throwable * Share ViewContext instance of current configuration with current request * Fixed some PHP 8 issues +* Support for activated content security policy header in backend modules +* Improved icon support in backend modules +* BC: impossible to use HTML-Code in title parameter of ToolBox::createModuleLink(). Most likely this was done for icon code. As fix provide icon as option ToolBox::OPTION_ICON_NAME. +* BC: removed public attributes `JScode` and `JScodeArray` in `Sys25\RnBase\Backend\Template\Override\DocumentTemplate` +* Support for FlashMessage via `DocumentTemplate::showFlashMessage()` +* Method `rmFromList` added to `T3General` +* Possible BC: `ToolBox::createNewButton()` is now alias of `ToolBox::createNewLink()` v1.17.4 (22.09.2023) diff --git a/Classes/Backend/Decorator/BaseDecorator.php b/Classes/Backend/Decorator/BaseDecorator.php index f1715efd..39e0f791 100644 --- a/Classes/Backend/Decorator/BaseDecorator.php +++ b/Classes/Backend/Decorator/BaseDecorator.php @@ -11,7 +11,6 @@ use Sys25\RnBase\Domain\Model\DataInterface; use Sys25\RnBase\Domain\Model\DataModel; use Sys25\RnBase\Domain\Model\DomainModelInterface as DomainInterface; -use Sys25\RnBase\Domain\Model\RecordInterface; use Sys25\RnBase\Utility\Strings; use Sys25\RnBase\Utility\TYPO3; @@ -427,11 +426,11 @@ protected function formatActionMovedown( /** * Returns the uid map and sets the pointer to the current element. * - * @param RecordInterface $item + * @param DomainInterface $item * * @return array */ - protected function getUidMap(RecordInterface $item) + protected function getUidMap(DomainInterface $item) { if (!$this->getOptions()->hasUidMap()) { return []; diff --git a/Classes/Backend/Form/Element/EnhancedLinkButton.php b/Classes/Backend/Form/Element/EnhancedLinkButton.php new file mode 100644 index 00000000..322423ca --- /dev/null +++ b/Classes/Backend/Form/Element/EnhancedLinkButton.php @@ -0,0 +1,127 @@ +dataAttributes = array_merge($this->dataAttributes, $dataAttributes); + } + + /** + * If set given css classes will override default button classes. + * + * @param bool $value + * + * @return LinkButton + */ + public function setOverrideCss($value) + { + $this->overrideCss = (bool) $value; + + return $this; + } + + /** + * hover tooltip attribute of the link. + * + * @var string + */ + private $hoverText = ''; + + /** + * Get hoverText. + * + * @return string + */ + public function getHoverText() + { + return ''.$this->hoverText; + } + + /** + * Set tooltip text. + * + * @param string $value hover text + * + * @return LinkButton + */ + public function setHoverText($value) + { + $this->hoverText = $value; + + return $this; + } + + /** + * Renders the markup for the button. + * + * @return string + */ + public function render() + { + $cssClasses = 'btn btn-default btn-sm '; + if ($this->overrideCss && !empty($this->getClasses())) { + $cssClasses = ''; + } + + $attributes = [ + 'href' => $this->getHref(), + 'class' => trim($cssClasses.$this->getClasses()), + 'title' => $this->getHoverText() ?: $this->getTitle(), + ]; + $labelText = ''; + if ($this->showLabelText) { + $labelText = ' '.$this->title; + } + foreach ($this->dataAttributes as $attributeName => $attributeValue) { + $attributes['data-'.$attributeName] = $attributeValue; + } + if (method_exists($this, 'isDisabled') && $this->isDisabled()) { + $attributes['disabled'] = 'disabled'; + $attributes['class'] .= ' disabled'; + } + $attributesString = T3General::implodeAttributes($attributes, true); + + $icon = $this->getIcon() ? $this->getIcon()->render() : ''; + + return '' + .trim($icon.htmlspecialchars($labelText)) + .''; + } +} diff --git a/Classes/Backend/Form/FormBuilder.php b/Classes/Backend/Form/FormBuilder.php index 886e7e6b..ccdbd23b 100644 --- a/Classes/Backend/Form/FormBuilder.php +++ b/Classes/Backend/Form/FormBuilder.php @@ -97,7 +97,7 @@ protected function isNEWRecord($uid) * @param string $uid * @param array $record should contain pid and other default values for record * - * @return multitype: + * @return array */ protected function compileFormData($table, $uid, $record) { diff --git a/Classes/Backend/Form/ToolBox.php b/Classes/Backend/Form/ToolBox.php index a8b7f249..41f98f6f 100644 --- a/Classes/Backend/Form/ToolBox.php +++ b/Classes/Backend/Form/ToolBox.php @@ -2,6 +2,7 @@ namespace Sys25\RnBase\Backend\Form; +use Sys25\RnBase\Backend\Form\Element\EnhancedLinkButton; use Sys25\RnBase\Backend\Form\Element\InputText; use Sys25\RnBase\Backend\Module\IModule; use Sys25\RnBase\Backend\Template\Override\DocumentTemplate; @@ -13,9 +14,10 @@ use Sys25\RnBase\Utility\Math; use Sys25\RnBase\Utility\Misc; use Sys25\RnBase\Utility\Strings; -use Sys25\RnBase\Utility\T3General as T3GeneralAlias; +use Sys25\RnBase\Utility\T3General; use Sys25\RnBase\Utility\TYPO3; use tx_rnbase; +use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction; /*************************************************************** @@ -56,6 +58,9 @@ class ToolBox */ private $module; + /** + * @var DocumentTemplate + */ protected $doc; public const CSS_CLASS_BTN = 'btn btn-default btn-sm'; @@ -66,8 +71,13 @@ class ToolBox public const OPTION_TITLE = 'title'; public const OPTION_CONFIRM = 'confirm'; + public const OPTION_ICON_NAME = 'icon'; + public const OPTION_HOVER_TEXT = 'hover'; + public const OPTION_HIDE_LABEL = 'hide-label'; public const OPTION_PARAMS = 'params'; + public const OPTION_CSS_CLASSES = 'class'; + public const OPTION_DATA_ATTR = 'data-attr'; /** * Clipboard object. @@ -76,6 +86,11 @@ class ToolBox */ private $clipObj; private $tceStack; + /** @var LanguageService */ + private $lang; + + /** @var \TYPO3\CMS\Backend\Routing\UriBuilder */ + private $uriBuilder; /** * @param DocumentTemplate $doc @@ -83,14 +98,15 @@ class ToolBox */ public function init(DocumentTemplate $doc, IModule $module) { - global $BACK_PATH; $this->doc = $doc; $this->module = $module; + $this->lang = $module->getLanguageService(); + + $this->uriBuilder = tx_rnbase::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class); // TCEform für das Formular erstellen $this->form = tx_rnbase::makeInstance(FormBuilder::class); $this->form->initDefaultBEmode(); - $this->form->backPath = $BACK_PATH; } /** @@ -140,147 +156,173 @@ public function createEditButton($editTable, $editUid, $options = []) } /** - * Erstellt einen Link zur Bearbeitung eines Datensatzes. + * Creates a Link to show an item in frontend. * - * @param string $editTable DB-Tabelle des Datensatzes - * @param int $editUid UID des Datensatzes - * @param string $label Bezeichnung des Links + * @param $pid + * @param $label + * @param string $urlParams * @param array $options * * @return string */ - public function createEditLink($editTable, $editUid, $label = 'Edit', $options = []) + public function createShowLink($pid, $label, $urlParams = '', $options = []) { - $params = '&edit['.$editTable.']['.$editUid.']=edit'; + if ($options['sprite'] ?? false) { + $label = Icons::getSpriteIcon($options['sprite']); + } + $jsCode = BackendUtility::viewOnClick($pid, '', null, '', '', $urlParams); + $title = ''; + if ($options['hover'] ?? false) { + $title = ' title="'.$options['hover'].'" '; + } + $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : self::CSS_CLASS_BTN; $class = ' class="'.$class.'"'; - $label = isset($options['label']) ? $options['label'] : $label; - $onClick = htmlspecialchars(BackendUtility::editOnClick($params)); - return '' - .Icons::getSpriteIcon('actions-page-open') - .$label - .''; + return ''.$label.''; } /** - * Erstellt einen History-Link - * Achtung: Benötigt die JS-Funktion jumpExt() in der Seite. - * - * @param string $table - * @param int $recordUid - * - * @return string + * @return EnhancedLinkButton */ - public function createHistoryLink($table, $recordUid, $label = '') + private function makeLinkButton($uri, $label = '') { - $this->addBaseInlineJSCode(); - $image = Icons::getSpriteIcon('actions-document-history-open'); - $moduleUrl = BackendUtility::getModuleUrl('record_history', ['element' => $table.':'.$recordUid]); - $onClick = 'return jumpExt('.Strings::quoteJSvalue($moduleUrl).',\'#latest\');'; + $btn = new EnhancedLinkButton(); + $btn->setHref($uri); + if ($label) { + $btn->setTitle($label) + ->setShowLabelText(true); + } - return '' - .$image.''; + return $btn; } /** - * Creates a new-record-button. + * Erstellt einen Link zur Erstellung eines neuen Datensatzes + * Possible options: + * - params: some plain url params like "&myparam=4" + * - title: label for this link + * - confirm: some confirm message + * - defvals: an array for defVals like this: ['mytable' => ['myfield' => 'initialvalue', ]] + * - class: css class for a tag. default is "btn btn-default btn-sm". * - * @param string $table - * @param int $pid + * @param string $table DB-Tabelle des Datensatzes + * @param int $pid UID der Zielseite + * @param string $label Bezeichnung des Links * @param array $options * * @return string */ - public function createNewButton($table, $pid, $options = []) + public function createNewLink($table, $pid, $label = 'New', $options = []) { - $params = '&edit['.$table.']['.$pid.']=new'; - if (isset($options[self::OPTION_PARAMS])) { - $params .= $options[self::OPTION_PARAMS]; - } - $params .= $this->buildDefVals($options); - $title = isset($options[self::OPTION_TITLE]) ? $options[self::OPTION_TITLE] : $GLOBALS['LANG']->getLL('new', 1); - $name = isset($options['name']) ? $options['params'] : ''; + $uri = $this->buildEditUri($table, $pid, 'new', $options); + $uri .= $this->buildDefVals($options); + + $image = Icons::getSpriteIcon('actions-document-new', ['asIcon' => true]); + $recordButton = $this->makeLinkButton($uri, $label) + ->setIcon($image); + + $recordButton->setHoverText($this->getHoverText($options) ?: $label); + $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : ''; - $jsCode = BackendUtility::editOnClick($params); if (isset($options[self::OPTION_CONFIRM]) && strlen($options[self::OPTION_CONFIRM]) > 0) { - $jsCode = 'if(confirm('.Strings::quoteJSvalue($options[self::OPTION_CONFIRM]).')) {'.$jsCode.'} else {return false;}'; + $class .= ' t3js-modal-trigger'; + $recordButton->setDataAttributes(['content' => $options[self::OPTION_CONFIRM]]); + $recordButton->setOverrideCss(false); } + $recordButton->setClasses($class); - $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : self::CSS_CLASS_BTN; - $class = ' class="'.$class.'"'; - - $btn = 'render(); + } - return $btn; + private function getHoverText(array &$options, $label = '') + { + return $this->getLanguageService()->getLL( + $options[self::OPTION_HOVER_TEXT] ?? $label + ) ?: ($options[self::OPTION_HOVER_TEXT] ?? $label); } /** - * Creates a Link to show an item in frontend. + * Creates a new-record-button. * - * @param $pid - * @param $label - * @param string $urlParams + * @param string $table + * @param int $pid * @param array $options * * @return string + * + * @deprecated use createNewLink() */ - public function createShowLink($pid, $label, $urlParams = '', $options = []) + public function createNewButton($table, $pid, $options = []) { - if ($options['sprite'] ?? false) { - $label = Icons::getSpriteIcon($options['sprite']); - } - $jsCode = BackendUtility::viewOnClick($pid, '', null, '', '', $urlParams); - $title = ''; - if ($options['hover'] ?? false) { - $title = ' title="'.$options['hover'].'" '; - } - - $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : self::CSS_CLASS_BTN; - $class = ' class="'.$class.'"'; - - return ''.$label.''; + return $this->createNewLink($table, $pid, $options[self::OPTION_TITLE] ?? 'New', $options); } /** - * Erstellt einen Link zur Erstellung eines neuen Datensatzes - * Possible options: - * - params: some plain url params like "&myparam=4" - * - title: label for this link - * - confirm: some confirm message - * - defvals: an array for defVals like this: ['mytable' => ['myfield' => 'initialvalue', ]] - * - class: css class for a tag. default is "btn btn-default btn-sm". + * Erstellt einen Link zur Bearbeitung eines Datensatzes. * - * @param string $table DB-Tabelle des Datensatzes - * @param int $pid UID der Zielseite - * @param string $label Bezeichnung des Links + * @param string $editTable DB-Tabelle des Datensatzes + * @param int $editUid UID des Datensatzes + * @param string $label Bezeichnung des Links * @param array $options * * @return string */ - public function createNewLink($table, $pid, $label = 'New', $options = []) + public function createEditLink($editTable, $editUid, $label = 'Edit', $options = []) + { + $uri = $this->buildEditUri($editTable, $editUid, 'edit', $options); + + $image = Icons::getSpriteIcon('actions-document-open', ['asIcon' => true]); + $recordButton = $this->makeLinkButton($uri, $label) + ->setIcon($image); + + $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : ''; + $recordButton->setClasses($class); + $recordButton->setHoverText($this->getHoverText($options) ?: $label); + + return $recordButton->render(); + } + + /** + * @param string $operation new or edit + */ + private function buildEditUri($table, $pid, $operation, array $options) { - $params = '&edit['.$table.']['.$pid.']=new'; + $returnUrl = T3General::getIndpEnv('REQUEST_URI'); + $uri = (string) $this->uriBuilder->buildUriFromRoute( + 'record_edit', + [ + 'id' => $pid, + 'returnUrl' => $returnUrl, + sprintf('edit[%s][%s]', $table, $pid) => $operation, + ] + ); if (isset($options[self::OPTION_PARAMS])) { - $params .= $options[self::OPTION_PARAMS]; + $uri .= $options[self::OPTION_PARAMS]; } - $params .= $this->buildDefVals($options); - $title = isset($options[self::OPTION_TITLE]) ? $options[self::OPTION_TITLE] : $GLOBALS['LANG']->getLL('new', 1); - $jsCode = BackendUtility::editOnClick($params); - if (isset($options[self::OPTION_CONFIRM]) && strlen($options[self::OPTION_CONFIRM]) > 0) { - $jsCode = 'if(confirm('.Strings::quoteJSvalue($options[self::OPTION_CONFIRM]).')) {'.$jsCode.'} else {return false;}'; - } - $image = Icons::getSpriteIcon('actions-document-new'); + return $uri; + } - $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : self::CSS_CLASS_BTN; - $class = ' class="'.$class.'"'; + /** + * Erstellt einen History-Link + * Achtung: Benötigt die JS-Funktion jumpExt() in der Seite. + * + * @param string $table + * @param int $recordUid + * + * @return string + */ + public function createHistoryLink($table, $recordUid, $label = '') + { + $moduleUrl = BackendUtility::getModuleUrl('record_history', ['element' => $table.':'.$recordUid]); + $options = [ + self::OPTION_ICON_NAME => 'actions-document-history-open', + ]; - return ''. - $image.$label.''; + $btn = $this->createModuleButton($moduleUrl, $label, $options); + + return $btn->render(); } /** @@ -298,15 +340,13 @@ public function createHideLink($table, $uid, $unhide = false, $options = []) $sEnableColumn = ($sEnableColumn) ? $sEnableColumn : 'hidden'; $label = isset($options['label']) ? $options['label'] : ''; - $image = Icons::getSpriteIcon( - $unhide ? 'actions-edit-unhide' : 'actions-edit-hide' - ); + $options[self::OPTION_ICON_NAME] = $unhide ? 'actions-edit-unhide' : 'actions-edit-hide'; $options['hover'] = $unhide ? 'Show' : 'Hide UID: '.$uid; return $this->createLinkForDataHandlerAction( 'data['.$table.']['.$uid.']['.$sEnableColumn.']='.($unhide ? 0 : 1), - $image.$label, + $label, $options ); } @@ -320,13 +360,23 @@ public function createHideLink($table, $uid, $unhide = false, $options = []) */ public function createInfoLink($editTable, $editUid, $label = 'Info', $options = []) { - $image = Icons::getSpriteIcon('actions-document-info'); - $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : self::CSS_CLASS_BTN; - $class = ' class="'.$class.'"'; - $label = isset($options['label']) ? $options['label'] : $label; + if (!TYPO3::isTYPO104OrHigher()) { + $image = Icons::getSpriteIcon('actions-document-info'); + $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : self::CSS_CLASS_BTN; + $class = ' class="'.$class.'"'; + $label = isset($options['label']) ? $options['label'] : $label; - return ''. - $image.$label.''; + return ''. + $image.$label.''; + } + $options[self::OPTION_ICON_NAME] = $options[self::OPTION_ICON_NAME] ?? 'actions-document-info'; + $options[self::OPTION_DATA_ATTR] = [ + 'dispatch-action' => 'TYPO3.InfoWindow.showItem', + 'dispatch-args-list' => sprintf('%s,%d', $editTable, $editUid), + ]; + $btn = $this->createModuleButton('#', $label, $options); + + return $btn->render(); } /** @@ -335,24 +385,26 @@ public function createInfoLink($editTable, $editUid, $label = 'Info', $options = * @param string $editTable DB-Tabelle des Datensatzes * @param int $recordUid UID des Datensatzes * @param int $currentPid PID der aktuellen Seite des Datensatzes - * @param string $label Bezeichnung des Links + * @param string $label Bezeichnung des Links */ - public function createMoveLink($editTable, $recordUid, $currentPid, $label = 'Move') + public function createMoveLink($editTable, $recordUid, $currentPid, $label = '') { - $this->initClipboard(); - $this->addBaseInlineJSCode(); - $isSel = (string) $this->clipObj->isSelected($editTable, $recordUid); - $image = Icons::getSpriteIcon('actions-edit-cut'.($isSel ? '-release' : '')); + $clipObj = $this->initClipboard(); + $isSel = (string) $clipObj->isSelected($editTable, $recordUid); + $options = []; + $options[self::OPTION_ICON_NAME] = 'actions-edit-cut'.($isSel ? '-release' : ''); + $tooltip = $isSel ? 'paste' : 'cut'; + $options[self::OPTION_HOVER_TEXT] = $this->getLanguageService()->sl('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.'.$tooltip); - return '' - .$image.''; + $uri = $clipObj->selUrlDB($editTable, $recordUid, 0, 'cut' === $isSel, ['returnUrl' => '']); + $btn = $this->createModuleButton($uri, $label, $options); + + return $btn->render(); } + /** + * @return \TYPO3\CMS\Backend\Clipboard\Clipboard + */ private function initClipboard() { if (!$this->clipObj) { @@ -360,13 +412,15 @@ private function initClipboard() // Initialize - reads the clipboard content from the user session $this->clipObj->initializeClipboard(); - $CB = T3GeneralAlias::_GET('CB'); + $CB = T3General::_GET('CB'); $this->clipObj->setCmd($CB ?? []); // Clean up pad $this->clipObj->cleanCurrent(); // Save the clipboard content $this->clipObj->endClipboard(); } + + return $this->clipObj; } /** @@ -376,22 +430,14 @@ private function initClipboard() * @param int $uid * @param int $moveId die uid des elements vor welches das element aus $uid gesetzt werden soll * @param array $options - * - * @TODO use $this->createLinkForDataHandlerAction */ public function createMoveUpLink($table, $uid, $moveId, $options = []) { - $jsCode = $this->getJavaScriptForLinkToDataHandlerAction('cmd['.$table.']['.$uid.'][move]=-'.$moveId.'&prErr=1&uPT=1', $options); - $label = isset($options['label']) ? $options['label'] : 'Move up'; - $title = isset($options['title']) ? $options['title'] : $label; - - $image = Icons::getSpriteIcon('actions-move-up'); - - return sprintf( - '%2$s%3$s', - $jsCode, - $image, - $label + return $this->createMoveUpDownLink( + 'cmd['.$table.']['.$uid.'][move]=-'.$moveId.'&prErr=1&uPT=1', + 'actions-move-up', + 'Move up', + $options ); } @@ -402,46 +448,27 @@ public function createMoveUpLink($table, $uid, $moveId, $options = []) * @param int $uid * @param int $moveId die uid des elements nach welchem das element aus $uid gesetzt werden soll * @param array $options - * - * @TODO use $this->createLinkForDataHandlerAction */ public function createMoveDownLink($table, $uid, $moveId, $options = []) { - $jsCode = $this->getJavaScriptForLinkToDataHandlerAction('cmd['.$table.']['.$uid.'][move]=-'.$moveId, $options); - $label = isset($options['label']) ? $options['label'] : 'Move up'; - $title = isset($options['title']) ? $options['title'] : $label; - - $image = Icons::getSpriteIcon('actions-move-down'); - - return sprintf( - '%2$s%3$s', - $jsCode, - $image, - $label + return $this->createMoveUpDownLink( + 'cmd['.$table.']['.$uid.'][move]=-'.$moveId, + 'actions-move-down', + 'Move down', + $options ); } - /** - * Creates js code with command for TCE datahandler and redirect to current script. - * Simple example to delete a page record: - * $this->getJavaScriptForLinkToDataHandlerAction('cmd[pages][123][delete]=1'). - * - * @param string $urlParameters command for datahandler - * @param array $options - * - * @return string - */ - protected function getJavaScriptForLinkToDataHandlerAction($urlParameters, array $options = []) + private function createMoveUpDownLink($cmd, $iconName, $defaultLabel, array $options) { - $redirect = TYPO3::isTYPO115OrHigher() ? null : -1; - $jumpToUrl = BackendUtility::getLinkToDataHandlerAction('&'.$urlParameters, $redirect); + $label = isset($options['label']) ? $options['label'] : $defaultLabel; + if (isset($options['title']) && !isset($options[self::OPTION_HOVER_TEXT])) { + $options[self::OPTION_HOVER_TEXT] = $options['title']; + } - // the jumpUrl method is no longer global available since TYPO3 8.7 - // furthermore we need the JS variable T3_THIS_LOCATION because it is used - // as redirect in getLinkToDataHandlerAction when -1 is passed - $this->addBaseInlineJSCode(); + $options[self::OPTION_ICON_NAME] = $iconName; - return $this->getConfirmCode('return jumpToUrl('.Strings::quoteJSvalue($jumpToUrl).');', $options); + return $this->createLinkForDataHandlerAction($cmd, $label, $options); } /** @@ -486,12 +513,12 @@ protected function getLinkThisScript($encode = true, array $options = []) */ public function createDeleteLink($table, $uid, $label = 'Remove', $options = []) { - $image = Icons::getSpriteIcon('actions-delete'); - $options['hover'] = 'Delete UID: '.$uid; + $options[self::OPTION_HOVER_TEXT] = 'Delete UID: '.$uid; + $options[self::OPTION_ICON_NAME] = 'actions-delete'; return $this->createLinkForDataHandlerAction( 'cmd['.$table.']['.$uid.'][delete]=1', - $image.$label, + $label, $options ); } @@ -539,14 +566,20 @@ public function createHidden($name, $value) return ''; } + /** + * @param string $onclick deprecated wird zukünftig wegen CSP nicht mehr unterstützt + */ public function createRadio($name, $value, $checked = false, $onclick = '') { - return ''; + return ''; } + /** + * @param string $onclick deprecated wird zukünftig wegen CSP nicht mehr unterstützt + */ public function createCheckbox($name, $value, $checked = false, $onclick = '') { - return ''; + return ''; } /** @@ -588,34 +621,60 @@ public function createLink($paramStr, $pid, $label, array $options = []) */ public function createModuleLink(array $params, $pid, $label, array $options = []) { - $label = $this->buildIconTag($options, $label); if (!isset($_GET['id']) && !isset($params['id'])) { // ensure pid is set even on POST requests. $params['id'] = $pid; } - $location = $this->getLinkThisScript(false, ['params' => $params]); + $uri = $this->getLinkThisScript(false, ['params' => $params]); + $recordButton = $this->createModuleButton($uri, $label, $options); - $jsCode = "window.location.href='".$location."'; return false;"; + return $recordButton->render(); + } - $title = ''; - if (!empty($options['hover'])) { - $title = 'title="'.$options['hover'].'"'; + /** + * @param string $uri + * @param mixed $label + * @param array $options + * @return EnhancedLinkButton + */ + private function createModuleButton(string $uri, $label, array $options = []) + { + $recordButton = $this->makeLinkButton($uri, $label); + + if (isset($options[self::OPTION_HOVER_TEXT])) { + $recordButton->setHoverText($options[self::OPTION_HOVER_TEXT]); } - $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : self::CSS_CLASS_BTN; - $class = 'class="'.$class.'"'; + if ($icon = $this->buildIcon($options)) { + $recordButton->setIcon($icon); + } - return ''.$label.''; + $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : ''; + if (isset($options[self::OPTION_CONFIRM]) && strlen($options[self::OPTION_CONFIRM]) > 0) { + $class .= ' t3js-modal-trigger'; + $recordButton->setDataAttributes(['content' => $options[self::OPTION_CONFIRM]]); + $recordButton->setOverrideCss(false); + } + $recordButton->setClasses($class); + + if (isset($options[self::OPTION_DATA_ATTR])) { + $recordButton->addDataAttributes((array) $options[self::OPTION_DATA_ATTR]); + } + + return $recordButton; } - protected function buildIconTag(array $options, $label = '') + /** + * @return \TYPO3\CMS\Core\Imaging\Icon|null + */ + protected function buildIcon(array $options) { - $tag = $label; + $tag = null; // $options['sprite'] für abwärtskompatibilität if (isset($options['icon']) || isset($options['sprite'])) { $icon = isset($options['icon']) ? $options['icon'] : $options['sprite']; // FIXME: label get lost here?? - $tag = Icons::getSpriteIcon($icon, $options); + $tag = Icons::getSpriteIcon($icon, array_merge(['asIcon' => true], $options)); } return $tag; @@ -633,24 +692,35 @@ protected function buildIconTag(array $options, $label = '') */ public function createSubmit($name, $value, $confirmMsg = '', $options = []) { - $icon = $this->buildIconTag($options, ''); - $onClick = ''; - if (strlen($confirmMsg)) { - $onClick = 'onclick="return confirm('.Strings::quoteJSvalue($confirmMsg).')"'; - } + $value = htmlspecialchars($value); + $icon = $this->buildIcon($options); + $icon = $icon ? $icon->render() : ''; $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : self::CSS_CLASS_BTN; - $class = ' class="'.$class.'"'; + $class .= ' rnbase-btn'; - if ($icon) { - $btn = ''; - } else { - $btn = ' $name, + 'value' => $value, + 'title' => $options[self::OPTION_HOVER_TEXT] ?? $name, + ]; + if (!TYPO3::isTYPO121OrHigher()) { + $attributes['data-href'] = sprintf('javascript:%s', 'document.forms[\'editform\'].submit()'); + } + + if (strlen($confirmMsg)) { + $class .= ' t3js-modal-trigger'; + $attributes['data-content'] = $confirmMsg; + // Der Name des Submit-Buttons liegt nicht mehr im POST. Deshalb ist zusätzliches JS notwendig. + $this->insertJsToolbox(); } + $attributes['class'] = $class; + + $attributesString = T3General::implodeAttributes($attributes, true); + + $btn = ''; + return $btn; } @@ -699,17 +769,17 @@ public function createIntInput($name, $value, $width, $maxlength = 10) /** * Erstellt ein Eingabefeld für DateTime. - * - * @todo fix prefilling of field in TYPO3 8.7 */ - public function createDateInput($name, $value) + public function createDateInput($name, $value, array $options = []) { // Take care of current time zone. Thanks to Thomas Maroschik! if (Math::isInteger($value) && !TYPO3::isTYPO121OrHigher()) { $value += date('Z', $value); } $this->initializeJavaScriptFormEngine(); - $dateElementClass = \TYPO3\CMS\Backend\Form\Element\InputDateTimeElement::class; + $dateElementClass = TYPO3::isTYPO121OrHigher() ? + \TYPO3\CMS\Backend\Form\Element\DatetimeElement::class : + \TYPO3\CMS\Backend\Form\Element\InputDateTimeElement::class; // [itemFormElName] => data[tx_cfcleague_games][4][status] // [itemFormElID] => data_tx_cfcleague_games_4_status @@ -752,6 +822,16 @@ public function createDateInput($name, $value) $pageRenderer->loadRequireJsModule($moduleName, $callback); } } + } elseif ($renderedElement['javaScriptModules'] ?? null) { + $pageRenderer = $this->getDoc()->getPageRenderer(); + foreach ($renderedElement['javaScriptModules'] as $moduleName) { + /* @var \TYPO3\CMS\Core\Page\JavaScriptModuleInstruction $moduleName */ + $this->insertJsModule($moduleName->getName()); + } + } + + if (isset($options[self::OPTION_HIDE_LABEL])) { + $renderedElement['html'] = preg_replace('//', '', $renderedElement['html']); } return $renderedElement['html']; @@ -767,17 +847,17 @@ protected function initializeJavaScriptFormEngine() $usDateFormat = 0; if (!TYPO3::isTYPO121OrHigher()) { $usDateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '1' : '0'; - } - $initializeFormEngineCallback = 'function(FormEngine) { - FormEngine.initialize( - '.$moduleUrl.','.$usDateFormat.' + $initializeFormEngineCallback = 'function(FormEngine) { + FormEngine.initialize( + '.$moduleUrl.','.$usDateFormat.' + ); + }'; + + $this->getDoc()->getPageRenderer()->loadRequireJsModule( + 'TYPO3/CMS/Backend/FormEngine', + $initializeFormEngineCallback ); - }'; - - $this->getDoc()->getPageRenderer()->loadRequireJsModule( - 'TYPO3/CMS/Backend/FormEngine', - $initializeFormEngineCallback - ); + } $this->getDoc()->getPageRenderer()->addInlineSetting('FormEngine', 'formName', 'editform'); } @@ -824,6 +904,11 @@ public function createSelectSingleByArray($name, $value, $arr, $options = 0) return $this->createSelectByArray($name, $value, $arr, $options); } + private function insertJsToolbox() + { + $this->insertJsModule('@sys25/rn_base/toolbox.js'); + } + /** * Erstellt eine Select-Box aus dem übergebenen Array. * in den Options kann mit dem key reload angegeben werden, @@ -844,20 +929,44 @@ public function createSelectByArray($name, $currentValues, array $selectOptions, { $options = is_array($options) ? $options : []; - $onChangeStr = !empty($options['reload']) ? ' this.form.submit(); ' : ''; - if (!empty($options['onchange'])) { + $attrArr = []; + $onChangeStr = ''; + if (!empty($options['reload'])) { + if (TYPO3::isTYPO104OrHigher()) { + $attrArr[] = 'data-global-event="change" data-action-submit="$form"'; + } else { + $onChangeStr = ' this.form.submit(); '; + } + $this->insertJsToolbox(); + } + + if (isset($options['onchange'])) { $onChangeStr .= $options['onchange']; } if ($onChangeStr) { - $onChangeStr = ' onchange="'.$onChangeStr.'" '; + $onChangeStr = 'onchange="'.$onChangeStr.'"'; + $attrArr[] = $onChangeStr; } - $multiple = !empty($options['multiple']) ? ' multiple="multiple"' : ''; + $multiple = !empty($options['multiple']) ? 'multiple="multiple"' : ''; + if ($multiple) { + $attrArr[] = $multiple; + } $name .= !empty($options['multiple']) ? '[]' : ''; - $size = !empty($options['size']) ? ' size="'.$options['size'].'"' : ''; + $size = !empty($options['size']) ? 'size="'.$options['size'].'"' : ''; + if ($size) { + $attrArr[] = $size; + } + $classes = $options[self::OPTION_CSS_CLASSES] ?? ''; + + $attr = implode(' ', $attrArr); - $out = '', + $name, + trim('select '.$classes), + $attr ? ' '.$attr : '' + ); $currentValues = Strings::trimExplode(',', $currentValues); @@ -952,6 +1061,8 @@ public function getJSCode($pid, $location = '') * and global Vars T3_RETURN_URL and T3_THIS_LOCATION to PageRenderer. * * @param string $location + * + * @deprecated should not be used anymore */ public function addBaseInlineJSCode($location = '') { @@ -971,8 +1082,10 @@ public function addBaseInlineJSCode($location = '') * @param string $location module url or empty * * @return string + * + * @deprecated should not be used anymore */ - protected function getBaseJavaScriptCode($location = '') + private function getBaseJavaScriptCode($location = '') { $location = $location ? $location : $this->getLinkThisScript(false); @@ -1030,7 +1143,7 @@ public function showTabMenu($pid, $name, $modName, $entries) ]; $SETTINGS = BackendUtility::getModuleData( $MENU, - T3GeneralAlias::_GP('SET'), + T3General::_GP('SET'), $modName ); $menuItems = []; @@ -1042,13 +1155,7 @@ public function showTabMenu($pid, $name, $modName, $entries) $menuItems[] = [ 'isActive' => $SETTINGS[$name] == $key, 'label' => $value, - // jumpUrl ist ab TYPO3 6.2 nicht mehr nötig - // @TODO jumpUrl entfernen wenn kein Support mehr für 4.5 - // Also jumpUrl wird auch in der 12 zumindest noch verwendet... - 'url' => '#', - 'addParams' => 'onclick="jumpToUrl(\''. - $this->buildScriptURI(['id' => $pid, 'SET['.$name.']' => $key]). - '\',this);"', + 'url' => $this->buildScriptURI(['id' => $pid, 'SET['.$name.']' => $key]), ]; } @@ -1105,8 +1212,7 @@ public static function showMenu($pid, $name, $modName, $entries, $script = '', $ if (is_array($MENU[$name]) && 1 == count($MENU[$name])) { $ret['menu'] = self::buildDummyMenu('SET['.$name.']', $MENU[$name]); } else { - $funcMenu = 'getDropdownMenu'; - $ret['menu'] = BackendUtility::$funcMenu( + $ret['menu'] = BackendUtility::getDropdownMenu( $pid, 'SET['.$name.']', $SETTINGS[$name], @@ -1183,6 +1289,8 @@ public function getTCEFormArray($table, $theUid, $isNew = false) } /** + * $this->createLinkForDataHandlerAction('cmd[pages][123][delete]=1'). + * * @param string $actionParameters * @param string $label * @param array $options @@ -1191,22 +1299,55 @@ public function getTCEFormArray($table, $theUid, $isNew = false) */ public function createLinkForDataHandlerAction($actionParameters, $label, array $options = []) { - // $options['sprite'] für abwärtskompatibilität - if (isset($options['icon']) || isset($options['sprite'])) { - $icon = isset($options['icon']) ? $options['icon'] : $options['sprite']; - $label = Icons::getSpriteIcon($icon, $options); + if (isset($options['sprite'])) { + $options[self::OPTION_ICON_NAME] = $options['sprite']; } + $uri = $this->buildDataHandlerUri($actionParameters, ''); - $jsCode = $this->getJavaScriptForLinkToDataHandlerAction($actionParameters, $options); - $title = ''; - if (!empty($options['hover'])) { - $title = 'title="'.$options['hover'].'"'; + $btn = $this->createModuleButton($uri, $label, $options); + + return $btn->render(); + } + + /** + * Bietet eine Möglichkeit JS-Module sowohl per AMD als auch ES6 (ab TYPO3 12) zu laden. + * + * ES6-Modul: @vendor/ext_key/some-es6-module.js + * wird konvertiert in + * AMD-Module: 'TYPO3/CMS/ExtKey/SomeEs6Module' + * + * Es die entsprechenden Dateien müssen natürlich in der jeweiligen Extension bereitgestellt werden. + * + * @param string $es6Module Name des ES6-Moduls + * @param string $callBackFunction optionaler Startup-Code for AMD-Variante. Ab T3 12 nicht mehr verwendet. + */ + public function insertJsModule(string $es6Module, $callBackFunction = null) + { + $pageRenderer = $this->doc->getPageRenderer(); + if (TYPO3::isTYPO121OrHigher()) { + $pageRenderer->loadJavaScriptModule($es6Module); + } else { + list($vendor, $extKey, $jsModule) = explode('/', $es6Module, 3); + $extKey = Strings::underscoredToUpperCamelCase($extKey); + $jsModule = Strings::dashedToUpperCamelCase($jsModule); + + $pageRenderer->loadRequireJsModule( + sprintf('TYPO3/CMS/%s/%s', $extKey, substr($jsModule, 0, -3)), + $callBackFunction + ); } + } - $class = array_key_exists('class', $options) ? htmlspecialchars($options['class']) : self::CSS_CLASS_BTN; - $class = 'class="'.$class.'"'; + protected function buildDataHandlerUri(string $params, $redirect) + { + return BackendUtility::getLinkToDataHandlerAction('&'.$params, $redirect); + } - return ''.$label.''; + /** + * @return \TYPO3\CMS\Core\Localization\LanguageService|\TYPO3\CMS\Lang\LanguageService + */ + public function getLanguageService() + { + return $this->lang; } } diff --git a/Classes/Backend/Module/BaseModule.php b/Classes/Backend/Module/BaseModule.php index faa1df96..aeea86b5 100644 --- a/Classes/Backend/Module/BaseModule.php +++ b/Classes/Backend/Module/BaseModule.php @@ -503,16 +503,8 @@ protected function initDoc($doc) $doc->inDocStylesArray[] = $doc->inDocStyles; $doc->tableLayout = $this->getTableLayout(); $doc->setModuleTemplate($this->getModuleTemplate()); + // Ernsthaft?? $doc->loadJavascriptLib('contrib/prototype/prototype.js'); - // JavaScript - $doc->JScode .= ' - - '; if (!TYPO3::isTYPO115OrHigher()) { // TODO: Die Zeile könnte problematisch sein... diff --git a/Classes/Backend/Template/ModuleTemplate.php b/Classes/Backend/Template/ModuleTemplate.php index 384dc4e5..c6e30787 100644 --- a/Classes/Backend/Template/ModuleTemplate.php +++ b/Classes/Backend/Template/ModuleTemplate.php @@ -247,24 +247,6 @@ protected function initDoc($doc) $doc->inDocStylesArray[] = $doc->inDocStyles; // $doc->tableLayout = $this->getTableLayout(); $doc->setModuleTemplate($this->options['template']); - // $doc->getPageRenderer()->loadJquery(); - // JavaScript - $doc->JScode .= ' - - '; - - if (!TYPO3::isTYPO115OrHigher()) { - // TODO: Die Zeile könnte problematisch sein... - $doc->postCode = ' - '; - } } private function prepareOptions($options) diff --git a/Classes/Backend/Template/Override/DocumentTemplate.php b/Classes/Backend/Template/Override/DocumentTemplate.php index e7829b71..9a365c7b 100644 --- a/Classes/Backend/Template/Override/DocumentTemplate.php +++ b/Classes/Backend/Template/Override/DocumentTemplate.php @@ -2,9 +2,11 @@ namespace Sys25\RnBase\Backend\Template\Override; +use Sys25\RnBase\Backend\Utility\Icons; use Sys25\RnBase\Utility\Files; use Sys25\RnBase\Utility\Strings; use Sys25\RnBase\Utility\T3General; +use Sys25\RnBase\Utility\TYPO3; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -34,18 +36,17 @@ class DocumentTemplate { + public const STATE_NOTICE = -2; + public const STATE_INFO = -1; public const STATE_OK = -1; - - public const STATE_NOTICE = 1; - - public const STATE_WARNING = 2; - - public const STATE_ERROR = 3; - public const STATE_DEFAULT = 0; + public const STATE_SUCCESS = 0; + public const STATE_WARNING = 1; + public const STATE_ERROR = 2; public $divClass = false; + /** @deprecated use external js files */ public $JScode = ''; public $endOfPageJsBlock = ''; /** @@ -54,10 +55,10 @@ class DocumentTemplate * @var array */ public $JScodeArray = ['jumpToUrl' => ' -function jumpToUrl(URL) { - window.location.href = URL; - return false; -} + function jumpToUrl(URL) { + window.location.href = URL; + return false; + } ']; /** @@ -91,6 +92,12 @@ function jumpToUrl(URL) { protected $moduleTemplateFilename; public $form; + /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService */ + protected $flashMessageService; + + /** @var LanguageService */ + private $lang; + /** * Constructor. */ @@ -98,6 +105,9 @@ public function __construct() { // Initializes the page rendering object: $this->initPageRenderer(); + + $this->flashMessageService = T3General::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class); + $this->lang = $GLOBALS['LANG']; } /** @@ -236,7 +246,7 @@ public function section($label, $text, $nostrtoupper = false, $sH = false, $type '
'. ''. ''. - ''. + ''. ''. '
'; } @@ -283,8 +293,12 @@ public function divider($dist) public function insertStylesAndJS($content) { // Insert accumulated JS - $jscode = $this->JScode.LF.GeneralUtility::wrapJS(implode(LF, $this->JScodeArray)); - $content = str_replace('', $jscode, $content); + $jscode = ''; + // TODO: check lowest version + if (!TYPO3::isTYPO121OrHigher()) { + $jscode = $this->JScode.LF.GeneralUtility::wrapJS(implode(LF, $this->JScodeArray)); + $content = str_replace('', $jscode, $content); + } return $content; } @@ -390,8 +404,8 @@ public function wrapScriptTags($string, $linebreak = true) // Remove nl from the beginning $string = ltrim($string, LF); // Re-ident to one tab using the first line as reference - if (TAB === $string[0]) { - $string = TAB.ltrim($string, TAB); + if ("\t" === $string[0]) { + $string = "\t".ltrim($string, "\t"); } $string = $cr.'