From cf26e30e58b4f109e43ee75158f855ac1167778a Mon Sep 17 00:00:00 2001
From: Mathias Schreiber <mathias.schreiber@wmdb.de>
Date: Tue, 22 Sep 2015 09:46:21 +0200
Subject: [PATCH] [FEATURE] Fluid based Module Template

A new class structure around "ModuleTemplate" is introduced to
substitute the rusty "DocumentTemplate". The architecture can
be seen as wrapper around the main html content a backend
controller action creates. ModuleTemplate provides an API
especially for the "doc header" and unifies its display
and creation.

The patch releases single controller actions from fiddling with
doc header details like actual button HTML and hands over
this concern to the framework, so it can steer these parts
at a central place.

Since this API and its embedding in the framework is a rather
complex task that will require further tweaks, this main API
is mostly marked as "experimental" for now: It will further
settle with 8. For extension authors it means that *if* this
API is used, there may be code adaptions required in version 8
since parts of the API will be adapted to further needs.

Resolves: #69814
Releases: master
Change-Id: I81cb9f01ebc4b13d8d88a928811e563d83ec97cd
Reviewed-on: http://review.typo3.org/43465
Reviewed-by: Benjamin Kott <info@bk2k.info>
Tested-by: Benjamin Kott <info@bk2k.info>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Mathias Schreiber <mathias.schreiber@wmdb.de>
Tested-by: Mathias Schreiber <mathias.schreiber@wmdb.de>
---
 .../Public/Less/Component/module.less         |  88 ++
 .../Public/Less/TYPO3/_element_buttons.less   |   6 +-
 Build/Resources/Public/Less/_minimal.less     |   1 +
 .../backend/Classes/Module/AbstractModule.php |  81 ++
 .../Template/Components/AbstractControl.php   | 135 +++
 .../Classes/Template/Components/ButtonBar.php | 137 ++++
 .../Components/Buttons/AbstractButton.php     | 100 +++
 .../Components/Buttons/ButtonInterface.php    |  49 ++
 .../Buttons/FullyRenderedButton.php           | 106 +++
 .../Components/Buttons/InputButton.php        | 141 ++++
 .../Components/Buttons/LinkButton.php         | 114 +++
 .../Components/Buttons/SplitButton.php        | 172 ++++
 .../Components/DocHeaderComponent.php         |  96 +++
 .../Classes/Template/Components/Menu/Menu.php | 135 +++
 .../Template/Components/Menu/MenuItem.php     |  97 +++
 .../Template/Components/MenuRegistry.php      |  79 ++
 .../Template/Components/MetaInformation.php   | 133 +++
 .../Classes/Template/ModuleTemplate.php       | 771 ++++++++++++++++++
 .../Resources/Private/Partials/ButtonBar.html |   9 +
 .../Resources/Private/Partials/DocHeader.html |  25 +
 .../Partials/Menus/SelectBoxJumpMenu.html     |   5 +
 .../Resources/Private/Templates/Module.html   |  16 +
 .../Resources/Private/Templates/blank.html    |   3 +
 .../Button/FullyRenderedButtonTest.php        |  50 ++
 .../Components/Button/InputButtonTest.php     | 106 +++
 .../Components/Button/LinkButtonTest.php      |  92 +++
 .../Components/Button/SplitButtonTest.php     | 126 +++
 .../Template/Components/Menu/MenuItemTest.php |  77 ++
 .../Unit/Template/Components/MenuTest.php     |  94 +++
 .../Feature-69814-ModuleTemplateAPI.rst       |  98 +++
 .../t3skin/Resources/Public/Css/backend.css   |  80 +-
 31 files changed, 3216 insertions(+), 6 deletions(-)
 create mode 100644 Build/Resources/Public/Less/Component/module.less
 create mode 100644 typo3/sysext/backend/Classes/Module/AbstractModule.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/AbstractControl.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/ButtonBar.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/Buttons/AbstractButton.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/Buttons/ButtonInterface.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/Buttons/FullyRenderedButton.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/Buttons/InputButton.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/Buttons/LinkButton.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/Buttons/SplitButton.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/DocHeaderComponent.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/Menu/Menu.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/Menu/MenuItem.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/MenuRegistry.php
 create mode 100644 typo3/sysext/backend/Classes/Template/Components/MetaInformation.php
 create mode 100644 typo3/sysext/backend/Classes/Template/ModuleTemplate.php
 create mode 100644 typo3/sysext/backend/Resources/Private/Partials/ButtonBar.html
 create mode 100644 typo3/sysext/backend/Resources/Private/Partials/DocHeader.html
 create mode 100644 typo3/sysext/backend/Resources/Private/Partials/Menus/SelectBoxJumpMenu.html
 create mode 100644 typo3/sysext/backend/Resources/Private/Templates/Module.html
 create mode 100644 typo3/sysext/backend/Resources/Private/Templates/blank.html
 create mode 100644 typo3/sysext/backend/Tests/Unit/Template/Components/Button/FullyRenderedButtonTest.php
 create mode 100644 typo3/sysext/backend/Tests/Unit/Template/Components/Button/InputButtonTest.php
 create mode 100644 typo3/sysext/backend/Tests/Unit/Template/Components/Button/LinkButtonTest.php
 create mode 100644 typo3/sysext/backend/Tests/Unit/Template/Components/Button/SplitButtonTest.php
 create mode 100644 typo3/sysext/backend/Tests/Unit/Template/Components/Menu/MenuItemTest.php
 create mode 100644 typo3/sysext/backend/Tests/Unit/Template/Components/MenuTest.php
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-69814-ModuleTemplateAPI.rst

diff --git a/Build/Resources/Public/Less/Component/module.less b/Build/Resources/Public/Less/Component/module.less
new file mode 100644
index 000000000000..cbbe71d916f9
--- /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 6a339682c575..20eea7dfea2c 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 8e1564a6e71b..789aeada41af 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 000000000000..901f3e9255ef
--- /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 000000000000..d7eedf4cc9b6
--- /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 000000000000..57be28467598
--- /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 000000000000..e5d8bdb32e67
--- /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 000000000000..df1f83bad5a0
--- /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 000000000000..6af32e61447b
--- /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 000000000000..70038435a211
--- /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 000000000000..aead71088e79
--- /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 000000000000..881e1314b772
--- /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 000000000000..bf0e5eeb292b
--- /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 000000000000..7dd6bbcc1f07
--- /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 000000000000..6960ef22d6d4
--- /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 000000000000..ded6f9eff618
--- /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 000000000000..ae349eee2cc2
--- /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) . '&nbsp;[' . $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 000000000000..f1157e5948d4
--- /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 000000000000..f1b5b7195421
--- /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 000000000000..b6dd7d015e75
--- /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 000000000000..c6c7e7d673e3
--- /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 000000000000..bb3fe1cc166d
--- /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 000000000000..b1ae1ad5620c
--- /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 000000000000..1309cab948c0
--- /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 000000000000..5046ee9180f4
--- /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 000000000000..bbdefc280aa0
--- /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 000000000000..78f19d4a3f11
--- /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 000000000000..180b173fc999
--- /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 000000000000..e4c4f779fab1
--- /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 000000000000..7950c2749fe4
--- /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 dadfc6207d6c..395b550eeeab 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;
-- 
GitLab