diff --git a/Build/Resources/Public/Less/Component/module.less b/Build/Resources/Public/Less/Component/module.less new file mode 100644 index 0000000000000000000000000000000000000000..cbbe71d916f958ee64470b5726fe840f5724d175 --- /dev/null +++ b/Build/Resources/Public/Less/Component/module.less @@ -0,0 +1,88 @@ +// +// ModuleTemplate +// ====== +// General component for backend modules. +// + + +// +// Variables +// +@module-bg: #fff; + +@module-docheader-bg: #eee; +@module-docheader-border: #c3c3c3; +@module-docheader-zindex: 300; +@module-docheader-height: 65px; +@module-docheader-padding-vertical: 0; +@module-docheader-padding-horizontal: 24px; +@module-docheader-padding: @module-docheader-padding-vertical @module-docheader-padding-horizontal; + +@module-docheader-bar-height: 26px; +@module-docheader-bar-margin-vertical: 4px; +@module-docheader-bar-margin-horizontal: 0; +@module-docheader-bar-margin: @module-docheader-bar-margin-vertical @module-docheader-bar-margin-horizontal; + +@module-body-padding-vertical: 24px; +@module-body-padding-horizontal: 24px; +@module-body-padding: @module-body-padding-vertical @module-body-padding-horizontal; + + +// +// Template +// +.module { + height: 100%; + width: 100%; + background-color: @module-bg; + form { + margin: 0; + } +} + + +// +// Docheader +// +.module-docheader { + .clearfix; + position: fixed; + width: 100%; + top: 0; + left: 0; + height: @module-docheader-height; + z-index: @module-docheader-zindex; + background-color: @module-docheader-bg; + border-bottom: 1px solid @module-docheader-border; + padding: @module-docheader-padding; +} +.module-docheader-bar { + .clearfix; + height: @module-docheader-bar-height; + margin: @module-docheader-bar-margin; + line-height: @module-docheader-bar-height; + label { + margin-top: 0; + margin-bottom: 0; + } + .form-group { + margin: 0; + } +} +.module-docheader-bar-column-left { + float: left; +} +.module-docheader-bar-column-right { + float: right; +} + + +// +// Body +// +.module-body { + padding: @module-body-padding; +} +.module-docheader + .module-body { + padding-top: @module-docheader-height + @module-body-padding-vertical; +} diff --git a/Build/Resources/Public/Less/TYPO3/_element_buttons.less b/Build/Resources/Public/Less/TYPO3/_element_buttons.less index 6a339682c575cbf13f49a0c526fadef00fc6397d..20eea7dfea2c43581f1b9cfa9f1c900e71654223 100644 --- a/Build/Resources/Public/Less/TYPO3/_element_buttons.less +++ b/Build/Resources/Public/Less/TYPO3/_element_buttons.less @@ -1,10 +1,8 @@ // // Button // -.btn { - .t3-icon { - margin: 0; - } +.btn-sm { + min-height: floor(2px + (@padding-small-vertical * 2) + (@font-size-small * @line-height-small)); } // diff --git a/Build/Resources/Public/Less/_minimal.less b/Build/Resources/Public/Less/_minimal.less index 8e1564a6e71b68d0ee5ff615731abf929b2db2f5..789aeada41af1a51033c4ea5d36a0768c8366156 100644 --- a/Build/Resources/Public/Less/_minimal.less +++ b/Build/Resources/Public/Less/_minimal.less @@ -74,6 +74,7 @@ @import "Component/callout.less"; @import "Component/icon.less"; @import "Component/diff.less"; +@import "Component/module.less"; // // Bootstrap Utility classes diff --git a/typo3/sysext/backend/Classes/Module/AbstractModule.php b/typo3/sysext/backend/Classes/Module/AbstractModule.php new file mode 100644 index 0000000000000000000000000000000000000000..901f3e9255ef198f59eb0b2f2d2d976b3832fe5c --- /dev/null +++ b/typo3/sysext/backend/Classes/Module/AbstractModule.php @@ -0,0 +1,81 @@ +<?php +namespace TYPO3\CMS\Backend\Module; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Backend\Template\ModuleTemplate; +use TYPO3\CMS\Core\Imaging\IconFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * A backend module. This class may be used by extension backend modules + * to implement own actions and controllers. It initializes the module + * template and comes with a simple dispatcher method. + * + * @internal Experimental for now + */ +class AbstractModule { + + /** + * ModuleTemplate object + * + * @var ModuleTemplate + */ + protected $moduleTemplate; + + /** + * IconFactory object + * + * @var IconFactory + */ + protected $iconFactory; + + /** + * Constructor Method + */ + public function __construct() { + $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class); + $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class); + } + + /** + * PSR Request Object + * + * @var ServerRequestInterface + */ + protected $request; + + /** + * Central Request Dispatcher + * + * @param ServerRequestInterface $request PSR7 Request Object + * @param ResponseInterface $response PSR7 Response Object + * + * @return ResponseInterface + * + * @throws \InvalidArgumentException In case an action is not callable + */ + public function processRequest(ServerRequestInterface $request, ResponseInterface $response) { + $methodName = $request->getQueryParams()['action'] ?: 'index'; + if (!is_callable([$this, $methodName])) { + throw new \InvalidArgumentException( + 'The method "' . $methodName . '" is not callable within "' . get_class($this) . '".', + 1442736343 + ); + } + return $this->{$methodName}($request, $response); + } +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/AbstractControl.php b/typo3/sysext/backend/Classes/Template/Components/AbstractControl.php new file mode 100644 index 0000000000000000000000000000000000000000..d7eedf4cc9b69ad0c2705e07e4c614b94127276f --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/AbstractControl.php @@ -0,0 +1,135 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * Control used by various components + */ +class AbstractControl { + + /** + * HTML tag attribute for class + * + * @var string + */ + protected $classes = ''; + + /** + * HTML tag attribute for title + * + * @var string + */ + protected $title = ''; + + /** + * HTML tag attributes for data-* + * Use key => value pairs + * + * @var array + */ + protected $dataAttributes = []; + + /** + * HTML tag attribute onClick + * Outdated, use sparingly + * + * @var string + */ + protected $onClick = ''; + + /** + * Get classes + * + * @return string + */ + public function getClasses() { + return $this->classes; + } + + /** + * Get Title + * + * @return string + */ + public function getTitle() { + return $this->title; + } + + /** + * Get Data attributes + * + * @return array + */ + public function getDataAttributes() { + return $this->dataAttributes; + } + + /** + * Get Onclick Attribute + * + * @return string + */ + public function getOnClick() { + return $this->onClick; + } + + /** + * Set classes + * + * @param string $classes HTML class attribute to set + * + * @return $this + */ + public function setClasses($classes) { + $this->classes = $classes; + return $this; + } + + /** + * Set title attribute + * + * @param string $title HTML title attribute to set + * + * @return $this + */ + public function setTitle($title) { + $this->title = $title; + return $this; + } + + /** + * Set Data attributes + * + * @param array $dataAttributes HTML data attributes to set + * + * @return $this + */ + public function setDataAttributes(array $dataAttributes) { + $this->dataAttributes = $dataAttributes; + return $this; + } + + /** + * Set OnClick + * + * @param string $onClick HTML onClick attribute to set + * + * @return $this + */ + public function setOnClick($onClick) { + $this->onClick = $onClick; + return $this; + } +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/ButtonBar.php b/typo3/sysext/backend/Classes/Template/Components/ButtonBar.php new file mode 100644 index 0000000000000000000000000000000000000000..57be284675984a589ea37ab917231aa7bbc4d115 --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/ButtonBar.php @@ -0,0 +1,137 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface; +use TYPO3\CMS\Backend\Template\Components\Buttons\FullyRenderedButton; +use TYPO3\CMS\Backend\Template\Components\Buttons\InputButton; +use TYPO3\CMS\Backend\Template\Components\Buttons\LinkButton; +use TYPO3\CMS\Backend\Template\Components\Buttons\SplitButton; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Bar holding the buttons + */ +class ButtonBar { + + /** + * Identifier for the left button bar + */ + const BUTTON_POSITION_LEFT = 'left'; + + /** + * Identifier for the right button bar + */ + const BUTTON_POSITION_RIGHT = 'right'; + + /** + * Internal array of all registered buttons + * + * @var array + */ + protected $buttons = []; + + /** + * Add button + * + * @param ButtonInterface $button The Button Object to add + * @param string $buttonPosition Position of the button (left/right) + * @param int $buttonGroup Buttongroup of the button + * + * @throws \InvalidArgumentException In case a button is not valid + * + * @return $this + */ + public function addButton( + ButtonInterface $button, + $buttonPosition = self::BUTTON_POSITION_LEFT, + $buttonGroup = 1 + ) { + if (!$button->isValid($button)) { + throw new \InvalidArgumentException('Button "' . $button->getType() . '" is not valid', 1441706370); + } + // We make the button immutable here + $this->buttons[$buttonPosition][$buttonGroup][] = clone $button; + return $this; + } + + /** + * Creates a new button of the given type + * + * @param string $button ButtonClass to invoke. Must implement ButtonInterface + * + * @throws \InvalidArgumentException In case a ButtonClass does not implement + * ButtonInterface + * + * @return ButtonInterface + */ + public function makeButton($button) { + if (!in_array(ButtonInterface::class, class_implements($button), TRUE)) { + throw new \InvalidArgumentException('A Button must implement ButtonInterface', 1441706378); + } + return GeneralUtility::makeInstance($button); + } + + /** + * Creates a new InputButton + * + * @return InputButton + */ + public function makeInputButton() { + return GeneralUtility::makeInstance(InputButton::class); + } + + /** + * Creates a new SplitButton + * + * @return SplitButton + */ + public function makeSplitButton() { + return GeneralUtility::makeInstance(SplitButton::class); + } + + /** + * Creates a new LinkButton + * + * @return LinkButton + */ + public function makeLinkButton() { + return GeneralUtility::makeInstance(LinkButton::class); + } + + /** + * Creates a new FullyRenderedButton + * + * @return FullyRenderedButton + */ + public function makeFullyRenderedButton() { + return GeneralUtility::makeInstance(FullyRenderedButton::class); + } + + /** + * Returns an associative array of all buttons in the form of + * ButtonPosition > ButtonGroup > Button + * + * @return array + */ + public function getButtons() { + // here we need to call the sorting methods and stuff. + foreach ($this->buttons as $position => $_) { + ksort($this->buttons[$position]); + } + // @todo do we want to provide a hook here? + return $this->buttons; + } +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/Buttons/AbstractButton.php b/typo3/sysext/backend/Classes/Template/Components/Buttons/AbstractButton.php new file mode 100644 index 0000000000000000000000000000000000000000..e5d8bdb32e671dc69e42ae272843512df99714dc --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/Buttons/AbstractButton.php @@ -0,0 +1,100 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components\Buttons; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\AbstractControl; +use TYPO3\CMS\Core\Imaging\Icon; + +/** + * AbstractButton + */ +class AbstractButton extends AbstractControl implements ButtonInterface { + + /** + * Icon object + * + * @var Icon + */ + protected $icon; + + /** + * ButtonType + * + * @var string + */ + protected $type; + + /** + * Get icon + * + * @return Icon + */ + public function getIcon() { + return $this->icon; + } + + /** + * Get type + * + * @return string + */ + public function getType() { + return get_class($this); + } + + /** + * Set icon + * + * @param Icon $icon Icon object for the button + * + * @return $this + */ + public function setIcon(Icon $icon) { + $this->icon = $icon; + return $this; + } + + /** + * Implementation from ButtonInterface + * This object is an abstract, so no implementation is necessary + * + * @return bool + */ + public function isValid() { + return FALSE; + } + + /** + * Implementation from ButtonInterface + * This object is an abstract, so no implementation is necessary + * + * @return string + */ + public function __toString() { + return ''; + } + + /** + * Implementation from ButtonInterface + * This object is an abstract, so no implementation is necessary + * + * @return string + */ + public function render() { + return ''; + } + + +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/Buttons/ButtonInterface.php b/typo3/sysext/backend/Classes/Template/Components/Buttons/ButtonInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..df1f83bad5a066c69d3cbf3c58e48302a7b0003d --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/Buttons/ButtonInterface.php @@ -0,0 +1,49 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components\Buttons; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * Interface for buttons + */ +interface ButtonInterface { + + /** + * Validates all set parameters of a button. + * + * @return bool + */ + public function isValid(); + + /** + * Returns the fully qualified class name of the button as a string + * + * @return string + */ + public function getType(); + + /** + * RenderMethod of a button if accessed as string from fluid + * + * @return string + */ + public function __toString(); + + /** + * Renders the markup for the button + * + * @return string + */ + public function render(); +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/Buttons/FullyRenderedButton.php b/typo3/sysext/backend/Classes/Template/Components/Buttons/FullyRenderedButton.php new file mode 100644 index 0000000000000000000000000000000000000000..6af32e61447b1051f8f56b69bdc054d9a5a6043d --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/Buttons/FullyRenderedButton.php @@ -0,0 +1,106 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components\Buttons; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * FullyRenderedButton + * + * This button type is an intermediate solution for buttons that are rendered + * by methods from TYPO3 itself, like the CSH buttons or Bookmark buttons. + * + * There should be no need to use them, so do yourself a favour and don't. + * + * EXAMPLE USAGE TO ADD A BUTTON TO THE FIRST BUTTON GROUP IN THE LEFT BAR: + * + * $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); + * $myButton = $buttonBar->makeFullyRenderedButton() + * ->setHtmlSource('<span class="i-should-not-be-using-this>Foo</span>'); + * $buttonBar->addButton($myButton, ButtonBar::BUTTON_POSITION_LEFT, 1); + */ +class FullyRenderedButton implements ButtonInterface { + + /** + * The full HTML source of the rendered button. + * This source will be passed through to the frontend as is, + * so keep htmlspecialchars() in mind + * + * @var string + */ + protected $htmlSource = ''; + + /** + * Gets the HTML Source of the button + * + * @return string + */ + public function getHtmlSource() { + return $this->htmlSource; + } + + /** + * Sets the HTML Source of the button and returns itself + * + * @param string $htmlSource HTML sourcecode of the button + * + * @return FullyRenderedButton + */ + public function setHtmlSource($htmlSource) { + $this->htmlSource = $htmlSource; + return $this; + } + + /** + * Gets the type of the button + * + * @return string + */ + public function getType() { + return get_class($this); + } + + /** + * Validator for a FullyRenderedButton + * + * @return bool + */ + public function isValid() { + if ( + trim($this->getHtmlSource()) !== '' + && $this->getType() === FullyRenderedButton::class + ) { + return TRUE; + } + return FALSE; + } + + /** + * Renders the button + * + * @return string + */ + public function __toString() { + return $this->render(); + } + + /** + * Renders the button + * + * @return string + */ + public function render() { + return '<span class="btn btn-sm btn-default">' . $this->getHtmlSource() . '</span>'; + } + +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/Buttons/InputButton.php b/typo3/sysext/backend/Classes/Template/Components/Buttons/InputButton.php new file mode 100644 index 0000000000000000000000000000000000000000..70038435a21184cb2ae5e83ff93cad79d380b083 --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/Buttons/InputButton.php @@ -0,0 +1,141 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components\Buttons; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * InputButton + * + * This button type renders a HTML tag <button> and takes the HTML attributes + * name and value as additional attributes to those defined in AbstractButton. + * + * Since we no longer want to have any <input type="submit" /> in the TYPO3 core + * you should use this button type to send forms + * + * EXAMPLE USAGE TO ADD A BUTTON TO THE FIRST BUTTON GROUP IN THE LEFT BAR: + * + * $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); + * $saveButton = $buttonBar->makeInputButton() + * ->setName('save') + * ->setValue('1') + * ->setIcon($this->iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL)) + * ->setTitle('Save'); + * $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 1); + */ +class InputButton extends AbstractButton implements ButtonInterface { + + /** + * Name Attribute of the button + * + * @var string + */ + protected $name = ''; + + /** + * Value attribute of the button + * + * @var string + */ + protected $value = ''; + + /** + * Get name + * + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Set name + * + * @param string $name Name attribute + * + * @return InputButton + */ + public function setName($name) { + $this->name = $name; + return $this; + } + + /** + * Get value + * + * @return string + */ + public function getValue() { + return $this->value; + } + + /** + * Set value + * + * @param string $value Value attribute + * + * @return InputButton + */ + public function setValue($value) { + $this->value = $value; + return $this; + } + + /** + * Validates the current button + * + * @return bool + */ + public function isValid() { + if ( + trim($this->getName()) !== '' + && trim($this->getValue()) !== '' + && trim($this->getTitle()) !== '' + && $this->getType() === InputButton::class + && $this->getIcon() !== NULL + ) { + return TRUE; + } + return FALSE; + } + + /** + * Renders the markup of the button + * + * @return string + */ + public function render() { + $content = ' + <button name="' . + htmlspecialchars($this->getName()) . + '" class="' . + htmlspecialchars($this->getClasses()) . + '" value="' . + htmlspecialchars($this->getValue()) . + '" title="' . + htmlspecialchars($this->getTitle()) . + '">' . $this->getIcon()->__toString() . + '</button> + '; + return $content; + } + + /** + * Magic method so Fluid can access a button via {button} + * + * @return string + */ + public function __toString() { + return $this->render(); + } +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/Buttons/LinkButton.php b/typo3/sysext/backend/Classes/Template/Components/Buttons/LinkButton.php new file mode 100644 index 0000000000000000000000000000000000000000..aead71088e7907fcceaf51561cb5c3509e1e7063 --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/Buttons/LinkButton.php @@ -0,0 +1,114 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components\Buttons; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * LinkButton + * + * This button type renders a regular anchor tag with TYPO3s way to render a + * button control. + * + * EXAMPLE USAGE TO ADD A BUTTON TO THE FIRST BUTTON GROUP IN THE LEFT BAR: + * + * $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); + * $saveButton = $buttonBar->makeLinkButton() + * ->setHref('#') + * ->setDataAttributes([ + * 'foo' => 'bar' + * ]) + * ->setIcon($this->iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL)) + * ->setTitle('Save'); + * $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 1); + */ +class LinkButton extends AbstractButton implements ButtonInterface { + + /** + * HREF attribute of the link + * + * @var string + */ + protected $href = ''; + + /** + * Get href + * + * @return string + */ + public function getHref() { + return $this->href; + } + + /** + * Set href + * + * @param string $href HREF attribute + * + * @return LinkButton + */ + public function setHref($href) { + $this->href = $href; + return $this; + } + + /** + * Validates the current button + * + * @return bool + */ + public function isValid() { + if ( + trim($this->getHref()) !== '' + && trim($this->getTitle()) !== '' + && $this->getType() === LinkButton::class + && $this->getIcon() !== NULL + ) { + return TRUE; + } + return FALSE; + } + + /** + * Renders the markup for the button + * + * @return string + */ + public function render() { + if ($this->onClick !== '') { + $onClick = 'onclick="' . htmlspecialchars($this->onClick) . '"'; + } else { + $onClick = ''; + } + return '<a href="' . + htmlspecialchars($this->getHref()) . + '" class="btn btn-sm btn-default ' . + htmlspecialchars($this->getClasses()) . + '" ' . + $onClick . + ' title="' . + htmlspecialchars($this->getTitle()) . + '">' . + $this->getIcon()->__toString() . + '</a>'; + } + + /** + * Magic method so Fluid can access a button via {button} + * + * @return string + */ + public function __toString() { + return $this->render(); + } +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/Buttons/SplitButton.php b/typo3/sysext/backend/Classes/Template/Components/Buttons/SplitButton.php new file mode 100644 index 0000000000000000000000000000000000000000..881e1314b772e65353f57c176de61987c44e2b4b --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/Buttons/SplitButton.php @@ -0,0 +1,172 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components\Buttons; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * SplitButton + * + * This button type renders a bootstrap split button. + * It takes multiple button objects as parameters + * + * EXAMPLE USAGE TO ADD A SPLIT BUTTON TO THE FIRST BUTTON GROUP IN THE LEFT BAR: + * + * $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); + * + * $saveButton = $buttonBar->makeInputButton() + * ->setName('save') + * ->setValue('1') + * ->setIcon($this->iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL)) + * ->setTitle('Save'); + * + * $saveAndCloseButton = $buttonBar->makeInputButton() + * ->setName('save_and_close') + * ->setValue('1') + * ->setTitle('Save and close') + * ->setIcon($this->iconFactory->getIcon('actions-document-save-close', Icon::SIZE_SMALL)); + * + * $saveAndShowPageButton = $buttonBar->makeInputButton() + * ->setName('save_and_show') + * ->setValue('1') + * ->setTitle('Save and show') + * ->setIcon($this->iconFactory->getIcon('actions-document-save-view', Icon::SIZE_SMALL)); + * + * $splitButtonElement = $buttonBar->makeSplitButton() + * ->addItem($saveButton, TRUE) + * ->addItem($saveAndCloseButton) + * ->addItem($saveAndShowPageButton); + */ +class SplitButton extends AbstractButton implements ButtonInterface { + + /** + * Internal var that determines whether the split button has received any primary + * actions yet + * + * @var bool + */ + protected $containsPrimaryAction = FALSE; + + /** + * Internal array of items in the split button + * + * @var array + */ + protected $items = []; + + /** + * Adds an instance of any button to the split button + * + * @param AbstractButton $item ButtonObject to add + * @param bool $primaryAction Is the button the primary action? + * + * @throws \InvalidArgumentException In case a button is not valid + * + * @return $this + */ + public function addItem(AbstractButton $item, $primaryAction = FALSE) { + if (!$item->isValid($item)) { + throw new \InvalidArgumentException( + 'Only valid items may be assigned to a split Button. "' . + $item->getType() . + '" did not pass validation', 1441706330 + ); + } + if ($primaryAction && $this->containsPrimaryAction) { + throw new \InvalidArgumentException('A splitButton may only contain one primary action', 1441706340); + } + if ($primaryAction) { + $this->containsPrimaryAction = TRUE; + $this->items['primary'] = clone $item; + } else { + $this->items['options'][] = clone $item; + } + return $this; + } + + /** + * Returns the current button + * + * @return array + */ + public function getButton() { + if (!isset($this->items['primary']) && isset($this->items['options'])) { + $primaryAction = array_shift($this->items['options']); + $this->items['primary'] = $primaryAction; + } + return $this->items; + } + + /** + * Validates the current button + * + * + * @return bool + */ + public function isValid() { + $subject = $this->getButton(); + if ( + isset($subject['primary']) + && ($subject['primary'] instanceof AbstractButton) + && isset($subject['options']) + ) { + return TRUE; + } + return FALSE; + } + + /** + * Renders the HTML markup of the button + * + * @return string + */ + public function render() { + $items = $this->getButton(); + $content = ' + <div class="btn-group"> + <button + type="submit" + value="' . htmlspecialchars($items['primary']->getValue()) . '" + class="btn btn-sm btn-default ' . htmlspecialchars($items['primary']->getClasses()) . '" + > + ' . $items['primary']->getIcon()->render() . ' + ' . htmlspecialchars($items['primary']->getTitle()) . ' + </button> + <button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> + <span class="caret"></span> + <span class="sr-only">Toggle Dropdown</span> + </button> + <ul class="dropdown-menu">'; + foreach ($items['options'] as $option) { + $content .= ' + <li> + <a href="#">' . $option->getIcon()->render() . ' ' . htmlspecialchars($option->getTitle()) . '</a> + </li> + '; + } + $content .= ' + </ul> + </div> + '; + return $content; + } + + /** + * Magic method so Fluid can access a button via {button} + * + * @return string + */ + public function __toString() { + return $this->render(); + } +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/DocHeaderComponent.php b/typo3/sysext/backend/Classes/Template/Components/DocHeaderComponent.php new file mode 100644 index 0000000000000000000000000000000000000000..bf0e5eeb292bad4ab9bf7506c2d2502f88bc229f --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/DocHeaderComponent.php @@ -0,0 +1,96 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * DocHeader component class + */ +class DocHeaderComponent { + + /** + * MenuRegistry Object + * + * @var MenuRegistry + */ + protected $menuRegistry; + + /** + * Meta information + * + * @var MetaInformation + */ + protected $metaInformation; + + /** + * Registry Container for Buttons + * + * @var ButtonBar + */ + protected $buttonBar; + + /** + * Sets up buttonBar and MenuRegistry + */ + public function __construct() { + $this->buttonBar = GeneralUtility::makeInstance(ButtonBar::class); + $this->menuRegistry = GeneralUtility::makeInstance(MenuRegistry::class); + $this->metaInformation = GeneralUtility::makeInstance(MetaInformation::class); + } + + /** + * Set page information + * + * @param array $metaInformation Record array + * + * @return void + */ + public function setMetaInformation(array $metaInformation) { + $this->metaInformation->setRecordArray($metaInformation); + } + + /** + * Get moduleMenuRegistry + * + * @return MenuRegistry + */ + public function getMenuRegistry() { + return $this->menuRegistry; + } + + /** + * Get ButtonBar + * + * @return ButtonBar + */ + public function getButtonBar() { + return $this->buttonBar; + } + + /** + * Returns the abstract content of the docHeader as an array + * + * @return array + */ + public function docHeaderContent() { + return [ + 'buttons' => $this->buttonBar->getButtons(), + 'menus' => $this->menuRegistry->getMenus(), + 'metaInformation' => $this->metaInformation + ]; + } + +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/Menu/Menu.php b/typo3/sysext/backend/Classes/Template/Components/Menu/Menu.php new file mode 100644 index 0000000000000000000000000000000000000000..7dd6bbcc1f07a24244d04625e363678c6840bb28 --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/Menu/Menu.php @@ -0,0 +1,135 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components\Menu; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Menu + */ +class Menu { + + /** + * Menu Identifier + * + * @var string + */ + protected $identifier = ''; + + /** + * Label of the Menu (useful for Selectbox menus) + * + * @var string + */ + protected $label = ''; + + /** + * Container for menuitems + * + * @var array + */ + protected $menuItems = []; + + /** + * Get the label + * + * @return string + */ + public function getLabel() { + return $this->label; + } + + /** + * Set label + * + * @param string $label LabelText for the menu (accepts LLL syntax) + * + * @return Menu + */ + public function setLabel($label) { + $this->label = $label; + return $this; + } + + /** + * Set identifier + * + * @param string $identifier Menu Identifier + * + * @return Menu + */ + public function setIdentifier($identifier) { + $this->identifier = $identifier; + return $this; + } + + /** + * Adds a new menuItem + * + * @param MenuItem $menuItem The menuItem to add to the menu + * + * @throws \InvalidArgumentException In case a menuItem is not valid + * + * @return void + */ + public function addMenuItem(MenuItem $menuItem) { + if (!$menuItem->isValid($menuItem)) { + throw new \InvalidArgumentException('MenuItem "' . $menuItem->getTitle() . '" is not valid', 1442236317); + } + // @todo implement sorting of menu items + // @todo maybe even things like spacers/sections? + $this->menuItems[] = clone $menuItem; + } + + /** + * Get menu items + * + * @return array + */ + public function getMenuItems() { + return $this->menuItems; + } + + /** + * Get identifier + * + * @return string + */ + public function getIdentifier() { + return $this->identifier; + } + + /** + * MenuItem Factory Method + * + * @return MenuItem + */ + public function makeMenuItem() { + $menuItem = GeneralUtility::makeInstance(MenuItem::class); + return $menuItem; + } + + /** + * Validation function + * + * @param Menu $menu The menu to validate + * + * @return bool + */ + public function isValid(Menu $menu) { + return (trim($menu->getIdentifier()) !== ''); + } + +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/Menu/MenuItem.php b/typo3/sysext/backend/Classes/Template/Components/Menu/MenuItem.php new file mode 100644 index 0000000000000000000000000000000000000000..6960ef22d6d4c3afea84b3b88470bad8c63d9df8 --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/Menu/MenuItem.php @@ -0,0 +1,97 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components\Menu; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\AbstractControl; + +/** + * MenuItem + */ +class MenuItem extends AbstractControl { + + /** + * Sets the href of the menuItem + * + * @var string + */ + protected $href = ''; + + /** + * Sets the active state of the menuItem + * + * @var bool + */ + protected $active = FALSE; + + /** + * Set href + * + * @param string $href Href of the MenuItem + * + * @return MenuItem + */ + public function setHref($href) { + $this->href = $href; + return $this; + } + + /** + * Set active + * + * @param bool $active Defines whether a menuItem is active + * + * @return MenuItem + */ + public function setActive($active) { + $this->active = $active; + return $this; + } + + /** + * Get href + * + * @return string + */ + public function getHref() { + return $this->href; + } + + /** + * Check if is active + * + * @return bool + */ + public function isActive() { + return $this->active; + } + + /** + * Validation + * + * @param MenuItem $menuItem The menuItem to validate + * + * @return bool + */ + public function isValid(MenuItem $menuItem) { + if ( + $menuItem->getHref() !== '' + && $menuItem->getTitle() !== '' + ) { + return TRUE; + } + return FALSE; + } + +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/MenuRegistry.php b/typo3/sysext/backend/Classes/Template/Components/MenuRegistry.php new file mode 100644 index 0000000000000000000000000000000000000000..ded6f9eff618c172d15ef1332e79eb9f46b731fe --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/MenuRegistry.php @@ -0,0 +1,79 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\Menu\Menu; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * MenuRegistry + */ +class MenuRegistry { + + /** + * Internal array that stores all registered menus + * + * @var array + */ + protected $menus = []; + + /** + * Adds a menu to the registry + * + * @param Menu $menu Menu object to add to the menuRegistry + * + * @throws \InvalidArgumentException In case a menu is not valid + * + * @return void + */ + public function addMenu(Menu $menu) { + if (!$menu->isValid($menu)) { + throw new \InvalidArgumentException('Menu "' . $menu->getIdentifier() . '" is not valid', 1442236362); + } + $this->menus[$menu->getIdentifier()] = clone $menu; + } + + /** + * Returns all menus in an abstract array + * + * @return array + */ + public function getMenus() { + // @todo do we want to provide a hook here? + /** + * For Code Completion + * + * @var int $key + * @var Menu $menu + */ + foreach ($this->menus as $key => $menu) { + if (empty($menu->getMenuItems())) { + unset($this->menus[$key]); + } + } + return $this->menus; + } + + /** + * MenuFactory method + * + * @return Menu + */ + public function makeMenu() { + $menu = GeneralUtility::makeInstance(Menu::class); + return $menu; + } + +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/Components/MetaInformation.php b/typo3/sysext/backend/Classes/Template/Components/MetaInformation.php new file mode 100644 index 0000000000000000000000000000000000000000..ae349eee2cc228e0578d11531587306c5ca8597a --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/Components/MetaInformation.php @@ -0,0 +1,133 @@ +<?php +namespace TYPO3\CMS\Backend\Template\Components; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\ModuleTemplate; +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Backend\Utility\IconUtility; +use TYPO3\CMS\Core\Imaging\Icon; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Lang\LanguageService; +use TYPO3\CMS\Core\Imaging\IconFactory; + +/** + * MetaInformation + */ +class MetaInformation { + + /** + * The recordArray. + * Typically this is a page record + * + * @var array + */ + protected $recordArray = []; + + /** + * Set the RecordArray + * + * @param array $recordArray RecordArray + * + * @return void + */ + public function setRecordArray(array $recordArray) { + $this->recordArray = $recordArray; + } + + /** + * Generate the page path for docHeader + * + * @return string The page path + */ + public function getPath() { + $pageRecord = $this->recordArray; + // Is this a real page + if (is_array($pageRecord) && $pageRecord['uid']) { + $title = substr($pageRecord['_thePathFull'], 0, -1); + // Remove current page title + $pos = strrpos($title, $pageRecord['title']); + if ($pos !== FALSE) { + $title = substr($title, 0, $pos); + } + } else { + $title = ''; + } + // Setting the path of the page + // crop the title to title limit (or 50, if not defined) + $beUser = $this->getBackendUser(); + $cropLength = empty($beUser->uc['titleLen']) ? 50 : $beUser->uc['titleLen']; + $croppedTitle = GeneralUtility::fixed_lgd_cs($title, - $cropLength); + if ($croppedTitle !== $title) { + $pagePath = '<abbr title="' . htmlspecialchars($title) . '">' . htmlspecialchars($croppedTitle) . '</abbr>'; + } else { + $pagePath = htmlspecialchars($title); + } + return $pagePath; + } + + /** + * Setting page icon with clickMenu + uid for docheader + * + * @return string Page info + */ + public function getRecordInformation() { + $iconFactory = GeneralUtility::makeInstance(IconFactory::class); + $pageRecord = $this->recordArray; + // Add icon with clickMenu, etc: + // If there IS a real page + if (is_array($pageRecord) && $pageRecord['uid']) { + $altText = BackendUtility::getRecordIconAltText($pageRecord, 'pages'); + $iconImg = IconUtility::getSpriteIconForRecord('pages', $pageRecord, array('title' => $altText)); + // Make Icon: + $theIcon = ModuleTemplate::wrapClickMenuOnIcon($iconImg, 'pages', $pageRecord['uid']); + $uid = $pageRecord['uid']; + $title = BackendUtility::getRecordTitle('pages', $pageRecord); + } else { + // On root-level of page tree + // Make Icon + $iconImg = '<span title="' . + htmlspecialchars($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']) . + '">' . + $iconFactory->getIcon('apps-pagetree-root', Icon::SIZE_SMALL)->render() . '</span>'; + if ($this->getBackendUser()->isAdmin()) { + $theIcon = ModuleTemplate::wrapClickMenuOnIcon($iconImg, 'pages', 0); + } else { + $theIcon = $iconImg; + } + $uid = '0'; + $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']; + } + // Setting icon with clickMenu + uid + return $theIcon . '<strong>' . htmlspecialchars($title) . ' [' . $uid . ']</strong>'; + } + + /** + * Get LanguageService Object + * + * @return LanguageService + */ + protected function getLanguageService() { + return $GLOBALS['LANG']; + } + + /** + * Get the Backend User Object + * + * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication + */ + protected function getBackendUser() { + return $GLOBALS['BE_USER']; + } +} \ No newline at end of file diff --git a/typo3/sysext/backend/Classes/Template/ModuleTemplate.php b/typo3/sysext/backend/Classes/Template/ModuleTemplate.php new file mode 100644 index 0000000000000000000000000000000000000000..f1157e5948d4e5db544d94ea928bc84b8a5b5043 --- /dev/null +++ b/typo3/sysext/backend/Classes/Template/ModuleTemplate.php @@ -0,0 +1,771 @@ +<?php +namespace TYPO3\CMS\Backend\Template; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\DocHeaderComponent; +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Imaging\Icon; +use TYPO3\CMS\Core\Imaging\IconFactory; +use TYPO3\CMS\Core\Page\PageRenderer; +use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Fluid\View\Exception\InvalidTemplateResourceException; +use TYPO3\CMS\Fluid\View\StandaloneView; +use TYPO3\CMS\Lang\LanguageService; +use TYPO3\CMS\Version\View\VersionView; + +/** + * A class taking care of the "outer" HTML of a module, especially + * the doc header and other related parts. + * + * @internal This API is not yet carved in stone and may be adapted later. + */ +class ModuleTemplate { + + /** + * Error Icon Constant + * + * @internal + */ + const STATUS_ICON_ERROR = 3; + + /** + * Warning Icon Constant + * + * @internal + */ + const STATUS_ICON_WARNING = 2; + + /** + * Notification Icon Constant + * + * @internal + */ + const STATUS_ICON_NOTIFICATION = 1; + + /** + * OK Icon Constant + * + * @internal + */ + const STATUS_ICON_OK = -1; + + /** + * DocHeaderComponent + * + * @var DocHeaderComponent + */ + protected $docHeaderComponent; + + /** + * Javascript Code Array + * Used for inline JS + * + * @var array + */ + protected $javascriptCodeArray = []; + + /** + * Expose the pageRenderer + * + * @var PageRenderer + */ + protected $pageRenderer; + + /** + * TemplateRootPath + * + * @var string + */ + protected $templateRootPath = 'EXT:backend/Resources/Private/Templates'; + + /** + * PartialRootPath + * + * @var string + */ + protected $partialRootPath = 'EXT:backend/Resources/Private/Partials'; + + /** + * LayoutRootPath + * + * @var string + */ + protected $layoutRootPath = 'EXT:backend/Resources/Private/Layouts'; + + /** + * Template name + * + * @var string + */ + protected $templateFile = 'Module.html'; + + /** + * Fluid Standalone View + * + * @var StandaloneView + */ + protected $view; + + /** + * Content String + * + * @var string + */ + protected $content = ''; + + /** + * Defines whether a section has been opened before + * + * @var int + */ + protected $sectionFlag = 0; + + /** + * IconFactory Member + * + * @var IconFactory + */ + protected $iconFactory; + + /** + * Module ID + * + * @var string + */ + protected $moduleId = ''; + + /** + * Module Name + * + * @var string + */ + protected $moduleName = ''; + + /** + * Get template root path + * + * @return string + */ + public function getTemplateRootPath() { + return $this->templateRootPath; + } + + /** + * Set content + * + * @param string $content Content of the module + * + * @return void + */ + public function setContent($content) { + $this->view->assign('content', $content); + } + + /** + * Class constructor + * Sets up view and property objects + * + * @throws InvalidTemplateResourceException In case a template is invalid + */ + public function __construct() { + $this->view = GeneralUtility::makeInstance(StandaloneView::class); + $this->view->setPartialRootPaths([$this->partialRootPath]); + $this->view->setTemplateRootPaths([$this->templateRootPath]); + $this->view->setLayoutRootPaths([$this->layoutRootPath]); + $this->view->setTemplate($this->templateFile); + $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); + $this->docHeaderComponent = GeneralUtility::makeInstance(DocHeaderComponent::class); + $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class); + } + + /** + * Loads all necessary Javascript Files + * + * @return void + */ + protected function loadJavaScripts() { + $this->pageRenderer->loadJquery(); + $this->pageRenderer->loadRequireJsModule('bootstrap'); + $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextHelp'); + } + + /** + * Loads all necessary stylesheets + * + * @return void + */ + protected function loadStylesheets() { + if ($GLOBALS['TBE_STYLES']['stylesheet']) { + $this->pageRenderer->addCssFile($GLOBALS['TBE_STYLES']['stylesheet']); + } + if ($GLOBALS['TBE_STYLES']['stylesheet2']) { + $this->pageRenderer->addCssFile($GLOBALS['TBE_STYLES']['stylesheet2']); + } + } + + /** + * Sets mandatory parameters for the view (pageRenderer) + * + * @return void + */ + protected function setupPage() { + // Yes, hardcoded on purpose + $this->pageRenderer->setCharSet('utf-8'); + $this->pageRenderer->setLanguage('default'); + } + + /** + * Wrapper function for adding JS inline blocks + * + * @return void + */ + protected function setJavaScriptCodeArray() { + foreach ($this->javascriptCodeArray as $name => $code) { + $this->pageRenderer->addJsInlineCode($name, $code, FALSE); + } + } + + /** + * Adds JS inline blocks of code to the internal registry + * + * @param string $name Javascript code block name + * @param string $code Inline Javascript + * + * @return void + */ + public function addJavaScriptCode($name = '', $code = '') { + $this->javascriptCodeArray[$name] = $code; + } + + /** + * Get the DocHeader + * + * @return DocHeaderComponent + */ + public function getDocHeaderComponent() { + return $this->docHeaderComponent; + } + + /** + * Returns the fully rendered view + * + * @return string + */ + public function renderContent() { + $this->setupPage(); + $this->loadJavaScripts(); + $this->setJavaScriptCodeArray(); + $this->loadStylesheets(); + + $this->view->assign('docHeader', $this->docHeaderComponent->docHeaderContent()); + if ($this->moduleId) { + $this->view->assign('moduleId', $this->moduleId); + } + if ($this->moduleName) { + $this->view->assign('moduleName', $this->moduleName); + } + + $renderedPage = $this->pageRenderer->render(PageRenderer::PART_HEADER); + $renderedPage .= $this->view->render(); + $renderedPage .= $this->pageRenderer->render(PageRenderer::PART_FOOTER); + + return $renderedPage; + } + + /** + * Get PageRenderer + * + * @return PageRenderer + */ + public function getPageRenderer() { + return $this->pageRenderer; + } + + /** + * Set form tag + * + * @param string $formTag Form tag to add + * + * @return void + */ + public function setForm($formTag = '') { + $this->view->assign('formTag', $formTag); + } + + /** + * Sets the ModuleId + * + * @param string $moduleId ID of the module + * + * @return void + */ + public function setModuleId($moduleId) { + $this->moduleId = $moduleId; + $this->registerModuleMenu($moduleId); + } + + /** + * Sets the ModuleName + * + * @param string $moduleName Name of the module + * + * @return void + */ + public function setModuleName($moduleName) { + $this->moduleName = $moduleName; + } + + /** + * Generates the Menu for things like Web->Info + * + * @param $moduleMenuIdentifier + */ + public function registerModuleMenu($moduleMenuIdentifier) { + if (isset($GLOBALS['TBE_MODULES_EXT'][$moduleMenuIdentifier])) { + $menuEntries = + $GLOBALS['TBE_MODULES_EXT'][$moduleMenuIdentifier]['MOD_MENU']['function']; + $menu = $this->getDocHeaderComponent()->getMenuRegistry()->makeMenu()->setIdentifier('MOD_FUNC'); + foreach ($menuEntries as $menuEntry) { + $menuItem = $menu->makeMenuItem() + ->setTitle($menuEntry['title']) + ->setHref('#'); + $menu->addMenuItem($menuItem); + } + $this->docHeaderComponent->getMenuRegistry()->addMenu($menu); + } + } + + + + /******************************************* + * THE FOLLOWING METHODS ARE SUBJECT TO BE DEPRECATED / DROPPED! + * + * These methods have been copied over from DocumentTemplate and enables + * core modules to drop the dependency to DocumentTemplate altogether without + * rewriting these modules now. + * The methods below are marked as internal and will be removed + * one-by-one with further refactoring of modules. + * + * Do not use these methods within own extensions if possible or + * be prepared to change this later again. + *******************************************/ + + /** + * Makes click menu link (context sensitive menu) + * Returns $str (possibly an <|img> tag/icon) wrapped in a link which will + * activate the context sensitive menu for the record ($table/$uid) or + * file ($table = file) + * The link will load the top frame with the parameter "&item" which is + * the table,uid and listFr arguments imploded + * by "|": rawurlencode($table.'|'.$uid.'|'.$listFr) + * + * @param string $content String to be wrapped in link, typ. image tag. + * @param string $table Table name/File path. If the icon is for a database + * record, enter the tablename from $GLOBALS['TCA']. If a file then enter + * the absolute filepath + * @param int $uid If icon is for database record this is the UID for the + * record from $table + * @param bool $listFr Tells the top frame script that the link is coming + * from a "list" frame which means a frame from within the backend content frame. + * @param string $addParams Additional GET parameters for the link to the + * ClickMenu AJAX request + * @param string $enDisItems Enable / Disable click menu items. + * Example: "+new,view" will display ONLY these two items (and any spacers + * in between), "new,view" will display all BUT these two items. + * @param bool $returnTagParameters If set, will return only the onclick + * JavaScript, not the whole link. + * + * @return string The link-wrapped input string. + * @internal + */ + public function wrapClickMenuOnIcon( + $content, + $table, + $uid = 0, + $listFr = TRUE, + $addParams = '', + $enDisItems = '', + $returnTagParameters = FALSE + ) { + $tagParameters = array( + 'class' => 't3-js-clickmenutrigger', + 'data-table' => $table, + 'data-uid' => (int)$uid !== 0 ? (int)$uid : '', + 'data-listframe' => $listFr, + 'data-iteminfo' => str_replace('+', '%2B', $enDisItems), + 'data-parameters' => $addParams, + ); + + if ($returnTagParameters) { + return $tagParameters; + } + return '<a href="#" ' . GeneralUtility::implodeAttributes($tagParameters, TRUE) . '>' . $content . '</a>'; + } + + /** + * Includes a javascript library that exists in the core /typo3/ directory + * + * @param string $lib Library name. Call it with the full path like + * "sysext/core/Resources/Public/JavaScript/QueryGenerator.js" to load it + * + * @return void + * @internal + */ + public function loadJavascriptLib($lib) { + // @todo: maybe we can remove this one as well + $this->pageRenderer->addJsFile($lib); + } + + /** + * Returns a linked shortcut-icon which will call the shortcut frame and set a + * shortcut there back to the calling page/module + * + * @param string $gvList Is the list of GET variables to store (if any) + * @param string $setList Is the list of SET[] variables to store + * (if any) - SET[] variables a stored in $GLOBALS["SOBE"]->MOD_SETTINGS + * for backend modules + * @param string $modName Module name string + * @param string|int $motherModName Is used to enter the "parent module + * name" if the module is a submodule under eg. Web>* or File>*. You + * can also set this value to 1 in which case the currentLoadedModule + * is sent to the shortcut script (so - not a fixed value!) - that is used + * in file_edit and wizard_rte modules where those are really running as + * a part of another module. + * + * @return string HTML content + * @todo Make this thing return a button object + * @internal + */ + public function makeShortcutIcon($gvList, $setList, $modName, $motherModName = '') { + $storeUrl = $this->makeShortcutUrl($gvList, $setList); + $pathInfo = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI')); + // Fallback for alt_mod. We still pass in the old xMOD... stuff, + // but TBE_MODULES only knows about "record_edit". + // We still need to pass the xMOD name to createShortcut below, + // since this is used for icons. + $moduleName = $modName === 'xMOD_alt_doc.php' ? 'record_edit' : $modName; + // Add the module identifier automatically if typo3/index.php is used: + if (GeneralUtility::_GET('M') !== NULL && isset($GLOBALS['TBE_MODULES']['_PATHS'][$moduleName])) { + $storeUrl = '&M=' . $moduleName . $storeUrl; + } + if ((int)$motherModName === 1) { + $motherModule = 'top.currentModuleLoaded'; + } elseif ($motherModName) { + $motherModule = GeneralUtility::quoteJSvalue($motherModName); + } else { + $motherModule = '\'\''; + } + $confirmationText = GeneralUtility::quoteJSvalue( + $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.makeBookmark') + ); + + $shortcutUrl = $pathInfo['path'] . '?' . $storeUrl; + $shortcutExist = BackendUtility::shortcutExists($shortcutUrl); + + if ($shortcutExist) { + return '<a class="active" title="">' . + $this->iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render() . '</a>'; + } + + $url = GeneralUtility::quoteJSvalue(rawurlencode($shortcutUrl)); + $onClick = 'top.TYPO3.ShortcutMenu.createShortcut(' . GeneralUtility::quoteJSvalue(rawurlencode($modName)) . + ', ' . $url . ', ' . $confirmationText . ', ' . $motherModule . ', this);return false;'; + + return '<a href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . + $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.makeBookmark', TRUE) . '">' . + $this->iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render() . '</a>'; + } + + /** + * MAKE url for storing + * Internal func + * + * @param string $gvList Is the list of GET variables to store (if any) + * @param string $setList Is the list of SET[] variables to store (if any) + * - SET[] variables a stored in $GLOBALS["SOBE"]->MOD_SETTINGS for backend + * modules + * + * @return string + * @internal + */ + public function makeShortcutUrl($gvList, $setList) { + $getParams = GeneralUtility::_GET(); + $storeArray = array_merge( + GeneralUtility::compileSelectedGetVarsFromArray($gvList, $getParams), + array('SET' => GeneralUtility::compileSelectedGetVarsFromArray($setList, (array)$GLOBALS['SOBE']->MOD_SETTINGS)) + ); + return GeneralUtility::implodeArrayForUrl('', $storeArray); + } + + /** + * Returns a URL with a command to TYPO3 Core Engine (tce_db.php) + * See description of the API elsewhere. + * + * @param string $params Is a set of GET params to send to tce_db.php. + * Example: "&cmd[tt_content][123][move]=456" or + * "&data[tt_content][123][hidden]=1&data[tt_content][123][title]=Hello%20World + * @param string|int $redirectUrl Redirect URL, default is to use + * GeneralUtility::getIndpEnv('REQUEST_URI'), -1 means to generate + * an URL for JavaScript using T3_THIS_LOCATION + * + * @return string URL to BackendUtility::getModuleUrl('tce_db') + parameters + * @internal + */ + public function issueCommand($params, $redirectUrl = '') { + $urlParameters = [ + 'prErr' => 1, + 'uPT' => 1, + 'vC' => $this->getBackendUserAuthentication()->veriCode() + ]; + $url = BackendUtility::getModuleUrl('tce_db', $urlParameters) . $params . '&redirect='; + if ((int)$redirectUrl === -1) { + $url = GeneralUtility::quoteJSvalue($url) . '+T3_THIS_LOCATION'; + } else { + $url .= rawurlencode($redirectUrl ?: GeneralUtility::getIndpEnv('REQUEST_URI')); + } + return $url; + } + + /** + * Creates the version selector for the page id inputted. + * Requires the core version management extension, "version" to be loaded. + * + * @param int $id Page id to create selector for. + * @param bool $noAction If set, there will be no button for swapping page. + * + * @return string + * @internal + */ + public function getVersionSelector($id, $noAction = FALSE) { + if ( + ExtensionManagementUtility::isLoaded('version') && + !ExtensionManagementUtility::isLoaded('workspaces') + ) { + /** + * For Code Completion + * + * @var $versionGuiObj VersionView + */ + $versionGuiObj = GeneralUtility::makeInstance(VersionView::class); + return $versionGuiObj->getVersionSelector($id, $noAction); + } + return ''; + } + + /** + * Begins an output section and sets header and content + * + * @param string $label The header + * @param string $text The HTML-content + * @param bool $noStrToUpper A flag that will prevent the header from + * being converted to uppercase + * @param bool $sH Defines the type of header (if set, "<h3>" rather + * than the default "h4") + * @param int $type The number of an icon to show with the header + * (see the icon-function). -1,1,2,3 + * @param bool $allowHtmlInHeader If set, HTML tags are allowed in + * $label (otherwise this value is by default htmlspecialchars()'ed) + * + * @return string HTML content + * @internal + */ + public function section($label, $text, $noStrToUpper = FALSE, $sH = FALSE, $type = 0, $allowHtmlInHeader = FALSE) { + $str = ''; + // Setting header + if ($label) { + if (!$allowHtmlInHeader) { + $label = htmlspecialchars($label); + } + $str .= $this->sectionHeader($this->icons($type) . $label, $sH, $noStrToUpper ? '' : ' class="uppercase"'); + } + // Setting content + $str .= ' + + <!-- Section content --> +' . $text; + return $this->sectionBegin() . $str; + } + + /** + * Inserts a divider image + * Ends a section (if open) before inserting the image + * + * @param int $dist The margin-top/-bottom of the <hr> ruler. + * + * @return string HTML content + * @internal + */ + public function divider($dist) { + $dist = (int)$dist; + $str = ' + + <!-- DIVIDER --> + <hr style="margin-top: ' . $dist . 'px; margin-bottom: ' . $dist . 'px;" /> +'; + return $this->sectionEnd() . $str; + } + + /** + * Returns a blank <div>-section with a height + * + * @param int $dist Padding-top for the div-section + * + * @return string HTML content + * @internal + */ + public function spacer($dist) { + if ($dist > 0) { + return ' + + <!-- Spacer element --> + <div style="padding-top: ' . (int)$dist . 'px;"></div> +'; + } + return ''; + } + + /** + * Make a section header. + * Begins a section if not already open. + * + * @param string $label The label between the <h3> or <h4> tags. (Allows HTML) + * @param bool $sH If set, <h3> is used, otherwise <h4> + * @param string $addAttribute Additional attributes to h-tag, eg. ' class=""' + * + * @return string HTML content + * @internal + */ + public function sectionHeader($label, $sH = FALSE, $addAttribute = '') { + $tag = $sH ? 'h2' : 'h3'; + if ($addAttribute && $addAttribute[0] !== ' ') { + $addAttribute = ' ' . $addAttribute; + } + $str = ' + + <!-- Section header --> + <' . $tag . $addAttribute . '>' . $label . '</' . $tag . '> +'; + return $this->sectionBegin() . $str; + } + + /** + * Begins an output section. + * Returns the <div>-begin tag AND sets the ->sectionFlag TRUE + * (if the ->sectionFlag is not already set!) + * You can call this function even if a section is already begun + * since the function will only return something if the sectionFlag + * is not already set! + * + * @return string HTML content + * @internal + */ + public function sectionBegin() { + if (!$this->sectionFlag) { + $this->sectionFlag = 1; + $str = ' + + <!-- *********************** + Begin output section. + *********************** --> + <div> +'; + return $str; + } + return ''; + } + + /** + * Ends and output section + * Returns the </div>-end tag AND clears the ->sectionFlag + * (but does so only IF the sectionFlag is set - that is a section is 'open') + * See sectionBegin() also. + * + * @return string HTML content + * @internal + */ + public function sectionEnd() { + if ($this->sectionFlag) { + $this->sectionFlag = 0; + return ' + </div> + <!-- ********************* + End output section. + ********************* --> +'; + } + return ''; + } + + + + /** + * Returns the BE USER Object + * + * @return BackendUserAuthentication + */ + protected function getBackendUserAuthentication() { + return $GLOBALS['BE_USER']; + } + + /** + * Returns the LanguageService + * + * @return LanguageService + */ + protected function getLanguageService() { + return $GLOBALS['LANG']; + } + + /** + * Returns an image-tag with an 18x16 icon of the following types: + * + * $type: + * -1:» OK icon (Check-mark) + * 1:» Notice (Speach-bubble) + * 2:» Warning (Yellow triangle) + * 3:» Fatal error (Red stop sign) + * + * @param int $type See description + * + * @return string HTML image tag (if applicable) + * @internal + */ + public function icons($type) { + $icon = ''; + switch ($type) { + case self::STATUS_ICON_ERROR: + $icon = 'status-dialog-error'; + break; + case self::STATUS_ICON_WARNING: + $icon = 'status-dialog-warning'; + break; + case self::STATUS_ICON_NOTIFICATION: + $icon = 'status-dialog-notification'; + break; + case self::STATUS_ICON_OK: + $icon = 'status-dialog-ok'; + break; + default: + // Do nothing + } + if ($icon != '') { + return $this->iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render(); + } + return ''; + } + +} \ No newline at end of file diff --git a/typo3/sysext/backend/Resources/Private/Partials/ButtonBar.html b/typo3/sysext/backend/Resources/Private/Partials/ButtonBar.html new file mode 100644 index 0000000000000000000000000000000000000000..f1b5b7195421abf07b277a718f089ded126ca9fc --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/Partials/ButtonBar.html @@ -0,0 +1,9 @@ +<div class="btn-toolbar" role="toolbar" aria-label=""> + <f:for each="{buttons}" as="buttonGroup"> + <div class="btn-group" role="group" aria-label=""> + <f:for each="{buttonGroup}" as="button"> + {button} + </f:for> + </div> + </f:for> +</div> \ No newline at end of file diff --git a/typo3/sysext/backend/Resources/Private/Partials/DocHeader.html b/typo3/sysext/backend/Resources/Private/Partials/DocHeader.html new file mode 100644 index 0000000000000000000000000000000000000000..b6dd7d015e75cce8a02c7963209f70b1396f1da6 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/Partials/DocHeader.html @@ -0,0 +1,25 @@ +<div class="module-docheader"> + <div class="module-docheader-bar"> + <div class="module-docheader-bar-column-left"> + <div class="form-inline"> + <f:for each="{docHeader.menus}" as="menu"> + <div class="form-group form-group-sm"> + <f:render partial="Menus/SelectBoxJumpMenu" arguments="{menu:menu}" /> + </div> + </f:for> + </div> + </div> + <div class="module-docheader-bar-column-right"> + <span class="typo3-docheader-pagePath"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.path" />: <f:format.raw>{docHeader.metaInformation.path}</f:format.raw></span> <f:format.raw>{docHeader.metaInformation.recordInformation}</f:format.raw> + </div> + + </div> + <div class="module-docheader-bar"> + <div class="module-docheader-bar-column-left"> + <f:render partial="ButtonBar" arguments="{buttons:docHeader.buttons.left}" /> + </div> + <div class="module-docheader-bar-column-right"> + <f:render partial="ButtonBar" arguments="{buttons:docHeader.buttons.right}" /> + </div> + </div> +</div> \ No newline at end of file diff --git a/typo3/sysext/backend/Resources/Private/Partials/Menus/SelectBoxJumpMenu.html b/typo3/sysext/backend/Resources/Private/Partials/Menus/SelectBoxJumpMenu.html new file mode 100644 index 0000000000000000000000000000000000000000..c6c7e7d673e32a1f2051414c5582ffbb0a5087d3 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/Partials/Menus/SelectBoxJumpMenu.html @@ -0,0 +1,5 @@ +{menu.label} <select class="form-control t3-js-jumpMenuBox" name="{menu.identifier}" onchange="if(this.options[this.selectedIndex].value){window.location.href=(this.options[this.selectedIndex].value);}"> + <f:for each="{menu.menuItems}" as="menuItem"> + <option value="{menuItem.href}" {f:if(condition: '{menuItem.active}', then: ' selected="selected"')}>{menuItem.title}</option> + </f:for> +</select> \ No newline at end of file diff --git a/typo3/sysext/backend/Resources/Private/Templates/Module.html b/typo3/sysext/backend/Resources/Private/Templates/Module.html new file mode 100644 index 0000000000000000000000000000000000000000..bb3fe1cc166d69cca3ecc203f80a3ea5eca72439 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/Templates/Module.html @@ -0,0 +1,16 @@ +<div class="module" data-module-id="{moduleId}" data-module-name="{moduleName}"> + <f:if condition="{formTag}"> + <f:then> + <f:format.raw>{formTag}</f:format.raw> + </f:then> + </f:if> + <f:render partial="DocHeader" arguments="{docHeader:docHeader}" /> + <div class="module-body"> + <f:format.raw>{content}</f:format.raw> + </div> + <f:if condition="{formTag}"> + <f:then> + </form> + </f:then> + </f:if> +</div> \ No newline at end of file diff --git a/typo3/sysext/backend/Resources/Private/Templates/blank.html b/typo3/sysext/backend/Resources/Private/Templates/blank.html new file mode 100644 index 0000000000000000000000000000000000000000..b1ae1ad5620cf347c64cdd62dc57883dfd8d3a06 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/Templates/blank.html @@ -0,0 +1,3 @@ +<!-- ###FULLDOC### begin --> +###CONTENT### +<!-- ###FULLDOC### end --> \ No newline at end of file diff --git a/typo3/sysext/backend/Tests/Unit/Template/Components/Button/FullyRenderedButtonTest.php b/typo3/sysext/backend/Tests/Unit/Template/Components/Button/FullyRenderedButtonTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1309cab948c0df57371695d20c8399c5b0f7fb21 --- /dev/null +++ b/typo3/sysext/backend/Tests/Unit/Template/Components/Button/FullyRenderedButtonTest.php @@ -0,0 +1,50 @@ +<?php +namespace TYPO3\CMS\Backend\Tests\Template\Components\Buttons; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\Buttons\FullyRenderedButton; +use TYPO3\CMS\Core\Tests\UnitTestCase; + +/** + * Test case for FullyRenderedButton + */ +class FullyRenderedButtonTest extends UnitTestCase { + + /** + * Try to valide an empty button + * + * @test + * @return void + */ + public function isButtonValidBlankCallExpectFalse() { + $button = new FullyRenderedButton(); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Tests a valid HTML Button + * + * @test + * @return void + */ + public function isButtonValidHtmlSourceGivenExpectTrue() { + $button = new FullyRenderedButton(); + $button->setHtmlSource('<span>Husel</span>'); + $isValid = $button->isValid(); + $this->assertTrue($isValid); + } + +} diff --git a/typo3/sysext/backend/Tests/Unit/Template/Components/Button/InputButtonTest.php b/typo3/sysext/backend/Tests/Unit/Template/Components/Button/InputButtonTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5046ee9180f47fc23c3feb11e0e8d93f45bad2d9 --- /dev/null +++ b/typo3/sysext/backend/Tests/Unit/Template/Components/Button/InputButtonTest.php @@ -0,0 +1,106 @@ +<?php +namespace TYPO3\CMS\Backend\Tests\Template\Components\Buttons; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\Buttons\InputButton; +use TYPO3\CMS\Core\Imaging\Icon; +use TYPO3\CMS\Core\Tests\UnitTestCase; + +/** + * Test case for InputButton + */ +class InputButtonTest extends UnitTestCase { + + /** + * Try to validate an empty button + * + * @test + * @return void + */ + public function isButtonValidBlankCallExpectFalse() { + $button = new InputButton(); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Omit the Icon + * + * @test + * @return void + */ + public function isButtonValidOmittedIconExpectFalse() { + $button = new InputButton(); + $button->setName('husel')->setValue('1')->setTitle('huhu'); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Omit the title + * + * @test + * @return void + */ + public function isButtonValidOmittedTitleExpectFalse() { + $button = new InputButton(); + $icon = new Icon(); + $button->setName('husel')->setValue('1')->setIcon($icon); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Omit the name + * + * @test + * @return void + */ + public function isButtonValidOmittedNameExpectFalse() { + $button = new InputButton(); + $icon = new Icon(); + $button->setTitle('husel')->setValue('1')->setIcon($icon); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Omit the Value + * + * @test + * @return void + */ + public function isButtonValidOmittedValueExpectFalse() { + $button = new InputButton(); + $icon = new Icon(); + $button->setTitle('husel')->setName('husel')->setIcon($icon); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Set a 100% valid button + * + * @test + * @return void + */ + public function isButtonValidAllValuesSetExpectTrue() { + $button = new InputButton(); + $icon = new Icon(); + $button->setTitle('husel')->setName('husel')->setIcon($icon)->setValue('1'); + $isValid = $button->isValid(); + $this->assertTrue($isValid); + } +} diff --git a/typo3/sysext/backend/Tests/Unit/Template/Components/Button/LinkButtonTest.php b/typo3/sysext/backend/Tests/Unit/Template/Components/Button/LinkButtonTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bbdefc280aa05ad46b855fdf30fbaf1d4f037edb --- /dev/null +++ b/typo3/sysext/backend/Tests/Unit/Template/Components/Button/LinkButtonTest.php @@ -0,0 +1,92 @@ +<?php +namespace TYPO3\CMS\Backend\Tests\Template\Components\Buttons; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\Buttons\LinkButton; +use TYPO3\CMS\Core\Imaging\Icon; +use TYPO3\CMS\Core\Tests\UnitTestCase; + +/** + * Test case for LinkButton + */ +class LinkButtonTest extends UnitTestCase { + + /** + * Try validating an empty button + * + * @test + * @return void + */ + public function isButtonValidBlankCallExpectFalse() { + $button = new LinkButton(); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Omit the Icon + * + * @test + * @return void + */ + public function isButtonValidOmittedIconExpectFalse() { + $button = new LinkButton(); + $button->setHref('#')->setTitle('huhu'); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Omit the title + * + * @test + * @return void + */ + public function isButtonValidOmittedTitleExpectFalse() { + $button = new LinkButton(); + $icon = new Icon(); + $button->setHref('husel')->setIcon($icon); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Omit Href + * + * @test + * @return void + */ + public function isButtonValidOmittedHrefExpectFalse() { + $button = new LinkButton(); + $icon = new Icon(); + $button->setTitle('husel')->setIcon($icon); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Send a valid button + * + * @test + * @return void + */ + public function isButtonValidAllValuesSetExpectTrue() { + $button = new LinkButton(); + $icon = new Icon(); + $button->setTitle('husel')->setHref('husel')->setIcon($icon); + $isValid = $button->isValid(); + $this->assertTrue($isValid); + } +} diff --git a/typo3/sysext/backend/Tests/Unit/Template/Components/Button/SplitButtonTest.php b/typo3/sysext/backend/Tests/Unit/Template/Components/Button/SplitButtonTest.php new file mode 100644 index 0000000000000000000000000000000000000000..78f19d4a3f11c42d3e3c65ab43b8a04be70bdbc3 --- /dev/null +++ b/typo3/sysext/backend/Tests/Unit/Template/Components/Button/SplitButtonTest.php @@ -0,0 +1,126 @@ +<?php +namespace TYPO3\CMS\Backend\Tests\Template\Components\Buttons; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\Buttons\LinkButton; +use TYPO3\CMS\Backend\Template\Components\Buttons\SplitButton; +use TYPO3\CMS\Core\Imaging\Icon; +use TYPO3\CMS\Core\Tests\UnitTestCase; + +/** + * Class BackendModuleRequestHandlerTest + */ +class SplitButtonTest extends UnitTestCase { + + /** + * Try to validate an empty button + * + * @test + * @return void + */ + public function isButtonValidBlankCallExpectFalse() { + $button = new SplitButton(); + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Try adding an invalid button to a splitButton + * + * @test + * @expectedException \InvalidArgumentException + * @expectedExceptionCode 1441706330 + * @return void + */ + public function isButtonValidInvalidButtonGivenExpectFalse() { + $button = new SplitButton(); + + $primaryAction = new LinkButton(); + $button->addItem($primaryAction); + + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Try to add multiple primary actions + * + * @test + * @expectedException \InvalidArgumentException + * @expectedExceptionCode 1441706340 + * @return void + */ + public function isButtonValidBrokenSetupMultiplePrimaryActionsGivenExpectFalse() { + $button = new SplitButton(); + + $primaryAction = new LinkButton(); + $icon = new Icon(); + $primaryAction->setTitle('husel')->setHref('husel')->setIcon($icon); + $button->addItem($primaryAction, TRUE); + + $anotherPrimaryAction = new LinkButton(); + $anotherPrimaryAction->setTitle('husel')->setHref('husel')->setIcon($icon); + $button->addItem($anotherPrimaryAction, TRUE); + + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Try to add an invalid button as second parameter + * + * @test + * @expectedException \InvalidArgumentException + * @expectedExceptionCode 1441706330 + * @return void + */ + public function isButtonValidBrokenSetupInvalidButtonAsSecondParametersGivenExpectFalse() { + $button = new SplitButton(); + + $primaryAction = new LinkButton(); + $icon = new Icon(); + $primaryAction->setTitle('husel')->setHref('husel')->setIcon($icon); + $button->addItem($primaryAction, TRUE); + + $anotherPrimaryAction = new LinkButton(); + $anotherPrimaryAction->setTitle('husel')->setHref('husel'); + $button->addItem($anotherPrimaryAction, TRUE); + + $isValid = $button->isValid(); + $this->assertFalse($isValid); + } + + /** + * Send in a valid button + * + * @test + * @return void + */ + public function isButtonValidValidSetupExpectTrue() { + $button = new SplitButton(); + + $primaryAction = new LinkButton(); + $icon = new Icon(); + $primaryAction->setTitle('husel')->setHref('husel')->setIcon($icon); + $button->addItem($primaryAction, TRUE); + + $anotherAction = new LinkButton(); + $anotherAction->setTitle('husel')->setHref('husel')->setIcon($icon); + $button->addItem($anotherAction); + + $isValid = $button->isValid(); + $this->assertTrue($isValid); + } +} diff --git a/typo3/sysext/backend/Tests/Unit/Template/Components/Menu/MenuItemTest.php b/typo3/sysext/backend/Tests/Unit/Template/Components/Menu/MenuItemTest.php new file mode 100644 index 0000000000000000000000000000000000000000..180b173fc9994b2ebda4eeb4193b51d7f56587a8 --- /dev/null +++ b/typo3/sysext/backend/Tests/Unit/Template/Components/Menu/MenuItemTest.php @@ -0,0 +1,77 @@ +<?php +namespace TYPO3\CMS\Backend\Tests\Template\Components\Menu; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\Menu\MenuItem; +use TYPO3\CMS\Core\Imaging\Icon; +use TYPO3\CMS\Core\Tests\UnitTestCase; + +/** + * Test case for MenuItem + */ +class MenuItemTest extends UnitTestCase { + + /** + * Try a blank menu Item + * + * @test + * @return void + */ + public function isMenuItemValidBlankCallExpectFalse() { + $menuItem = new MenuItem(); + $isValid = $menuItem->isValid($menuItem); + $this->assertFalse($isValid); + } + + /** + * Try omitting the title and a Href + * + * @test + * @return void + */ + public function isMenuItemValidOmittedHrefAndRouteExpectFalse() { + $menuItem = new MenuItem(); + $menuItem->setTitle('huhu'); + $isValid = $menuItem->isValid($menuItem); + $this->assertFalse($isValid); + } + + /** + * Try omitting the title + * + * @test + * @return void + */ + public function isMenuItemValidOmittedTitleExpectFalse() { + $menuItem = new MenuItem(); + $menuItem->setHref('husel'); + $isValid = $menuItem->isValid($menuItem); + $this->assertFalse($isValid); + } + + /** + * Set a valid title and href + * + * @test + * @return void + */ + public function isMenuItemValidSetValidHrefAndTitleExpectTrue() { + $menuItem = new MenuItem(); + $menuItem->setTitle('husel')->setHref('husel'); + $isValid = $menuItem->isValid($menuItem); + $this->assertTrue($isValid); + } + +} \ No newline at end of file diff --git a/typo3/sysext/backend/Tests/Unit/Template/Components/MenuTest.php b/typo3/sysext/backend/Tests/Unit/Template/Components/MenuTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e4c4f779fab1648d822bdc233c0da693c5eb6384 --- /dev/null +++ b/typo3/sysext/backend/Tests/Unit/Template/Components/MenuTest.php @@ -0,0 +1,94 @@ +<?php +namespace TYPO3\CMS\Backend\Tests\Template\Components; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Backend\Template\Components\Menu\Menu; +use TYPO3\CMS\Backend\Template\Components\MenuRegistry; +use TYPO3\CMS\Core\Tests\UnitTestCase; + +/** + * Test case for Menu + */ +class MenuTest extends UnitTestCase { + + /** + * Try setting an empty menu + * + * @test + * @return void + */ + public function isMenuValidBlankCallExpectFalse() { + $menu = new Menu(); + $isValid = $menu->isValid($menu); + $this->assertFalse($isValid); + } + + /** + * Set a valid menu + * + * @test + * @return void + */ + public function isMenuValidValidMenuWithDefaultsExpectTrue() { + $menu = new Menu(); + $menu->setIdentifier('husel'); + $isValid = $menu->isValid($menu); + $this->assertTrue($isValid); + } + + /** + * Set a valid menu + * + * @test + * @return void + */ + public function makeMenuAllGoodExpectTrue() { + $menuRegistry = new MenuRegistry(); + $result = $menuRegistry->makeMenu()->setLabel('MenuLabel')->setIdentifier('MenuIdent'); + $expected = new Menu(); + $expected->setIdentifier('MenuIdent'); + $expected->setLabel('MenuLabel'); + $this->assertEquals($expected, $result); + } + + /** + * Tests if empty menus get removed from the stack + * + * @test + * @return void + */ + public function getMenusremovedEmptyMenusExpectsEquals() { + $menuRegistry = new MenuRegistry(); + + $menu1 = $menuRegistry->makeMenu(); + $menu1->setIdentifier('husel'); + $menu1->setLabel('Label of an empty Menu'); + $menuRegistry->addMenu($menu1); + + $menu2 = $menuRegistry->makeMenu()->setIdentifier('Foo'); + $item = $menu2->makeMenuItem()->setHref('#')->setTitle('Husel'); + $menu2->addMenuItem($item); + + $menuRegistry->addMenu($menu2); + + $result = $menuRegistry->getMenus(); + $expected = [ + 'Foo' => $menu2 + ]; + + $this->assertEquals($expected, $result); + } + +} \ No newline at end of file diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-69814-ModuleTemplateAPI.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-69814-ModuleTemplateAPI.rst new file mode 100644 index 0000000000000000000000000000000000000000..7950c2749fe49726877af294e80aff17a36a431e --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-69814-ModuleTemplateAPI.rst @@ -0,0 +1,98 @@ +================== +ModuleTemplate API +================== + +Challenge +========= + + +Currently all DocHeaders are implemented on their own. + +This means we have about 80 DocHeaders which are equal but not the same. + +The main challenge is to provide extension developers with all tools they need to build decent backend modules while maintaining control of the docHeader itself. + +Solution +======== + +We will provide a replacement for DocumentTemplate which provides an easy-to-use API which is on the other hand flexible enough to tackle all tasks we currently think of. + +At the same time we will remove the amount of duplicate marker based templates. + +The API uses the Fluent-API approach and has been built to supply maximum IDE code completion support. + +Parts of a docHeader Currently a typical docHeader is split up into the following sections: + +* Top Bar + + * Context Sensitive Help Icon + * Select Menu(s) + * Path + * RecordInformation incl. Clickmenu + +* Bottom Bar + + * Left Button Bar + * Right Button Bar + +API Components +============== + +Buttons +------- + +**InputButton** + Used to generate a <button> element. + +**LinkButton** + Used to generate links + +**SplitButton** + A mixed component accepting multiple button objects and renders them into a condensed form. + +**FullyRenderedButton** + Displays arbitrary HTML code and we highly recommend to use these. + +Menus +----- + +Creating menus is pretty simple. +Ask the ``DocHeaderComponent`` for the ``MenuRegistry`` and ask the ``MenuRegistry`` to create a ``Menu`` for you. + +The ``Menu`` in return can create ``MenuItems`` for you. + +A ``Menu`` can have several **Types** which are represented by their respective Fluid Partials in EXT:backend/Resources/Private/Partials/Menu/. + + +Examples of usages +================== + +**Adding a button** + +.. code-block:: php + + $openInNewWindowButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar() + ->makeLinkButton() + ->setHref('#') + ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow', TRUE)) + ->setIcon($this->iconFactory->getIcon('actions-window-open', Icon::SIZE_SMALL)) + ->setOnClick($aOnClick); + + $this->moduleTemplate->getDocHeaderComponent()->getButtonBar() + ->addButton($openInNewWindowButton, ButtonBar::BUTTON_POSITION_RIGHT); + +**Adding a menu with menu items** + +.. code-block:: php + + $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getModuleMenuRegistry()->makeMenu() + ->setIdentifier('_langSelector') + ->setLabel($this->getLanguageService()->sL('LLL:EXT:lang/locallang_general.xlf:LGL.language', TRUE)); + $menuItem = $languageMenu->makeMenuItem() + ->setTitle($lang['title'] . $newTranslation) + ->setHref($href); + if((int)$lang['uid'] === $currentLanguage) { + $menuItem->setActive(TRUE); + } + $languageMenu->addMenuItem($menuItem); + $this->moduleTemplate->getDocHeaderComponent()->getModuleMenuRegistry()->addMenu($languageMenu); \ No newline at end of file diff --git a/typo3/sysext/t3skin/Resources/Public/Css/backend.css b/typo3/sysext/t3skin/Resources/Public/Css/backend.css index dadfc6207d6ccf5577f8426cf420f18b3113fa53..395b550eeeab3e0e2e72305f3f111dfbfdb0066f 100644 --- a/typo3/sysext/t3skin/Resources/Public/Css/backend.css +++ b/typo3/sysext/t3skin/Resources/Public/Css/backend.css @@ -7854,6 +7854,81 @@ button.close { background-color: #ebfce3; text-decoration: none; } +.module { + height: 100%; + width: 100%; + background-color: #ffffff; +} +.module form { + margin: 0; +} +.module-docheader { + position: fixed; + width: 100%; + top: 0; + left: 0; + height: 65px; + z-index: 300; + background-color: #eeeeee; + border-bottom: 1px solid #c3c3c3; + padding: 0 24px; +} +.module-docheader:before, +.module-docheader:after { + content: " "; + display: table; +} +.module-docheader:after { + clear: both; +} +.module-docheader:before, +.module-docheader:after { + content: " "; + display: table; +} +.module-docheader:after { + clear: both; +} +.module-docheader-bar { + height: 26px; + margin: 4px 0; + line-height: 26px; +} +.module-docheader-bar:before, +.module-docheader-bar:after { + content: " "; + display: table; +} +.module-docheader-bar:after { + clear: both; +} +.module-docheader-bar:before, +.module-docheader-bar:after { + content: " "; + display: table; +} +.module-docheader-bar:after { + clear: both; +} +.module-docheader-bar label { + margin-top: 0; + margin-bottom: 0; +} +.module-docheader-bar .form-group { + margin: 0; +} +.module-docheader-bar-column-left { + float: left; +} +.module-docheader-bar-column-right { + float: right; +} +.module-body { + padding: 24px 24px; +} +.module-docheader + .module-body { + padding-top: 89px; +} .clearfix:before, .clearfix:after, .dl-horizontal dd:before, @@ -11461,8 +11536,9 @@ a.badge-danger:hover, a.badge-danger:focus { background-color: #a32e2e; } -.btn .t3-icon { - margin: 0; +.btn-sm, +.btn-group-sm > .btn { + min-height: 26px; } .btn-group { font-size: 0;