From 13d0fcc0c27ff27f3498ebda2cbd2f22f12492ab Mon Sep 17 00:00:00 2001
From: Andreas Fernandez <a.fernandez@scripting-base.de>
Date: Tue, 16 Jun 2015 21:52:05 +0200
Subject: [PATCH] [TASK] Port toolbar dropdowns to Fluid

The toolbar dropdown menus are ported to Fluid. Also, some small
improvements are made in this patch.

Resolves: #67537
Related: #67568
Releases: master
Change-Id: I41b502e97108494bf6d4acfa0dedb32bb18a5e18
Reviewed-on: http://review.typo3.org/40329
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
---
 .../ToolbarItems/AbstractToolbarItem.php      |  36 ++++--
 .../ToolbarItems/ClearCacheToolbarItem.php    |  29 +++--
 .../Backend/ToolbarItems/HelpToolbarItem.php  |  42 ++++---
 .../ToolbarItems/ShortcutToolbarItem.php      | 104 +++++++++++-------
 .../SystemInformationToolbarItem.php          |   9 +-
 .../Backend/ToolbarItems/UserToolbarItem.php  |  55 ++++-----
 .../Templates/ToolbarMenu/ClearCache.html     |   9 ++
 .../Private/Templates/ToolbarMenu/Help.html   |  10 ++
 .../Templates/ToolbarMenu/Shortcut.html       |  30 +++++
 .../Private/Templates/ToolbarMenu/User.html   |  16 +++
 .../ToolbarItems/OpendocsToolbarItem.php      |  78 ++++++++-----
 .../Templates/ToolbarMenu/Opendocs.html       |  45 ++++++++
 .../WorkspaceSelectorToolbarItem.php          |  57 ++++++----
 .../ToolbarMenu/WorkspaceSelector.html        |  32 ++++++
 14 files changed, 397 insertions(+), 155 deletions(-)
 create mode 100644 typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/ClearCache.html
 create mode 100644 typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/Help.html
 create mode 100644 typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/Shortcut.html
 create mode 100644 typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/User.html
 create mode 100644 typo3/sysext/opendocs/Resources/Private/Templates/ToolbarMenu/Opendocs.html
 create mode 100644 typo3/sysext/workspaces/Resources/Private/Templates/ToolbarMenu/WorkspaceSelector.html

diff --git a/typo3/sysext/backend/Classes/Backend/ToolbarItems/AbstractToolbarItem.php b/typo3/sysext/backend/Classes/Backend/ToolbarItems/AbstractToolbarItem.php
index 9ddf47e0b906..98f9f2acd36e 100644
--- a/typo3/sysext/backend/Classes/Backend/ToolbarItems/AbstractToolbarItem.php
+++ b/typo3/sysext/backend/Classes/Backend/ToolbarItems/AbstractToolbarItem.php
@@ -23,27 +23,47 @@ use TYPO3\CMS\Fluid\View\StandaloneView;
  */
 abstract class AbstractToolbarItem {
 
+	/**
+	 * @var string Extension context
+	 */
+	protected $extension = 'backend';
+
+	/**
+	 * @var string Template file for the dropdown menu
+	 */
+	protected $templateFile = '';
+
 	/**
 	 * @var StandaloneView
 	 */
 	protected $standaloneView = NULL;
 
+	/**
+	 * Constructor
+	 *
+	 * @throws \InvalidArgumentException
+	 */
 	public function __construct() {
-		$extPath = ExtensionManagementUtility::extPath('backend');
+		if (empty($this->templateFile)) {
+			throw new \InvalidArgumentException('The template file for class "' . get_class($this) . '" is not set.', 1434530382);
+		}
+
+		$extPath = ExtensionManagementUtility::extPath($this->extension);
 		/* @var $view StandaloneView */
 		$this->standaloneView = GeneralUtility::makeInstance(StandaloneView::class);
-		$this->standaloneView->setTemplatePathAndFilename($extPath . 'Resources/Private/Templates/ToolbarMenu/' . static::TOOLBAR_MENU_TEMPLATE);
+		$this->standaloneView->setTemplatePathAndFilename($extPath . 'Resources/Private/Templates/ToolbarMenu/' . $this->templateFile);
+		$this->standaloneView->setPartialRootPaths(array(
+			$extPath . 'Resources/Private/Partials/ToolbarMenu/'
+		));
 	}
 
 	/**
-	 * @param string $extension Set the extension context (required for shorthand locallang.xlf references)
 	 * @return StandaloneView
 	 */
-	protected function getStandaloneView($extension = NULL) {
-		if (!empty($extension)) {
-			$request = $this->standaloneView->getRequest();
-			$request->setControllerExtensionName($extension);
-		}
+	protected function getStandaloneView() {
+		$request = $this->standaloneView->getRequest();
+		$request->setControllerExtensionName($this->extension);
+
 		return $this->standaloneView;
 	}
 }
diff --git a/typo3/sysext/backend/Classes/Backend/ToolbarItems/ClearCacheToolbarItem.php b/typo3/sysext/backend/Classes/Backend/ToolbarItems/ClearCacheToolbarItem.php
index 658a1c6ee9a1..f0311868cf4f 100644
--- a/typo3/sysext/backend/Classes/Backend/ToolbarItems/ClearCacheToolbarItem.php
+++ b/typo3/sysext/backend/Classes/Backend/ToolbarItems/ClearCacheToolbarItem.php
@@ -25,7 +25,12 @@ use TYPO3\CMS\Backend\Toolbar\ClearCacheActionsHookInterface;
  *
  * @author Ingo Renner <ingo@typo3.org>
  */
-class ClearCacheToolbarItem implements ToolbarItemInterface {
+class ClearCacheToolbarItem extends AbstractToolbarItem implements ToolbarItemInterface {
+
+	/**
+	 * @var string Template file for the dropdown menu
+	 */
+	protected $templateFile = 'ClearCache.html';
 
 	/**
 	 * @var array
@@ -43,6 +48,8 @@ class ClearCacheToolbarItem implements ToolbarItemInterface {
 	 * @throws \UnexpectedValueException
 	 */
 	public function __construct() {
+		parent::__construct();
+
 		$backendUser = $this->getBackendUser();
 		$languageService = $this->getLanguageService();
 
@@ -136,18 +143,20 @@ class ClearCacheToolbarItem implements ToolbarItemInterface {
 	 * @return string Drop down HTML
 	 */
 	public function getDropDown() {
-		$result = array();
-		$result[] = '<ul class="dropdown-list">';
+		$items = array();
 		foreach ($this->cacheActions as $cacheAction) {
 			$title = $cacheAction['description'] ?: $cacheAction['title'];
-			$result[] = '<li>';
-			$result[] = '<a class="dropdown-list-link" href="' . htmlspecialchars($cacheAction['href']) . '" title="' . htmlspecialchars($title) . '">';
-			$result[] = $cacheAction['icon'] . ' ' . htmlspecialchars($cacheAction['title']);
-			$result[] = '</a>';
-			$result[] = '</li>';
+			$items[] = array(
+				'title' => $title,
+				'label' => $cacheAction['title'],
+				'href' => $cacheAction['href'],
+				'icon' => $cacheAction['icon']
+			);
 		}
-		$result[] = '</ul>';
-		return implode(LF, $result);
+
+		$standaloneView = $this->getStandaloneView();
+		$standaloneView->assign('items', $items);
+		return $standaloneView->render();
 	}
 
 	/**
diff --git a/typo3/sysext/backend/Classes/Backend/ToolbarItems/HelpToolbarItem.php b/typo3/sysext/backend/Classes/Backend/ToolbarItems/HelpToolbarItem.php
index e86af44381c4..e3134c93b981 100644
--- a/typo3/sysext/backend/Classes/Backend/ToolbarItems/HelpToolbarItem.php
+++ b/typo3/sysext/backend/Classes/Backend/ToolbarItems/HelpToolbarItem.php
@@ -22,7 +22,12 @@ use TYPO3\CMS\Backend\Domain\Model\Module\BackendModule;
 /**
  * Help toolbar item
  */
-class HelpToolbarItem implements ToolbarItemInterface {
+class HelpToolbarItem extends AbstractToolbarItem implements ToolbarItemInterface {
+
+	/**
+	 * @var string Template file for the dropdown menu
+	 */
+	protected $templateFile = 'Help.html';
 
 	/**
 	 * @var \SplObjectStorage<BackendModule>
@@ -33,6 +38,8 @@ class HelpToolbarItem implements ToolbarItemInterface {
 	 * Constructor
 	 */
 	public function __construct() {
+		parent::__construct();
+
 		/** @var BackendModuleRepository $backendModuleRepository */
 		$backendModuleRepository = GeneralUtility::makeInstance(BackendModuleRepository::class);
 		/** @var \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule $userModuleMenu */
@@ -68,26 +75,25 @@ class HelpToolbarItem implements ToolbarItemInterface {
 	 */
 	public function getDropDown() {
 		$dropdown = array();
-		$dropdown[] = '<ul class="dropdown-list">';
 		foreach ($this->helpModuleMenu->getChildren() as $module) {
 			/** @var BackendModule $module */
-			$moduleIcon = $module->getIcon();
-			$dropdown[] ='<li'
-				. ' id="' . htmlspecialchars($module->getName()) . '"'
-				. ' class="typo3-module-menu-item submodule mod-' . htmlspecialchars($module->getName()) . '" '
-				. ' data-modulename="' . htmlspecialchars($module->getName()) . '"'
-				. ' data-navigationcomponentid="' . htmlspecialchars($module->getNavigationComponentId()) . '"'
-				. ' data-navigationframescript="' . htmlspecialchars($module->getNavigationFrameScript()) . '"'
-				. ' data-navigationframescriptparameters="' . htmlspecialchars($module->getNavigationFrameScriptParameters()) . '"'
-				. '>';
-			$dropdown[] = '<a title="' . htmlspecialchars($module->getDescription()) . '" href="' . htmlspecialchars($module->getLink()) . '" class="dropdown-list-link modlink">';
-			$dropdown[] = '<span class="submodule-icon typo3-app-icon"><span><span>' . $moduleIcon . '</span></span></span>';
-			$dropdown[] = '<span class="submodule-label">' . htmlspecialchars($module->getTitle()) . '</span>';
-			$dropdown[] = '</a>';
-			$dropdown[] = '</li>';
+			$dropdown[] = array(
+				'id' => $module->getName(),
+				'navigation' => array(
+					'componentId' => $module->getNavigationComponentId(),
+					'frameScript' => $module->getNavigationFrameScript(),
+					'frameScriptParameters' => $module->getNavigationFrameScriptParameters(),
+				),
+				'href' => $module->getLink(),
+				'description' => $module->getDescription(),
+				'icon' => $module->getIcon(),
+				'label' => $module->getTitle()
+			);
 		}
-		$dropdown[] = '</ul>';
-		return implode(LF, $dropdown);
+
+		$standaloneView = $this->getStandaloneView();
+		$standaloneView->assign('dropdown', $dropdown);
+		return $standaloneView->render();
 	}
 
 	/**
diff --git a/typo3/sysext/backend/Classes/Backend/ToolbarItems/ShortcutToolbarItem.php b/typo3/sysext/backend/Classes/Backend/ToolbarItems/ShortcutToolbarItem.php
index da5374d5f4d7..d9a82d6787a6 100644
--- a/typo3/sysext/backend/Classes/Backend/ToolbarItems/ShortcutToolbarItem.php
+++ b/typo3/sysext/backend/Classes/Backend/ToolbarItems/ShortcutToolbarItem.php
@@ -28,13 +28,28 @@ use TYPO3\CMS\Core\Utility\PathUtility;
  *
  * @author Ingo Renner <ingo@typo3.org>
  */
-class ShortcutToolbarItem implements ToolbarItemInterface {
+class ShortcutToolbarItem extends AbstractToolbarItem implements ToolbarItemInterface {
 
 	/**
-	 * @const integer Number of super global group
+	 * @const int Number of super global group
 	 */
 	const SUPERGLOBAL_GROUP = -100;
 
+	/**
+	 * @const string Type of shortcut groups
+	 */
+	const TYPE_GROUP = 'group';
+
+	/**
+	 * @const string Type of shortcut items
+	 */
+	const TYPE_ITEM = 'item';
+
+	/**
+	 * @var string Template file for the dropdown menu
+	 */
+	protected $templateFile = 'Shortcut.html';
+
 	/**
 	 * @var string
 	 */
@@ -76,6 +91,8 @@ class ShortcutToolbarItem implements ToolbarItemInterface {
 			$loadModules->load($GLOBALS['TBE_MODULES']);
 		}
 
+		parent::__construct();
+
 		// By default, 5 groups are set
 		$this->shortcutGroups = array(
 			1 => '1',
@@ -126,65 +143,70 @@ class ShortcutToolbarItem implements ToolbarItemInterface {
 		$editIcon = '<a href="#" class="dropdown-list-link-edit shortcut-edit">' . IconUtility::getSpriteIcon('actions-document-open', array('title' => $shortcutEdit)) . '</a>';
 		$deleteIcon = '<a href="#" class="dropdown-list-link-delete shortcut-delete">' . IconUtility::getSpriteIcon('actions-edit-delete', array('title' => $shortcutDelete)) . '</a>';
 
-		$shortcutMenu[] = '<ul class="dropdown-list">';
+		$shortcutMenu = array();
 
 		// Render shortcuts with no group (group id = 0) first
 		$noGroupShortcuts = $this->getShortcutsByGroup(0);
 		foreach ($noGroupShortcuts as $shortcut) {
-
-			$shortcutMenu[] = '
-				<li class="shortcut" data-shortcutid="' . (int)$shortcut['raw']['uid'] . '">
-					<a class="dropdown-list-link dropdown-link-list-add-editdelete" href="#" onclick="' . htmlspecialchars($shortcut['action']) . ' return false;">' .
-						$shortcut['icon'] . ' ' .
-						htmlspecialchars($shortcut['label']) .
-					'</a>
-					' . $editIcon . $deleteIcon . '
-				</li>';
+			$shortcutMenu[] = array(
+				'uid' => (int)$shortcut['raw']['uid'],
+				'groupid' => static::SUPERGLOBAL_GROUP,
+				'type' => static::TYPE_ITEM,
+				'action' => $shortcut['action'],
+				'icon' => $shortcut['icon'],
+				'label' => $shortcut['label']
+			);
 		}
 		// Now render groups and the contained shortcuts
 		$groups = $this->getGroupsFromShortcuts();
 		krsort($groups, SORT_NUMERIC);
 		foreach ($groups as $groupId => $groupLabel) {
-			if ($groupId != 0) {
-				$shortcutGroup = '';
-				if (count($shortcutMenu) > 1) {
-					$shortcutGroup .= '<li class="divider"></li>';
-				}
-				$shortcutGroup .= '
-					<li class="dropdown-header" id="shortcut-group-' . (int)$groupId . '">
-						' . $groupLabel . '
-					</li>';
-				$shortcuts = $this->getShortcutsByGroup($groupId);
-				$i = 0;
-				foreach ($shortcuts as $shortcut) {
-					$i++;
-					$shortcutGroup .= '
-					<li class="shortcut" data-shortcutid="' . (int)$shortcut['raw']['uid'] . '" data-shortcutgroup="' . (int)$groupId . '">
-						<a class="dropdown-list-link dropdown-link-list-add-editdelete" href="#" onclick="' . htmlspecialchars($shortcut['action']) . ' return false;">' .
-							$shortcut['icon'] . ' ' .
-							htmlspecialchars($shortcut['label']) .
-						'</a>
-						' . $editIcon . $deleteIcon . '
-					</li>';
-				}
-				$shortcutMenu[] = $shortcutGroup;
+			$groupId = (int)$groupId;
+			if ($groupId === 0) {
+				continue;
+			}
+
+			$shortcutMenu[] = array(
+				'uid' => $groupId,
+				'type' => static::TYPE_GROUP,
+				'label' => $groupLabel
+			);
+
+			$shortcuts = $this->getShortcutsByGroup($groupId);
+			$i = 0;
+			foreach ($shortcuts as $shortcut) {
+				$i++;
+				$shortcutMenu[] = array(
+					'uid' => (int)$shortcut['raw']['uid'],
+					'groupid' => (int)$groupId,
+					'type' => static::TYPE_ITEM,
+					'action' => $shortcut['action'],
+					'icon' => $shortcut['icon'],
+					'label' => $shortcut['label']
+				);
 			}
 		}
-		$shortcutMenu[] = '</ul>';
 
-		if (count($shortcutMenu) == 2) {
+		$standaloneView = $this->getStandaloneView();
+		$standaloneView->assignMultiple(array(
+			'hasEntries' => !empty($shortcutMenu),
+			'shortcutMenu' => $shortcutMenu,
+			'editIcon' => $editIcon,
+			'deleteIcon' => $deleteIcon,
+		));
+
+		if (empty($shortcutMenu)) {
 			// No shortcuts added yet, show a small help message how to add shortcuts
 			$title = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.bookmarks', TRUE);
 			$icon = IconUtility::getSpriteIcon('actions-system-shortcut-new', array(
 				'title' => $title
 			));
 			$label = str_replace('%icon%', $icon, $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:bookmarkDescription'));
-			$compiledShortcutMenu = '<p>' . $label . '</p>';
-		} else {
-			$compiledShortcutMenu = implode(LF, $shortcutMenu);
+
+			$standaloneView->assign('introduction', $label);
 		}
 
-		return $compiledShortcutMenu;
+		return $standaloneView->render();
 	}
 
 	/**
diff --git a/typo3/sysext/backend/Classes/Backend/ToolbarItems/SystemInformationToolbarItem.php b/typo3/sysext/backend/Classes/Backend/ToolbarItems/SystemInformationToolbarItem.php
index f2e86857ce05..f01b6243ae12 100644
--- a/typo3/sysext/backend/Classes/Backend/ToolbarItems/SystemInformationToolbarItem.php
+++ b/typo3/sysext/backend/Classes/Backend/ToolbarItems/SystemInformationToolbarItem.php
@@ -29,9 +29,9 @@ use TYPO3\CMS\Core\Utility\StringUtility;
 class SystemInformationToolbarItem extends AbstractToolbarItem implements ToolbarItemInterface {
 
 	/**
-	 * Template file for the dropdown menu
+	 * @var string Template file for the dropdown menu
 	 */
-	const TOOLBAR_MENU_TEMPLATE = 'SystemInformation.html';
+	protected $templateFile = 'SystemInformation.html';
 
 	/**
 	 * Number displayed as badge on the dropdown trigger
@@ -280,14 +280,15 @@ class SystemInformationToolbarItem extends AbstractToolbarItem implements Toolba
 			return '';
 		}
 
-		$this->getStandaloneView('backend')->assignMultiple(array(
+		$standaloneView = $this->getStandaloneView();
+		$standaloneView->assignMultiple(array(
 			'installToolUrl' => BackendUtility::getModuleUrl('system_InstallInstall'),
 			'messages' => $this->systemMessages,
 			'count' => $this->totalCount,
 			'severityBadgeClass' => $this->severityBadgeClass,
 			'systemInformation' => $this->systemInformation
 		));
-		return $this->getStandaloneView()->render();
+		return $standaloneView->render();
 	}
 
 	/**
diff --git a/typo3/sysext/backend/Classes/Backend/ToolbarItems/UserToolbarItem.php b/typo3/sysext/backend/Classes/Backend/ToolbarItems/UserToolbarItem.php
index 406ac7394de0..271fa26f28bb 100644
--- a/typo3/sysext/backend/Classes/Backend/ToolbarItems/UserToolbarItem.php
+++ b/typo3/sysext/backend/Classes/Backend/ToolbarItems/UserToolbarItem.php
@@ -24,7 +24,12 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 /**
  * User toolbar item
  */
-class UserToolbarItem implements ToolbarItemInterface {
+class UserToolbarItem extends AbstractToolbarItem implements ToolbarItemInterface {
+
+	/**
+	 * @var string Template file for the dropdown menu
+	 */
+	protected $templateFile = 'User.html';
 
 	/**
 	 * Item is always enabled
@@ -75,7 +80,6 @@ class UserToolbarItem implements ToolbarItemInterface {
 		$languageService = $this->getLanguageService();
 
 		$dropdown = array();
-		$dropdown[] = '<ul class="dropdown-list">';
 
 		/** @var BackendModuleRepository $backendModuleRepository */
 		$backendModuleRepository = GeneralUtility::makeInstance(BackendModuleRepository::class);
@@ -84,34 +88,33 @@ class UserToolbarItem implements ToolbarItemInterface {
 		if ($userModuleMenu != FALSE && $userModuleMenu->getChildren()->count() > 0) {
 			foreach ($userModuleMenu->getChildren() as $module) {
 				/** @var BackendModule $module */
-				$dropdown[] ='<li'
-					. ' id="' . htmlspecialchars($module->getName()) . '"'
-					. ' class="typo3-module-menu-item submodule mod-' . htmlspecialchars($module->getName()) . '" '
-					. ' data-modulename="' . htmlspecialchars($module->getName()) . '"'
-					. ' data-navigationcomponentid="' . htmlspecialchars($module->getNavigationComponentId()) . '"'
-					. ' data-navigationframescript="' . htmlspecialchars($module->getNavigationFrameScript()) . '"'
-					. ' data-navigationframescriptparameters="' . htmlspecialchars($module->getNavigationFrameScriptParameters()) . '"'
-					. '>';
-				$dropdown[] = '<a title="' . htmlspecialchars($module->getDescription()) . '" href="' . htmlspecialchars($module->getLink()) . '" class="dropdown-list-link modlink">';
-				$dropdown[] = '<span class="submodule-icon typo3-app-icon"><span><span>' . $module->getIcon() . '</span></span></span>';
-				$dropdown[] = '<span class="submodule-label">' . htmlspecialchars($module->getTitle()) . '</span>';
-				$dropdown[] = '</a>';
-				$dropdown[] = '</li>';
+				$dropdown[] = array(
+					'id' => $module->getName(),
+					'navigation' => array(
+						'componentId' => $module->getNavigationComponentId(),
+						'frameScript' => $module->getNavigationFrameScript(),
+						'frameScriptParameters' => $module->getNavigationFrameScriptParameters(),
+					),
+					'href' => $module->getLink(),
+					'description' => $module->getDescription(),
+					'icon' => $module->getIcon(),
+					'label' => $module->getTitle()
+				);
 			}
-			$dropdown[] = '<li class="divider"></li>';
 		}
 
 		// Logout button
-		$buttonLabel = 'LLL:EXT:lang/locallang_core.xlf:' . ($backendUser->user['ses_backuserid'] ? 'buttons.exit' : 'buttons.logout');
-		$dropdown[] = '<li class="reset-dropdown">';
-		$dropdown[] = '<a href="' . htmlspecialchars(BackendUtility::getModuleUrl('logout')) . '" class="btn btn-danger pull-right" target="_top"><i class="fa fa-power-off"></i> ';
-		$dropdown[] = $languageService->sL($buttonLabel, TRUE);
-		$dropdown[] = '</a>';
-		$dropdown[] = '</li>';
-
-		$dropdown[] = '</ul>';
-
-		return implode(LF, $dropdown);
+		$logoutButton = array(
+			'label' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:' . ($backendUser->user['ses_backuserid'] ? 'buttons.exit' : 'buttons.logout')),
+			'href' => htmlspecialchars(BackendUtility::getModuleUrl('logout')),
+		);
+
+		$standaloneView = $this->getStandaloneView();
+		$standaloneView->assignMultiple(array(
+			'dropdown' => $dropdown,
+			'logoutButton' => $logoutButton
+		));
+		return $standaloneView->render();
 	}
 
 	/**
diff --git a/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/ClearCache.html b/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/ClearCache.html
new file mode 100644
index 000000000000..e1a59d0b5bf1
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/ClearCache.html
@@ -0,0 +1,9 @@
+<ul class="dropdown-list">
+	<f:for each="{items}" as="item">
+		<li>
+			<a class="dropdown-list-link" href="{item.href}" title="{item.title}">
+				<f:format.raw>{item.icon}</f:format.raw> {item.label}
+			</a>
+		</li>
+	</f:for>
+</ul>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/Help.html b/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/Help.html
new file mode 100644
index 000000000000..a2b3450ac290
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/Help.html
@@ -0,0 +1,10 @@
+<ul class="dropdown-list">
+	<f:for each="{dropdown}" as="item">
+		<li class="typo3-module-menu-item submodule mod-{item.id}" id="{item.id}" data-modulename="{item.id}" data-navigationcomponentid="{item.navigation.componentId}" data-navigationframescript="{item.navigation.frameScript}"  data-navigationframescriptparameters="{item.navigation.frameScriptParameters}">
+			<a class="dropdown-list-link modlink" href="{item.href}" title="{item.description}">
+				<span class="submodule-icon typo3-app-icon"><span><span><f:format.raw>{item.icon}</f:format.raw></span></span></span>
+				<span class="submodule-label">{item.label}</span>
+			</a>
+		</li>
+	</f:for>
+</ul>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/Shortcut.html b/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/Shortcut.html
new file mode 100644
index 000000000000..3470370b29e6
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/Shortcut.html
@@ -0,0 +1,30 @@
+<f:if condition="{hasEntries}">
+	<f:then>
+		<ul class="dropdown-list">
+			<f:for each="{shortcutMenu}" as="shortcut" iteration="iteration">
+				<f:if condition="{shortcut.type} == 'group'">
+					<f:if condition="{iteration.isFirst}">
+						<f:then></f:then>
+						<f:else><li class="divider"></li></f:else>
+					</f:if>
+					<li class="dropdown-header" id="shortcut-group-{shortcut.uid}">
+						{shortcut.label}
+					</li>
+				</f:if>
+
+				<f:if condition="{shortcut.type} == 'item'">
+					<li class="shortcut" data-shortcutid="{shortcut.uid}">
+						<a class="dropdown-list-link dropdown-link-list-add-editdelete" href="#" onclick="{shortcut.action} return false;">
+							<f:format.raw>{shortcut.icon}</f:format.raw> {shortcut.label}
+						</a>
+						<f:format.raw>{editIcon}</f:format.raw>
+						<f:format.raw>{deleteIcon}</f:format.raw>
+					</li>
+				</f:if>
+			</f:for>
+		</ul>
+	</f:then>
+	<f:else>
+		<f:format.raw>{introduction}</f:format.raw>
+	</f:else>
+</f:if>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/User.html b/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/User.html
new file mode 100644
index 000000000000..616add699967
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Templates/ToolbarMenu/User.html
@@ -0,0 +1,16 @@
+<ul class="dropdown-list">
+	<f:for each="{dropdown}" as="item">
+		<li class="typo3-module-menu-item submodule mod-{item.id}" id="{item.id}" data-modulename="{item.id}" data-navigationcomponentid="{item.navigation.componentId}" data-navigationframescript="{item.navigation.frameScript}"  data-navigationframescriptparameters="{item.navigation.frameScriptParameters}">
+			<a class="dropdown-list-link modlink" href="{item.href}" title="{item.description}">
+				<span class="submodule-icon typo3-app-icon"><span><span><f:format.raw>{item.icon}</f:format.raw></span></span></span>
+				<span class="submodule-label">{item.label}</span>
+			</a>
+		</li>
+	</f:for>
+	<li class="divider"></li>
+	<li class="reset-dropdown">
+		<a href="{logoutButton.href}" class="btn btn-danger pull-right" target="_top">
+			<i class="fa fa-power-off"></i> {logoutButton.label}
+		</a>
+	</li>
+</ul>
\ No newline at end of file
diff --git a/typo3/sysext/opendocs/Classes/Backend/ToolbarItems/OpendocsToolbarItem.php b/typo3/sysext/opendocs/Classes/Backend/ToolbarItems/OpendocsToolbarItem.php
index 97f307b9de78..67efa84f67c0 100644
--- a/typo3/sysext/opendocs/Classes/Backend/ToolbarItems/OpendocsToolbarItem.php
+++ b/typo3/sysext/opendocs/Classes/Backend/ToolbarItems/OpendocsToolbarItem.php
@@ -15,16 +15,27 @@ namespace TYPO3\CMS\Opendocs\Backend\ToolbarItems;
  */
 
 use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
+use TYPO3\CMS\Backend\Backend\ToolbarItems\AbstractToolbarItem;
 use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
- * Alist of all open documents
+ * A list of all open documents
  *
  * @author Benjamin Mack <benni@typo3.org>
  * @author Ingo Renner <ingo@typo3.org>
  */
-class OpendocsToolbarItem implements ToolbarItemInterface {
+class OpendocsToolbarItem extends AbstractToolbarItem implements ToolbarItemInterface {
+
+	/**
+	 * @var string Extension context
+	 */
+	protected $extension = 'opendocs';
+
+	/**
+	 * @var string Template file for the dropdown menu
+	 */
+	protected $templateFile = 'Opendocs.html';
 
 	/**
 	 * @var array
@@ -40,7 +51,8 @@ class OpendocsToolbarItem implements ToolbarItemInterface {
 	 * Constructor
 	 */
 	public function __construct() {
-		$this->getLanguageService()->includeLLFile('EXT:opendocs/Resources/Private/Language/locallang.xlf');
+		parent::__construct();
+
 		$this->loadDocsFromUserSession();
 		$pageRenderer = $this->getPageRenderer();
 		$pageRenderer->loadRequireJsModule('TYPO3/CMS/Opendocs/Toolbar/OpendocsMenu');
@@ -89,34 +101,40 @@ class OpendocsToolbarItem implements ToolbarItemInterface {
 	 * @return string HTML
 	 */
 	public function getDropDown() {
-		$languageService = $this->getLanguageService();
 		$openDocuments = $this->openDocs;
 		$recentDocuments = $this->recentDocs;
-		$entries = array();
-		if (count($openDocuments)) {
-			$entries[] = '<li class="dropdown-header">' . $languageService->getLL('open_docs', TRUE) . '</li>';
+		$entries = array(
+			'open' => array(),
+			'recent' => array()
+		);
+		if (!empty($openDocuments)) {
 			$i = 0;
 			foreach ($openDocuments as $md5sum => $openDocument) {
 				$i++;
-				$entries[] = $this->renderMenuEntry($openDocument, $md5sum, FALSE, $i == 1);
+				$entry = $this->renderMenuEntry($openDocument, $md5sum, FALSE, $i === 1);
+				if (!empty($entry)) {
+					$entries['open'][] = $entry;
+				}
 			}
-			$entries[] = '<li class="divider"></li>';
 		}
 		// If there are "recent documents" in the list, add them
-		if (count($recentDocuments)) {
-			$entries[] = '<li class="dropdown-header">' . $languageService->getLL('recent_docs', TRUE) . '</li>';
+		if (!empty($recentDocuments)) {
 			$i = 0;
 			foreach ($recentDocuments as $md5sum => $recentDocument) {
 				$i++;
-				$entries[] = $this->renderMenuEntry($recentDocument, $md5sum, TRUE, $i == 1);
+				$entry = $this->renderMenuEntry($recentDocument, $md5sum, TRUE, $i === 1);
+				if (!empty($entry)) {
+					$entries['recent'][] = $entry;
+				}
 			}
 		}
-		if (count($entries)) {
-			$content = '<ul class="dropdown-list">' . implode('', $entries) . '</ul>';
-		} else {
-			$content = '<p>' . $languageService->getLL('no_docs', TRUE) . '</p>';
-		}
-		return $content;
+
+		$standaloneView = $this->getStandaloneView();
+		$standaloneView->assignMultiple(array(
+			'entries' => $entries,
+			'hasEntries' => !empty($entries['open']) || !empty($entries['recent'])
+		));
+		return $standaloneView->render();
 	}
 
 	/**
@@ -136,7 +154,7 @@ class OpendocsToolbarItem implements ToolbarItemInterface {
 			// Record seems to be deleted
 			return '';
 		}
-		$label = htmlspecialchars(strip_tags(htmlspecialchars_decode($document[0])));
+		$label = strip_tags(htmlspecialchars_decode($document[0]));
 		$icon = \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconForRecord($table, $record);
 		$link = \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleUrl('record_edit') . '&' . $document[2];
 		$pageId = (int)$document[3]['uid'];
@@ -148,17 +166,21 @@ class OpendocsToolbarItem implements ToolbarItemInterface {
 			$title = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:rm.closeDoc', TRUE);
 			// Open document
 			$closeIcon = \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIcon('actions-document-close');
-			$entry = '
-				<li class="opendoc">
-					<a href="#" class="dropdown-list-link dropdown-link-list-add-close" onclick="' . htmlspecialchars($onClickCode) . '" target="content">' . $icon . ' ' . $label . '</a>
-					<a href="#" class="dropdown-list-link-close" data-opendocsidentifier="' . $md5sum . '" title="' . $title . '">' . $closeIcon . '</a>
-				</li>';
+			$entry = array(
+				'onclick' => $onClickCode,
+				'icon' => $icon,
+				'label' => $label,
+				'md5sum' => $md5sum,
+				'title' => $title,
+				'closeIcon' => $closeIcon
+			);
 		} else {
 			// Recently used document
-			$entry = '
-				<li>
-					<a href="#" class="dropdown-list-link" onclick="' . htmlspecialchars($onClickCode) . '" target="content">' . $icon . ' ' . $label . '</a>
-				</li>';
+			$entry = array(
+				'onclick' => $onClickCode,
+				'icon' => $icon,
+				'label' => $label
+			);
 		}
 		return $entry;
 	}
diff --git a/typo3/sysext/opendocs/Resources/Private/Templates/ToolbarMenu/Opendocs.html b/typo3/sysext/opendocs/Resources/Private/Templates/ToolbarMenu/Opendocs.html
new file mode 100644
index 000000000000..cd7398144fbb
--- /dev/null
+++ b/typo3/sysext/opendocs/Resources/Private/Templates/ToolbarMenu/Opendocs.html
@@ -0,0 +1,45 @@
+<f:if condition="{hasEntries}">
+	<f:then>
+		<ul class="dropdown-list">
+			<f:for each="{entries.open}" as="entry" iteration="iteration">
+				<f:if condition="{iteration.isFirst}">
+					<li class="dropdown-header"><f:translate key="open_docs" /></li>
+				</f:if>
+				<li class="opendoc">
+					<a href="#" class="dropdown-list-link dropdown-link-list-add-close" onclick="{entry.onclick}" target="content">
+						<f:format.raw>{entry.icon}</f:format.raw> {entry.label}
+					</a>
+					<a href="#" class="dropdown-list-link-close" data-opendocsidentifier="{entry.md5sum}" title="{entry.title}">
+						<f:format.raw>{entry.closeIcon}</f:format.raw>
+					</a>
+				</li>
+				<f:if condition="isLast">
+					<f:then></f:then>
+					<f:else>
+						<li class="divider"></li>
+					</f:else>
+				</f:if>
+			</f:for>
+
+			<f:for each="{entries.recent}" as="entry" iteration="iteration">
+				<f:if condition="{iteration.isFirst}">
+					<li class="dropdown-header"><f:translate key="recent_docs" /></li>
+				</f:if>
+				<li>
+					<a href="#" class="dropdown-list-link" onclick="{entry.onclick}" target="content">
+						<f:format.raw>{entry.icon}</f:format.raw> {entry.label}
+					</a>
+				</li>
+				<f:if condition="isLast">
+					<f:then></f:then>
+					<f:else>
+						<li class="divider"></li>
+					</f:else>
+				</f:if>
+			</f:for>
+		</ul>
+	</f:then>
+	<f:else>
+		<p><f:translate key="no_docs" /></p>
+	</f:else>
+</f:if>
diff --git a/typo3/sysext/workspaces/Classes/Backend/ToolbarItems/WorkspaceSelectorToolbarItem.php b/typo3/sysext/workspaces/Classes/Backend/ToolbarItems/WorkspaceSelectorToolbarItem.php
index 64d734312fba..fb79c3b50fcb 100644
--- a/typo3/sysext/workspaces/Classes/Backend/ToolbarItems/WorkspaceSelectorToolbarItem.php
+++ b/typo3/sysext/workspaces/Classes/Backend/ToolbarItems/WorkspaceSelectorToolbarItem.php
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Workspaces\Backend\ToolbarItems;
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
+use TYPO3\CMS\Backend\Backend\ToolbarItems\AbstractToolbarItem;
 use TYPO3\CMS\Workspaces\Service\WorkspaceService;
 use TYPO3\CMS\Backend\Utility\IconUtility;
 
@@ -24,7 +25,17 @@ use TYPO3\CMS\Backend\Utility\IconUtility;
  *
  * @author Ingo Renner <ingo@typo3.org>
  */
-class WorkspaceSelectorToolbarItem implements ToolbarItemInterface {
+class WorkspaceSelectorToolbarItem extends AbstractToolbarItem implements ToolbarItemInterface {
+
+	/**
+	 * @var string Extension context
+	 */
+	protected $extension = 'workspaces';
+
+	/**
+	 * @var string Template file for the dropdown menu
+	 */
+	protected $templateFile = 'WorkspaceSelector.html';
 
 	/**
 	 * @var array
@@ -35,7 +46,9 @@ class WorkspaceSelectorToolbarItem implements ToolbarItemInterface {
 	 * Constructor
 	 */
 	public function __construct() {
-		/** @var \TYPO3\CMS\Workspaces\Service\WorkspaceService $wsService */
+		parent::__construct();
+
+		/** @var WorkspaceService $wsService */
 		$wsService = GeneralUtility::makeInstance(WorkspaceService::class);
 		$this->availableWorkspaces = $wsService->getAvailableWorkspaces();
 
@@ -95,36 +108,40 @@ class WorkspaceSelectorToolbarItem implements ToolbarItemInterface {
 		foreach ($this->availableWorkspaces as $workspaceId => $label) {
 			$workspaceId = (int)$workspaceId;
 			$iconState = ($workspaceId === $activeWorkspace ? $stateCheckedIcon : $stateUncheckedIcon);
-			$classValue = ($workspaceId === $activeWorkspace ? ' class="selected"' : '');
+			$classValue = ($workspaceId === $activeWorkspace ? 'selected' : '');
 			$sectionName = ($index++ === 0 ? 'top' : 'items');
-			$workspaceSections[$sectionName][] = '<li' . $classValue . '>'
-				. '<a href="backend.php?changeWorkspace=' . $workspaceId . '" data-workspaceid="' . $workspaceId . '" class="dropdown-list-link tx-workspaces-switchlink">'
-				. $iconState . ' ' . htmlspecialchars($label)
-				. '</a></li>';
+			$workspaceSections[$sectionName][] = array(
+				'href' => 'backend.php?changeWorkspace=' . $workspaceId,
+				'class' => $classValue,
+				'wsid' => $workspaceId,
+				'icon' => $iconState,
+				'label' => $label
+			);
 		}
 
 		if (!empty($workspaceSections['top'])) {
 			// Add the "Go to workspace module" link
 			// if there is at least one icon on top and if the access rights are there
 			if ($backendUser->check('modules', 'web_WorkspacesWorkspaces')) {
-				$workspaceSections['top'][] = '<li><a target="content" data-module="web_WorkspacesWorkspaces" class="dropdown-list-link tx-workspaces-modulelink">'
-					. $stateUncheckedIcon . ' ' . $languageService->getLL('bookmark_workspace', TRUE)
-					. '</a></li>';
+				$workspaceSections['top'][] = array(
+					'module' => 'web_WorkspacesWorkspaces',
+					'icon' => $stateUncheckedIcon,
+					'label' => $languageService->getLL('bookmark_workspace', TRUE)
+				);
 			}
 		} else {
 			// no items on top (= no workspace to work in)
-			$workspaceSections['top'][] = '<li>' . $stateUncheckedIcon . ' ' . $languageService->getLL('bookmark_noWSfound', TRUE) . '</li>';
+			$workspaceSections['top'][] = array(
+				'icon' => $stateUncheckedIcon,
+				'label' => $languageService->getLL('bookmark_noWSfound', TRUE)
+			);
 		}
 
-		$workspaceMenu = array(
-			'<ul class="dropdown-list">' ,
-				implode(LF, $workspaceSections['top']),
-				(!empty($workspaceSections['items']) ? '<li class="divider"></li>' : ''),
-				implode(LF, $workspaceSections['items']),
-			'</ul>'
-		);
-
-		return implode(LF, $workspaceMenu);
+		$standaloneView = $this->getStandaloneView();
+		$standaloneView->assignMultiple(array(
+			'workspaceSections' => $workspaceSections
+		));
+		return $standaloneView->render();
 	}
 
 	/**
diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/ToolbarMenu/WorkspaceSelector.html b/typo3/sysext/workspaces/Resources/Private/Templates/ToolbarMenu/WorkspaceSelector.html
new file mode 100644
index 000000000000..d1b82c0bbfd0
--- /dev/null
+++ b/typo3/sysext/workspaces/Resources/Private/Templates/ToolbarMenu/WorkspaceSelector.html
@@ -0,0 +1,32 @@
+<ul class="dropdown-list">
+	<f:for each="{workspaceSections}" key="sectionKey" as="section">
+		<f:for each="{section}" as="item" iteration="iteration">
+			<f:if condition="{sectionKey} == 'items'">
+				<f:if condition="{iteration.isFirst}">
+					<li class="divider"></li>
+				</f:if>
+			</f:if>
+			<li class="{item.class}">
+				<f:if condition="{item.href}">
+					<f:then>
+						<a href="{item.href}" data-workspaceid="{item.wsid}" class="dropdown-list-link tx-workspaces-switchlink">
+							<f:format.raw>{item.icon}</f:format.raw> {item.label}
+						</a>
+					</f:then>
+					<f:else>
+						<f:if condition="{item.module}">
+							<f:then>
+								<a target="content" data-module="{item.module}" class="dropdown-list-link tx-workspaces-modulelink">
+									<f:format.raw>{item.icon}</f:format.raw> {item.label}
+								</a>
+							</f:then>
+							<f:else>
+								<f:format.raw>{item.icon}</f:format.raw> {item.label}
+							</f:else>
+						</f:if>
+					</f:else>
+				</f:if>
+			</li>
+		</f:for>
+	</f:for>
+</ul>
\ No newline at end of file
-- 
GitLab