[!!!][FEATURE] ElementBrowser refactoring, LinkBrowser API - Part 1 51/42951/19
authorMarkus Klein <markus.klein@typo3.org>
Fri, 2 Oct 2015 11:42:47 +0000 (13:42 +0200)
committerBenni Mack <benni@typo3.org>
Thu, 8 Oct 2015 21:54:23 +0000 (23:54 +0200)
This change refactors the ElementBrowser and splits it into
multiple dedicated classes for each mode.

The former modes for file, folder and record selection are now
placed in dedicated *Browser classes.
The former mode "wizard", which defines its actual functionality
via the "act" variable, is a separate controller now, which
provides the API to be exensible.

The integration of the various trees and lists is solved by
a dedicated LinkParameterProviderInterface. Any class can
provides link parameters now by implementing this interface.

Other than that:
 * Lots of unused code is removed
 * All JS is moved into requireJS modules
 * Tree code is simplified by properly using constructors

Part 2 will:
 * add Breaking and Feature documentation
 * finalize RTE re-integration
 * finally delete unused classes

Resolves: #66369
Releases: master
Change-Id: I0a28663ce4c91c2405abc9e2a13063699c6bb231
Reviewed-on: http://review.typo3.org/42951
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
70 files changed:
typo3/sysext/backend/Classes/Controller/EditDocumentController.php
typo3/sysext/backend/Classes/Controller/FileSystemNavigationFrameController.php
typo3/sysext/backend/Classes/Form/Element/AbstractFormElement.php
typo3/sysext/backend/Classes/RecordList/AbstractRecordList.php
typo3/sysext/backend/Classes/RecordList/ElementBrowserRecordList.php
typo3/sysext/backend/Classes/Tree/View/AbstractTreeView.php
typo3/sysext/backend/Classes/Tree/View/ElementBrowserFolderTreeView.php
typo3/sysext/backend/Classes/Tree/View/ElementBrowserPageTreeView.php
typo3/sysext/backend/Classes/Tree/View/FolderTreeView.php
typo3/sysext/backend/Classes/Tree/View/PagePositionMap.php
typo3/sysext/backend/Classes/View/PageLayoutView.php
typo3/sysext/backend/Classes/View/PageTreeView.php
typo3/sysext/backend/Resources/Public/JavaScript/BrowseLinks.js [deleted file]
typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js
typo3/sysext/compatibility6/Configuration/TCA/Overrides/tt_content.php
typo3/sysext/core/Classes/ElementBrowser/ElementBrowserHookInterface.php [deleted file]
typo3/sysext/core/Classes/Migrations/TcaMigration.php
typo3/sysext/core/Configuration/TCA/sys_file_reference.php
typo3/sysext/core/Documentation/Changelog/master/Feature-66369-AddedLinkBrowserAPIs.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Migrations/TcaMigrationTest.php
typo3/sysext/filelist/Classes/FileList.php
typo3/sysext/frontend/Configuration/TCA/tt_content.php
typo3/sysext/impexp/Classes/View/ExportPageTreeView.php
typo3/sysext/impexp/Tests/Functional/Fixtures/Extensions/impexp_group_files/Configuration/TCA/tx_impexpgroupfiles_item.php
typo3/sysext/mediace/Configuration/FlexForms/media.xml
typo3/sysext/recordlist/Classes/Browser/AbstractElementBrowser.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/Browser/DatabaseBrowser.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/Browser/ElementBrowser.php
typo3/sysext/recordlist/Classes/Browser/ElementBrowserInterface.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/Browser/FileBrowser.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/Browser/FolderBrowser.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/Controller/ElementBrowserController.php
typo3/sysext/recordlist/Classes/Controller/LinkBrowserController.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/LinkHandler/AbstractLinkHandler.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/LinkHandler/FileLinkHandler.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/LinkHandler/FolderLinkHandler.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/LinkHandler/LinkHandlerInterface.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/LinkHandler/MailLinkHandler.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/LinkHandler/PageLinkHandler.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/LinkHandler/UrlLinkHandler.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php
typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php
typo3/sysext/recordlist/Classes/Tree/View/DummyLinkParameterProvider.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/Tree/View/ElementBrowserPageTreeView.php
typo3/sysext/recordlist/Classes/Tree/View/LinkParameterProviderInterface.php [new file with mode: 0644]
typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php [new file with mode: 0644]
typo3/sysext/recordlist/Configuration/Backend/AjaxRoutes.php [new file with mode: 0644]
typo3/sysext/recordlist/Configuration/Backend/Routes.php
typo3/sysext/recordlist/Resources/Public/JavaScript/BrowseDatabase.js [new file with mode: 0644]
typo3/sysext/recordlist/Resources/Public/JavaScript/BrowseFiles.js [new file with mode: 0644]
typo3/sysext/recordlist/Resources/Public/JavaScript/BrowseFolders.js [new file with mode: 0644]
typo3/sysext/recordlist/Resources/Public/JavaScript/ElementBrowser.js [new file with mode: 0644]
typo3/sysext/recordlist/Resources/Public/JavaScript/FieldSelectBox.js
typo3/sysext/recordlist/Resources/Public/JavaScript/FileLinkHandler.js [new file with mode: 0644]
typo3/sysext/recordlist/Resources/Public/JavaScript/LinkBrowser.js [new file with mode: 0644]
typo3/sysext/recordlist/Resources/Public/JavaScript/MailLinkHandler.js [new file with mode: 0644]
typo3/sysext/recordlist/Resources/Public/JavaScript/PageLinkHandler.js [new file with mode: 0644]
typo3/sysext/recordlist/Resources/Public/JavaScript/Recordlist.js
typo3/sysext/recordlist/Resources/Public/JavaScript/Tooltip.js
typo3/sysext/recordlist/Resources/Public/JavaScript/UrlLinkHandler.js [new file with mode: 0644]
typo3/sysext/recordlist/ext_tables.php
typo3/sysext/rtehtmlarea/Classes/BrowseLinks.php
typo3/sysext/rtehtmlarea/Classes/Controller/BrowseLinksController.php
typo3/sysext/rtehtmlarea/Classes/FolderTree.php
typo3/sysext/rtehtmlarea/Classes/LinkHandler/RemoveLinkHandler.php [new file with mode: 0644]
typo3/sysext/rtehtmlarea/Classes/PageTree.php
typo3/sysext/rtehtmlarea/Classes/SelectImage.php
typo3/sysext/rtehtmlarea/Documentation/Configuration/PageTsconfig/interfaceConfiguration/Index.rst
typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/RteLinkBrowser.js [new file with mode: 0644]
typo3/sysext/setup/Classes/Controller/SetupModuleController.php

index 15adce1..7dafc2a 100644 (file)
@@ -1275,7 +1275,7 @@ class EditDocumentController {
                if ($this->returnUrl === 'sysext/backend/Resources/Private/Templates/Close.html') {
                        return '';
                }
-               $aOnClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript(array('returnUrl' => 'sysext/backend/Resources/Private/Templates/Close.html'))) . ',' . GeneralUtility::quoteJSvalue(md5($this->R_URI)) . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
+               $aOnClick = 'var vHWin=window.open(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript(array('returnUrl' => 'sysext/backend/Resources/Private/Templates/Close.html'))) . ',' . GeneralUtility::quoteJSvalue(md5($this->R_URI)) . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
                return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow', TRUE) . '">' . $this->iconFactory->getIcon('actions-window-open', Icon::SIZE_SMALL)->render() . '</a>';
        }
 
index 16c3a56..c2c8e0b 100644 (file)
@@ -23,7 +23,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Filelist\FileListFolderTree;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Recordlist\Browser\ElementBrowser;
+use TYPO3\CMS\Recordlist\Tree\View\DummyLinkParameterProvider;
 
 /**
  * Main script class for rendering of the folder tree
@@ -113,10 +113,13 @@ class FileSystemNavigationFrameController {
                        $this->foldertree->thisScript = $this->scopeData['script'];
                        $this->foldertree->ext_noTempRecyclerDirs = $this->scopeData['ext_noTempRecyclerDirs'];
                        if ($this->foldertree instanceof ElementBrowserFolderTreeView) {
-                               $browser = GeneralUtility::makeInstance(ElementBrowser::class);
-                               $browser->mode = $this->scopeData['browser']['mode'];
-                               $browser->act = $this->scopeData['browser']['act'];
-                               $this->foldertree->setElementBrowser($browser);
+                               // create a fake provider to pass link data along properly
+                               $linkParamProvider = GeneralUtility::makeInstance(DummyLinkParameterProvider::class,
+                                       $this->scopeData['browser']['mode'],
+                                       $this->scopeData['browser']['act'],
+                                       $this->scopeData['script']
+                               );
+                               $this->foldertree->setLinkParameterProvider($linkParamProvider);
                        }
                } else {
                        $this->foldertree = GeneralUtility::makeInstance(FileListFolderTree::class);
index ff97137..04c56fd 100644 (file)
@@ -295,7 +295,7 @@ abstract class AbstractFormElement extends AbstractNode {
                                                'this.blur();' .
                                                $onlyIfSelectedJS .
                                                'vHWin=window.open(' . GeneralUtility::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' .
-                                                               'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value,200' .
+                                                               'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value,300' .
                                                        ')' .
                                                        '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName) . '),' .
                                                        GeneralUtility::quoteJSvalue('popUp' . $md5ID) . ',' .
@@ -340,7 +340,7 @@ abstract class AbstractFormElement extends AbstractNode {
                                        $aOnClick =
                                                'this.blur();' .
                                                'vHWin=window.open('. GeneralUtility::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' .
-                                                       'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value,200' .
+                                                       'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value,300' .
                                                        ')' .
                                                        '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName) . '),' .
                                                        GeneralUtility::quoteJSvalue('popUp' . $md5ID) . ',' .
index bd2b0ba..8a95f4f 100644 (file)
@@ -14,7 +14,11 @@ namespace TYPO3\CMS\Backend\RecordList;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
+use TYPO3\CMS\Backend\Routing\Router;
+use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Database\DatabaseConnection;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -32,6 +36,11 @@ use TYPO3\CMS\Lang\LanguageService;
 abstract class AbstractRecordList {
 
        /**
+        * @var int
+        */
+       protected $id = 0;
+
+       /**
         * default Max items shown
         *
         * @var int
@@ -153,7 +162,7 @@ abstract class AbstractRecordList {
        public $languageIconTitles = array();
 
        /**
-        * @var \TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider
+        * @var TranslationConfigurationProvider
         */
        public $translateTools;
 
@@ -165,16 +174,22 @@ abstract class AbstractRecordList {
                        $this->fixedL = $GLOBALS['BE_USER']->uc['titleLen'];
                }
                $this->getTranslateTools();
+               $this->determineScriptUrl();
        }
 
        /**
         * Sets the script url depending on being a module or script request
         */
        protected function determineScriptUrl() {
-               if ($moduleName = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('M')) {
-                       $this->thisScript = \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleUrl($moduleName);
+               if ($routePath = GeneralUtility::_GP('route')) {
+                       $router = GeneralUtility::makeInstance(Router::class);
+                       $route = $router->match($routePath);
+                       $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+                       $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
+               } elseif ($moduleName = GeneralUtility::_GP('M')) {
+                       $this->thisScript = BackendUtility::getModuleUrl($moduleName);
                } else {
-                       $this->thisScript = \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('SCRIPT_NAME');
+                       $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
                }
        }
 
@@ -193,12 +208,12 @@ abstract class AbstractRecordList {
         * @param string $icon Is the <img>+<a> of the record. If not supplied the first 'join'-icon will be a 'line' instead
         * @param array $data Is the dataarray, record with the fields. Notice: These fields are (currently) NOT htmlspecialchar'ed before being wrapped in <td>-tags
         * @param string $rowParams Is insert in the <tr>-tags. Must carry a ' ' as first character
-        * @param int OBSOLETE - NOT USED ANYMORE. $lMargin is the leftMargin (int)
-        * @param string $altLine Is the HTML <img>-tag for an alternative 'gfx/ol/line.gif'-icon (used in the top)
+        * @param string $_ OBSOLETE - NOT USED ANYMORE. $lMargin is the leftMargin (int)
+        * @param string $_ OBSOLETE - NOT USED ANYMORE. Is the HTML <img>-tag for an alternative 'gfx/ol/line.gif'-icon (used in the top)
         * @param string $colType Defines the tag being used for the columns. Default is td.
         * @return string HTML content for the table row
         */
-       public function addElement($h, $icon, $data, $rowParams = '', $lMargin = '', $altLine = '', $colType = 'td') {
+       public function addElement($h, $icon, $data, $rowParams = '', $_ = '', $_ = '', $colType = 'td') {
                $colType = ($colType === 'th') ? 'th' : 'td';
                $noWrap = $this->no_noWrap ? '' : ' nowrap="nowrap"';
                // Start up:
@@ -397,7 +412,7 @@ abstract class AbstractRecordList {
         */
        public function initializeLanguages() {
                // Look up page overlays:
-               $this->pageOverlays = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'pages_language_overlay', 'pid=' . (int)$this->id . BackendUtility::deleteClause('pages_language_overlay') . BackendUtility::versioningPlaceholderClause('pages_language_overlay'), '', '', '', 'sys_language_uid');
+               $this->pageOverlays = $this->getDatabaseConnection()->exec_SELECTgetRows('*', 'pages_language_overlay', 'pid=' . (int)$this->id . BackendUtility::deleteClause('pages_language_overlay') . BackendUtility::versioningPlaceholderClause('pages_language_overlay'), '', '', '', 'sys_language_uid');
                $this->languageIconTitles = $this->getTranslateTools()->getSystemLanguages($this->id);
        }
 
@@ -426,11 +441,11 @@ abstract class AbstractRecordList {
        /**
         * Gets an instance of TranslationConfigurationProvider
         *
-        * @return \TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider
+        * @return TranslationConfigurationProvider
         */
        protected function getTranslateTools() {
                if (!isset($this->translateTools)) {
-                       $this->translateTools = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider::class);
+                       $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
                }
                return $this->translateTools;
        }
@@ -466,4 +481,13 @@ abstract class AbstractRecordList {
                return $GLOBALS['LANG'];
        }
 
+       /**
+        * Returns the database connection
+        *
+        * @return DatabaseConnection
+        */
+       protected function getDatabaseConnection() {
+               return $GLOBALS['TYPO3_DB'];
+       }
+
 }
index b3b4ca7..92128a4 100644 (file)
@@ -17,13 +17,10 @@ namespace TYPO3\CMS\Backend\RecordList;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Recordlist\Browser\ElementBrowser;
 use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList;
 
 /**
- * Displays the page/file tree for browsing database records or files.
- * Used from TCEFORMS an other elements
- * In other words: This is the ELEMENT BROWSER!
+ * Displays the page tree for browsing database records.
  */
 class ElementBrowserRecordList extends DatabaseRecordList {
 
@@ -42,57 +39,6 @@ class ElementBrowserRecordList extends DatabaseRecordList {
        protected $relatingField;
 
        /**
-        * Back-reference to ElementBrowser class
-        *
-        * @var ElementBrowser
-        */
-       protected $elementBrowser;
-
-       /**
-        * Initializes the script path
-        */
-       public function __construct() {
-               parent::__construct();
-               $this->determineScriptUrl();
-       }
-
-       /**
-        * @param ElementBrowser $elementBrowser
-        * @return void
-        */
-       public function setElementBrowser(ElementBrowser $elementBrowser) {
-               $this->elementBrowser = $elementBrowser;
-       }
-
-       /**
-        * Creates the URL for links
-        *
-        * @param mixed $altId If not blank string, this is used instead of $this->id as the id value.
-        * @param string $table If this is "-1" then $this->table is used, otherwise the value of the input variable.
-        * @param string $exclList Commalist of fields NOT to pass as parameters (currently "sortField" and "sortRev")
-        * @return string Query-string for URL
-        */
-       public function listURL($altId = '', $table = '-1', $exclList = '') {
-               return $this->getThisScript() . 'id=' . ($altId !== '' ? $altId : $this->id)
-                       . '&table=' . rawurlencode((int)$table === -1 ? $this->table : $table)
-                       . ($this->thumbs ? '&imagemode=' . $this->thumbs : '')
-                       . ($this->searchString ? '&search_field=' . rawurlencode($this->searchString) : '')
-                       . ($this->searchLevels ? '&search_levels=' . rawurlencode($this->searchLevels) : '')
-                       . ((!$exclList || !GeneralUtility::inList($exclList, 'sortField')) && $this->sortField ? '&sortField=' . rawurlencode($this->sortField) : '')
-                       . ((!$exclList || !GeneralUtility::inList($exclList, 'sortRev')) && $this->sortRev ? '&sortRev=' . rawurlencode($this->sortRev) : '')
-                       . $this->ext_addP();
-       }
-
-       /**
-        * Returns additional, local GET parameters to include in the links of the record list.
-        *
-        * @return string
-        */
-       public function ext_addP() {
-               return '&act=' . $this->elementBrowser->act . '&mode=' . $this->elementBrowser->mode . '&expandPage=' . $this->elementBrowser->expandPage . '&bparams=' . rawurlencode($this->elementBrowser->bparams);
-       }
-
-       /**
         * Returns the title (based on $code) of a record (from table $table) with the proper link around (that is for "pages"-records a link to the level of that record...)
         *
         * @param string $table Table name
@@ -109,11 +55,15 @@ class ElementBrowserRecordList extends DatabaseRecordList {
                }
                $title = BackendUtility::getRecordTitle($table, $row, FALSE, TRUE);
                $ficon = $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render();
-               $aOnClick = 'return insertElement(' . GeneralUtility::quoteJSvalue($table) . ', ' . GeneralUtility::quoteJSvalue($row['uid']) . ', \'db\', ' . GeneralUtility::quoteJSvalue($title) . ', \'\', \'\', ' . GeneralUtility::quoteJSvalue($ficon) . ');';
-               $ATag = '<a href="#" onclick="' . $aOnClick . '" title="' . $this->getLanguageService()->getLL('addToList', TRUE) . '">';
-               $ATag_alt = substr($ATag, 0, -4) . ',\'\',1);">';
+
+               $ATag = '<a href="#" data-close="0" title="' . $this->getLanguageService()->getLL('addToList', TRUE) . '">';
+               $ATag_alt = '<a href="#" data-close="1" title="' . $this->getLanguageService()->getLL('addToList', TRUE) . '">';
                $ATag_e = '</a>';
-               return $ATag . $this->iconFactory->getIcon('actions-edit-add', Icon::SIZE_SMALL)->render() . $ATag_e . $ATag_alt . $code . $ATag_e;
+               $out = '<span data-uid="' . htmlspecialchars($row['uid']) . '" data-table="' . htmlspecialchars($table) . '" data-title="' . htmlspecialchars($title) . '" data-icon="' . htmlspecialchars($ficon) . '">';
+               $out .= $ATag . $this->iconFactory->getIcon('actions-edit-add', Icon::SIZE_SMALL)->render() . $ATag_e . $ATag_alt . $code . $ATag_e;
+               $out .= '</span>';
+
+               return $out;
        }
 
        /**
index de1585b..c9152fe 100644 (file)
@@ -14,10 +14,15 @@ namespace TYPO3\CMS\Backend\Tree\View;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Routing\Router;
+use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\DatabaseConnection;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Lang\LanguageService;
 
 /**
  * Base class for creating a browsable array/page/folder tree in HTML
@@ -263,13 +268,25 @@ abstract class AbstractTreeView {
        public $recs = array();
 
        /**
+        * Constructor
+        */
+       public function __construct() {
+               $this->determineScriptUrl();
+       }
+
+       /**
         * Sets the script url depending on being a module or script request
         */
        protected function determineScriptUrl() {
-               if ($moduleName = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('M')) {
-                       $this->thisScript = \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleUrl($moduleName);
+               if ($routePath = GeneralUtility::_GP('route')) {
+                       $router = GeneralUtility::makeInstance(Router::class);
+                       $route = $router->match($routePath);
+                       $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+                       $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
+               } elseif ($moduleName = GeneralUtility::_GP('M')) {
+                       $this->thisScript = BackendUtility::getModuleUrl($moduleName);
                } else {
-                       $this->thisScript = \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('SCRIPT_NAME');
+                       $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
                }
        }
 
@@ -283,8 +300,8 @@ abstract class AbstractTreeView {
        /**
         * Initialize the tree class. Needs to be overwritten
         *
-        * @param string Record WHERE clause
-        * @param string Record ORDER BY field
+        * @param string $clause Record WHERE clause
+        * @param string $orderByFields Record ORDER BY field
         * @return void
         */
        public function init($clause = '', $orderByFields = '') {
@@ -315,7 +332,7 @@ abstract class AbstractTreeView {
         * @param bool $noCheck If set, the fieldname will be set no matter what. Otherwise the field name must either be found as key in $GLOBALS['TCA'][$table]['columns'] or in the list ->defaultList
         * @return void
         */
-       public function addField($field, $noCheck = 0) {
+       public function addField($field, $noCheck = FALSE) {
                if ($noCheck || is_array($GLOBALS['TCA'][$this->table]['columns'][$field]) || GeneralUtility::inList($this->defaultList, $field)) {
                        $this->fieldArray[] = $field;
                }
@@ -369,7 +386,7 @@ abstract class AbstractTreeView {
                                $firstHtml .= $this->getIcon($rootRec);
                        } else {
                                // Artificial record for the tree root, id=0
-                               $rootRec = $this->getRootRecord($uid);
+                               $rootRec = $this->getRootRecord();
                                $firstHtml .= $this->getRootIcon($rootRec);
                        }
                        if (is_array($rootRec)) {
@@ -395,7 +412,7 @@ abstract class AbstractTreeView {
        /**
         * Compiles the HTML code for displaying the structure found inside the ->tree array
         *
-        * @param array $treeArr "tree-array" - if blank string, the internal ->tree array is used.
+        * @param array|string $treeArr "tree-array" - if blank string, the internal ->tree array is used.
         * @return string The HTML code for the tree
         */
        public function printTree($treeArr = '') {
@@ -647,7 +664,7 @@ abstract class AbstractTreeView {
         */
        public function getTitleStr($row, $titleLen = 30) {
                $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLen));
-               $title = trim($row['title']) === '' ? '<em>[' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title', TRUE) . ']</em>' : $title;
+               $title = trim($row['title']) === '' ? '<em>[' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title', TRUE) . ']</em>' : $title;
                return $title;
        }
 
@@ -693,11 +710,10 @@ abstract class AbstractTreeView {
         * @param int $uid item id for which to select subitems (parent id)
         * @param int $depth Max depth (recursivity limit)
         * @param string $depthData HTML-code prefix for recursive calls.
-        * @param string $blankLineCode ? (internal)
-        * @param string $subCSSclass CSS class to use for <td> sub-elements
+
         * @return int The count of items on the level
         */
-       public function getTree($uid, $depth = 999, $depthData = '', $blankLineCode = '', $subCSSclass = '') {
+       public function getTree($uid, $depth = 999, $depthData = '') {
                // Buffer for id hierarchy is reset:
                $this->buffer_idH = array();
                // Init vars
@@ -709,9 +725,9 @@ abstract class AbstractTreeView {
                $crazyRecursionLimiter = 999;
                $idH = array();
                // Traverse the records:
-               while ($crazyRecursionLimiter > 0 && ($row = $this->getDataNext($res, ''))) {
+               while ($crazyRecursionLimiter > 0 && ($row = $this->getDataNext($res))) {
                        $pageUid = ($this->table === 'pages') ? $row['uid'] : $row['pid'];
-                       if (!$GLOBALS['BE_USER']->isInWebMount($pageUid)) {
+                       if (!$this->getBackendUser()->isInWebMount($pageUid)) {
                                // Current record is not within web mount => skip it
                                continue;
                        }
@@ -791,17 +807,18 @@ abstract class AbstractTreeView {
                        $res = $this->getDataInit($uid);
                        return $this->getDataCount($res);
                } else {
-                       return $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $this->table, $this->parentField . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($uid, $this->table) . BackendUtility::deleteClause($this->table) . BackendUtility::versioningPlaceholderClause($this->table) . $this->clause);
+                       $db = $this->getDatabaseConnection();
+                       $where = $this->parentField . '=' . $db->fullQuoteStr($uid, $this->table) . BackendUtility::deleteClause($this->table) . BackendUtility::versioningPlaceholderClause($this->table) . $this->clause;
+                       return $db->exec_SELECTcountRows('uid', $this->table, $where);
                }
        }
 
        /**
         * Returns root record for uid (<=0)
         *
-        * @param int $uid uid, <= 0 (normally, this does not matter)
         * @return array Array with title/uid keys with values of $this->title/0 (zero)
         */
-       public function getRootRecord($uid) {
+       public function getRootRecord() {
                return array('title' => $this->title, 'uid' => 0);
        }
 
@@ -827,11 +844,11 @@ abstract class AbstractTreeView {
         * For arrays: This will return key to the ->dataLookup array
         *
         * @param int $parentId parent item id
-        * @param string $subCSSclass Class for sub-elements.
+        *
         * @return mixed Data handle (Tables: An sql-resource, arrays: A parentId integer. -1 is returned if there were NO subLevel.)
         * @access private
         */
-       public function getDataInit($parentId, $subCSSclass = '') {
+       public function getDataInit($parentId) {
                if (is_array($this->data)) {
                        if (!is_array($this->dataLookup[$parentId][$this->subLevelID])) {
                                $parentId = -1;
@@ -840,7 +857,9 @@ abstract class AbstractTreeView {
                        }
                        return $parentId;
                } else {
-                       return $GLOBALS['TYPO3_DB']->exec_SELECTquery(implode(',', $this->fieldArray), $this->table, $this->parentField . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($parentId, $this->table) . BackendUtility::deleteClause($this->table) . BackendUtility::versioningPlaceholderClause($this->table) . $this->clause, '', $this->orderByFields);
+                       $db = $this->getDatabaseConnection();
+                       $where = $this->parentField . '=' . $db->fullQuoteStr($parentId, $this->table) . BackendUtility::deleteClause($this->table) . BackendUtility::versioningPlaceholderClause($this->table) . $this->clause;
+                       return $db->exec_SELECTquery(implode(',', $this->fieldArray), $this->table, $where, '', $this->orderByFields);
                }
        }
 
@@ -856,7 +875,7 @@ abstract class AbstractTreeView {
                if (is_array($this->data)) {
                        return count($this->dataLookup[$res][$this->subLevelID]);
                } else {
-                       return $GLOBALS['TYPO3_DB']->sql_num_rows($res);
+                       return $this->getDatabaseConnection()->sql_num_rows($res);
                }
        }
 
@@ -864,12 +883,12 @@ abstract class AbstractTreeView {
         * Getting the tree data: next entry
         *
         * @param mixed $res Data handle
-        * @param string $subCSSclass CSS class for sub elements (workspace related)
+        *
         * @return array item data array OR FALSE if end of elements.
         * @access private
         * @see getDataInit()
         */
-       public function getDataNext(&$res, $subCSSclass = '') {
+       public function getDataNext(&$res) {
                if (is_array($this->data)) {
                        if ($res < 0) {
                                $row = FALSE;
@@ -878,7 +897,7 @@ abstract class AbstractTreeView {
                        }
                        return $row;
                } else {
-                       while ($row = @$GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
+                       while ($row = @$this->getDatabaseConnection()->sql_fetch_assoc($res)) {
                                BackendUtility::workspaceOL($this->table, $row, $this->BE_USER->workspace, TRUE);
                                if (is_array($row)) {
                                        break;
@@ -897,7 +916,7 @@ abstract class AbstractTreeView {
         */
        public function getDataFree(&$res) {
                if (!is_array($this->data)) {
-                       $GLOBALS['TYPO3_DB']->sql_free_result($res);
+                       $this->getDatabaseConnection()->sql_free_result($res);
                }
        }
 
@@ -943,4 +962,25 @@ abstract class AbstractTreeView {
                $this->dataLookup = &$treeLookupArr;
        }
 
+       /**
+        * @return LanguageService
+        */
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
+       /**
+        * @return BackendUserAuthentication
+        */
+       protected function getBackendUser() {
+               return $GLOBALS['BE_USER'];
+       }
+
+       /**
+        * @return DatabaseConnection
+        */
+       protected function getDatabaseConnection() {
+               return $GLOBALS['TYPO3_DB'];
+       }
+
 }
index c7cadd4..6df5821 100644 (file)
@@ -13,9 +13,10 @@ namespace TYPO3\CMS\Backend\Tree\View;
  *
  * The TYPO3 project - inspiring people to share!
  */
+
 use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Recordlist\Browser\ElementBrowser;
+use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
 
 /**
  * Base extension class which generates the folder tree.
@@ -35,24 +36,18 @@ class ElementBrowserFolderTreeView extends FolderTreeView {
        /**
         * Back-reference to ElementBrowser class
         *
-        * @var ElementBrowser
-        */
-       protected $elementBrowser;
-
-       /**
-        * Initializes the script path
+        * @var LinkParameterProviderInterface
         */
-       public function __construct() {
-               $this->determineScriptUrl();
-               parent::__construct();
-       }
+       protected $linkParameterProvider;
 
        /**
-        * @param ElementBrowser $elementBrowser
+        * @param LinkParameterProviderInterface $linkParameterProvider
+        *
         * @return void
         */
-       public function setElementBrowser(ElementBrowser $elementBrowser) {
-               $this->elementBrowser = $elementBrowser;
+       public function setLinkParameterProvider(LinkParameterProviderInterface $linkParameterProvider) {
+               $this->linkParameterProvider = $linkParameterProvider;
+               $this->thisScript = $linkParameterProvider->getScriptUrl();
        }
 
        /**
@@ -64,8 +59,8 @@ class ElementBrowserFolderTreeView extends FolderTreeView {
         */
        public function wrapTitle($title, Folder $folderObject) {
                if ($this->ext_isLinkable($folderObject)) {
-                       $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'act=' . $this->elementBrowser->act . '&mode=' . $this->elementBrowser->mode . '&expandFolder=' . rawurlencode($folderObject->getCombinedIdentifier())) . ');';
-                       return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $title . '</a>';
+                       $parameters = GeneralUtility::implodeArrayForUrl('', $this->linkParameterProvider->getUrlParameters(['identifier' => $folderObject->getCombinedIdentifier()]));
+                       return '<a href="#" onclick="return jumpToUrl(' . htmlspecialchars(GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim($parameters, '&'))) . ');">' . $title . '</a>';
                } else {
                        return '<span class="text-muted">' . $title . '</span>';
                }
@@ -78,11 +73,20 @@ class ElementBrowserFolderTreeView extends FolderTreeView {
         * @return bool TRUE is returned if the path is found in the web-part of the server and is NOT a recycler or temp folder AND if ->ext_noTempRecyclerDirs is not set.
         */
        public function ext_isLinkable(Folder $folderObject) {
-               if ($this->ext_noTempRecyclerDirs && (substr($folderObject->getIdentifier(), -7) === '_temp_/' || substr($folderObject->getIdentifier(), -11) === '_recycler_/')) {
-                       return FALSE;
-               } else {
-                       return TRUE;
+               $identifier = $folderObject->getIdentifier();
+               return !$this->ext_noTempRecyclerDirs || substr($identifier, -7) !== '_temp_/' && substr($identifier, -11) !== '_recycler_/';
+       }
+
+       /**
+        * @param string $cmd
+        * @param bool $isOpen
+        * @return string
+        */
+       protected function renderPMIconAndLink($cmd, $isOpen) {
+               if (get_class($this) === __CLASS__) {
+                       return $this->PMiconATagWrap('', $cmd, !$isOpen);
                }
+               return parent::renderPMIconAndLink($cmd, $isOpen);
        }
 
        /**
@@ -96,12 +100,11 @@ class ElementBrowserFolderTreeView extends FolderTreeView {
         * @access private
         */
        public function PM_ATagWrap($icon, $cmd, $bMark = '', $isOpen = FALSE) {
-               $name = $anchor = '';
-               if ($bMark) {
-                       $anchor = '#' . $bMark;
-                       $name = ' name="' . $bMark . '"';
-               }
-               $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'PM=' . $cmd) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');';
+               $anchor = $bMark ? '#' . $bMark : '';
+               $name = $bMark ? ' name=' . $bMark : '';
+               $urlParameters = $this->linkParameterProvider->getUrlParameters([]);
+               $urlParameters['PM'] = $cmd;
+               $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&')) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');';
                return '<a href="#"' . htmlspecialchars($name) . ' onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
        }
 
@@ -120,10 +123,7 @@ class ElementBrowserFolderTreeView extends FolderTreeView {
                                'class' => get_class($this),
                                'script' => $this->thisScript,
                                'ext_noTempRecyclerDirs' => $this->ext_noTempRecyclerDirs,
-                               'browser' => array(
-                                       'mode' => $this->elementBrowser->mode,
-                                       'act' => $this->elementBrowser->act,
-                               ),
+                               'browser' => $this->linkParameterProvider->getUrlParameters([]),
                        );
                }
 
index accd0ba..654bd42 100644 (file)
@@ -13,15 +13,15 @@ namespace TYPO3\CMS\Backend\Tree\View;
  *
  * The TYPO3 project - inspiring people to share!
  */
+
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Page\PageRepository;
-use TYPO3\CMS\Recordlist\Browser\ElementBrowser;
+use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
 
 /**
- * Class which generates the page tree
+ * Class which generates the selectable page tree
  *
- * Browsable tree, used in PagePositionMaps (move elements), Element Browser and RTE (for which it will be extended)
- * previously located inside typo3/class.browse_links.php
+ * Browsable tree, used in PagePositionMaps (move elements), the Link Wizard and the Database Browser (for which it will be extended)
  */
 class ElementBrowserPageTreeView extends BrowseTreeView {
 
@@ -46,25 +46,27 @@ class ElementBrowserPageTreeView extends BrowseTreeView {
        /**
         * Back-reference to ElementBrowser class
         *
-        * @var ElementBrowser
+        * @var LinkParameterProviderInterface
         */
-       protected $elementBrowser;
+       protected $linkParameterProvider;
 
        /**
         * Constructor. Just calling init()
         */
        public function __construct() {
-               $this->determineScriptUrl();
+               parent::__construct();
                $this->init();
-               $this->clause = ' AND doktype != ' . PageRepository::DOKTYPE_RECYCLER . $this->clause;
+               $this->clause = ' AND doktype <> ' . PageRepository::DOKTYPE_RECYCLER . $this->clause;
        }
 
        /**
-        * @param ElementBrowser $elementBrowser
+        * @param LinkParameterProviderInterface $linkParameterProvider
+        *
         * @return void
         */
-       public function setElementBrowser(ElementBrowser $elementBrowser) {
-               $this->elementBrowser = $elementBrowser;
+       public function setLinkParameterProvider(LinkParameterProviderInterface $linkParameterProvider) {
+               $this->linkParameterProvider = $linkParameterProvider;
+               $this->thisScript = $linkParameterProvider->getScriptUrl();
        }
 
        /**
@@ -77,8 +79,7 @@ class ElementBrowserPageTreeView extends BrowseTreeView {
         */
        public function wrapTitle($title, $v, $ext_pArrPages = FALSE) {
                if ($this->ext_isLinkable($v['doktype'], $v['uid'])) {
-                       $aOnClick = 'return link_typo3Page(' . GeneralUtility::quoteJSvalue($v['uid']) . ');';
-                       return '<span class="list-tree-title"><a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $title . '</a></span>';
+                       return '<span class="list-tree-title"><a href="#" class="t3-js-pageLink" data-id="' . (int)$v['uid'] . '">' . $title . '</a></span>';
                } else {
                        return '<span class="list-tree-title text-muted">' . $title . '</span>';
                }
@@ -111,11 +112,12 @@ class ElementBrowserPageTreeView extends BrowseTreeView {
                        }
 
                        $selected = '';
-                       if ($this->elementBrowser->curUrlInfo['act'] == 'page' && $this->elementBrowser->curUrlInfo['pageid'] == $treeItem['row']['uid'] && $this->elementBrowser->curUrlInfo['pageid']) {
+                       if ($this->linkParameterProvider->isCurrentlySelectedItem(['pid' => (int)$treeItem['row']['uid']])) {
                                $selected = ' bg-success';
                                $classAttr .= ' active';
                        }
-                       $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'act=' . $this->elementBrowser->act . '&mode=' . $this->elementBrowser->mode . '&expandPage=' . $treeItem['row']['uid']) . ');';
+                       $urlParameters = $this->linkParameterProvider->getUrlParameters(['pid' => (int)$treeItem['row']['uid']]);
+                       $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&')) . ');';
                        $cEbullet = $this->ext_isLinkable($treeItem['row']['doktype'], $treeItem['row']['uid']) ? '<a href="#" class="list-tree-show" onclick="' . htmlspecialchars($aOnClick) . '"><i class="fa fa-caret-square-o-right"></i></a>' : '';
                        $out .= '
                                <li' . ($classAttr ? ' class="' . trim($classAttr) . '"' : '') . '>
@@ -169,7 +171,9 @@ class ElementBrowserPageTreeView extends BrowseTreeView {
        public function PM_ATagWrap($icon, $cmd, $bMark = '', $isOpen = FALSE) {
                $anchor = $bMark ? '#' . $bMark : '';
                $name = $bMark ? ' name=' . $bMark : '';
-               $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'PM=' . $cmd) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');';
+               $urlParameters = $this->linkParameterProvider->getUrlParameters([]);
+               $urlParameters['PM'] = $cmd;
+               $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&')) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');';
                return '<a class="list-tree-control ' . ($isOpen ? 'list-tree-control-open' : 'list-tree-control-closed')
                        . '" href="#"' . htmlspecialchars($name) . ' onclick="' . htmlspecialchars($aOnClick) . '"><i class="fa"></i></a>';
        }
index 63ecf5b..78b3e66 100644 (file)
@@ -63,7 +63,7 @@ class FolderTreeView extends AbstractTreeView {
         * If file-drag mode is set, temp and recycler folders are filtered out.
         * @var bool
         */
-       public $ext_noTempRecyclerDirs;
+       public $ext_noTempRecyclerDirs = FALSE;
 
        /**
         * override to not use a title attribute
@@ -88,7 +88,8 @@ class FolderTreeView extends AbstractTreeView {
         * Constructor function of the class
         */
        public function __construct() {
-               parent::init();
+               parent::__construct();
+               $this->init();
                $this->storages = $this->BE_USER->getFileStorages();
                $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
        }
@@ -124,7 +125,6 @@ class FolderTreeView extends AbstractTreeView {
         * @internal
         */
        public function PMiconATagWrap($icon, $cmd, $isExpand = TRUE) {
-
                if (empty($this->scope)) {
                        $this->scope = array(
                                'class' => get_class($this),
@@ -145,6 +145,16 @@ class FolderTreeView extends AbstractTreeView {
        }
 
        /**
+        * @param string $cmd
+        * @param bool $isOpen
+        * @return string
+        */
+       protected function renderPMIconAndLink($cmd, $isOpen) {
+               $link = $this->thisScript ? ' href="' . htmlspecialchars($this->getThisScript() . 'PM=' . $cmd) . '"' : '';
+               return '<a class="list-tree-control list-tree-control-' . ($isOpen ? 'open' : 'closed') . '"' . $link . '><i class="fa"></i></a>';
+       }
+
+       /**
         * Wrapping the folder icon
         *
         * @param string $icon The image tag for the icon
@@ -289,19 +299,11 @@ class FolderTreeView extends AbstractTreeView {
                        $isOpen = $this->stored[$storageHashNumber][$folderHashSpecUID] || $this->expandFirst;
                        // Set PM icon:
                        $cmd = $this->generateExpandCollapseParameter($this->bank, !$isOpen, $rootLevelFolder);
+                       // Only show and link icon if storage is browseable
                        if (!$storageObject->isBrowsable() || $this->getNumberOfSubfolders($rootLevelFolder) === 0) {
                                $firstHtml = '';
                        } else {
-                               // Only show and link icon if storage is browseable
-                               if (get_class($this) !== ElementBrowserFolderTreeView::class) {
-                                       $link = '';
-                                       if ($this->thisScript) {
-                                               $link = ' href="' . htmlspecialchars($this->getThisScript() . 'PM=' . $cmd) . '"';
-                                       }
-                                       $firstHtml = '<a class="list-tree-control list-tree-control-' . ($isOpen ? 'open' : 'closed') . '"' . $link . '><i class="fa"></i></a>';
-                               } else {
-                                       $firstHtml = $this->PMiconATagWrap('', $cmd, !$isOpen);
-                               }
+                               $firstHtml = $this->renderPMIconAndLink($cmd, $isOpen);
                        }
                        // Mark a storage which is not online, as offline
                        // maybe someday there will be a special icon for this
index dfed6b6..3d09973 100644 (file)
@@ -414,7 +414,7 @@ class PagePositionMap {
                        $row = '';
                        foreach ($colPosArray as $kk => $vv) {
                                $row .= '<td class="col-nowrap col-min" width="' . round(100 / $count) . '%">';
-                               $row .= '<p><strong>' . $this->wrapColumnHeader($GLOBALS['LANG']->sL(BackendUtility::getLabelFromItemlist('tt_content', 'colPos', $vv, $pid), TRUE), $vv) . '</strong></p>';
+                               $row .= '<p><strong>' . $this->wrapColumnHeader($GLOBALS['LANG']->sL(BackendUtility::getLabelFromItemlist('tt_content', 'colPos', $vv), TRUE), $vv) . '</strong></p>';
                                if (!empty($lines[$vv])) {
                                        $row .= '<ul class="list-unstyled">';
                                        foreach ($lines[$vv] as $line) {
index af4d5f2..b9dac83 100644 (file)
@@ -205,6 +205,7 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
         * Construct to initialize class variables.
         */
        public function __construct() {
+               parent::__construct();
                $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
        }
 
index 6532d65..a25cab7 100644 (file)
@@ -61,6 +61,7 @@ class PageTreeView extends BrowseTreeView {
         * Calls init functions
         */
        public function __construct() {
+               parent::__construct();
                $this->init();
        }
 
@@ -308,7 +309,7 @@ class PageTreeView extends BrowseTreeView {
                                $firstHtml .= $this->getIcon($rootRec);
                        } else {
                                // Artificial record for the tree root, id=0
-                               $rootRec = $this->getRootRecord($uid);
+                               $rootRec = $this->getRootRecord();
                                $firstHtml .= $this->getRootIcon($rootRec);
                        }
                        if (is_array($rootRec)) {
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/BrowseLinks.js b/typo3/sysext/backend/Resources/Public/JavaScript/BrowseLinks.js
deleted file mode 100644 (file)
index 3aae828..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/**
- * 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!
- */
-var BrowseLinks;
-/**
- * this module is currently a wrapper for BrowseLinks, as the functionality
- * is split up in still a lot of inline JS code.
- */
-define('TYPO3/CMS/Backend/BrowseLinks', ['jquery'], function ($) {
-       BrowseLinks = {
-               elements: {},
-               addElements: function(elements) {
-                       $.extend(BrowseLinks.elements, elements);
-               },
-               focusOpenerAndClose: function(close) {
-                       if (close) {
-                               parent.window.opener.focus();
-                               parent.close();
-                       }
-               }
-       };
-
-       // when selecting one or multiple files, this action is called
-       BrowseLinks.File = {
-               insertElement: function(index, close) {
-                       var result = false;
-                       if (typeof BrowseLinks.elements[index] !== undefined) {
-                               var element = BrowseLinks.elements[index];
-
-                               // insertElement takes the following parameters
-                               // table, uid, type, filename,fp,filetype,imagefile,action, close
-                               result = insertElement(element.table, element.uid, element.type, element.fileName, element.filePath, element.fileExt, element.fileIcon, '', close);
-                       }
-                       return result;
-               },
-               insertElementMultiple: function(list) {
-                       var uidList = [];
-                       for (var i = 0, n = list.length; i < n; i++) {
-                               if (typeof BrowseLinks.elements[list[i]] !== undefined) {
-                                       var element = BrowseLinks.elements[list[i]];
-                                       uidList.push(element.uid);
-                               }
-                       }
-                       insertMultiple('sys_file', uidList);
-               }
-       };
-
-       /**
-        * Selector when using "Import selection" and "Toggle selection"
-        */
-       BrowseLinks.Selector = {
-               containerSelectorElement: '#typo3-filelist',
-               // Toggle selection button is pressed
-               toggle: function() {
-                       var items = this.getItems();
-                       if (items.length) {
-                               items.each(function(position, item) {
-                                       item.checked = (item.checked ? null : 'checked');
-                               });
-                       }
-               },
-               // Import selection button is pressed
-               handle: function() {
-                       var items = this.getItems();
-                       var selectedItems = [];
-                       if (items.length) {
-                               items.each(function(position, item) {
-                                       if (item.checked && item.name) {
-                                               selectedItems.push(item.name);
-                                       }
-                               });
-                               if (selectedItems.length > 0) {
-                                       // The variable _hasActionMultipleCode is set in ElementBrowser.php.
-                                       // This is a workaround to get multiple selection working with
-                                       // IRRE and groups fields.
-                                       if (typeof _hasActionMultipleCode !== 'undefined' && _hasActionMultipleCode) {
-                                               BrowseLinks.File.insertElementMultiple(selectedItems);
-                                       } else {
-                                               for (var i = 0; i < selectedItems.length; i++) {
-                                                       BrowseLinks.File.insertElement(selectedItems[i]);
-                                               }
-                                       }
-                               }
-                               BrowseLinks.focusOpenerAndClose(true);
-                       }
-               },
-               getParentElement: function(element) {
-                       return $(element ? element : this.containerSelectorElement);
-               },
-               getItems: function() {
-                       return this.getParentElement().find('.typo3-bulk-item');
-               }
-       };
-
-       // return the object in the global space
-       return BrowseLinks;
-});
index 03d3281..aa43851 100644 (file)
@@ -276,7 +276,7 @@ var TBE_EDITOR = {
                return (theStr.substring(sPos+lengthOfDelim,ePos));
        },
        curSelected: function(theField) {
-               var fObjSel = TYPO3.jQuery('select[data-formengine-input-name="' + theField + '"]').get(0);
+               var fObjSel = TYPO3.jQuery('[data-formengine-input-name="' + theField + '"]').get(0);
                var retVal="";
                if (fObjSel) {
                        if (fObjSel.type=='select-multiple' || fObjSel.type=='select-one') {
index 328c617..bc915d5 100644 (file)
@@ -61,10 +61,7 @@ $extraContentColumns = array(
                                        'title' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:image_link_formlabel',
                                        'icon' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif',
                                        'module' => array(
-                                               'name' => 'wizard_element_browser',
-                                               'urlParameters' => array(
-                                                       'mode' => 'wizard'
-                                               )
+                                               'name' => 'wizard_link_browser',
                                        ),
                                        'JSopenParams' => 'width=800,height=600,status=0,menubar=0,scrollbars=1'
                                )
@@ -131,10 +128,7 @@ $extraContentColumns = array(
                                        'title' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:image_link_formlabel',
                                        'icon' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif',
                                        'module' => array(
-                                               'name' => 'wizard_element_browser',
-                                               'urlParameters' => array(
-                                                       'mode' => 'wizard'
-                                               )
+                                               'name' => 'wizard_link_browser',
                                        ),
                                        'params' => array(
                                                'blindLinkOptions' => 'folder,file,mail,spec',
diff --git a/typo3/sysext/core/Classes/ElementBrowser/ElementBrowserHookInterface.php b/typo3/sysext/core/Classes/ElementBrowser/ElementBrowserHookInterface.php
deleted file mode 100644 (file)
index fc6d031..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\ElementBrowser;
-
-/*
- * 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 classes which hook into \TYPO3\CMS\Recordlist\Browser\ElementBrowser
- */
-interface ElementBrowserHookInterface {
-
-       /**
-        * Initializes the hook object
-        *
-        * @param \TYPO3\CMS\Recordlist\Browser\ElementBrowser Parent \TYPO3\CMS\Recordlist\Browser\ElementBrowser object
-        * @param array Additional parameters
-        * @return void
-        */
-       public function init($parentObject, $additionalParameters);
-
-       /**
-        * Adds new items to the currently allowed ones and returns them
-        *
-        * @param array Currently allowed items
-        * @return array Currently allowed items plus added items
-        */
-       public function addAllowedItems($currentlyAllowedItems);
-
-       /**
-        * Modifies the menu definition and returns it
-        *
-        * @param array Menu definition
-        * @return array Modified menu definition
-        */
-       public function modifyMenuDefinition($menuDefinition);
-
-       /**
-        * Returns a new tab for the browse links wizard
-        *
-        * @param string Current link selector action
-        * @return string A tab for the selected link action
-        */
-       public function getTab($linkSelectorAction);
-
-       /**
-        * Checks the current URL and determines what to do
-        *
-        * @param string $href
-        * @param string $siteUrl
-        * @param array $info
-        * @return array
-        */
-       public function parseCurrentUrl($href, $siteUrl, $info);
-
-}
index 36cfc89..2ae27c9 100644 (file)
@@ -49,6 +49,7 @@ class TcaMigration {
                $tca = $this->migrateIconsInOptionTags($tca);
                $tca = $this->migrateIconfileRelativePathOrFilenameOnlyToExtReference($tca);
                $tca = $this->migrateSelectFieldRenderType($tca);
+               $tca = $this->migrateElementBrowserWizardToLinkHandler($tca);
                // @todo: if showitem/defaultExtras wizards[xy] is migrated to columnsOverrides here, enableByTypeConfig could be dropped
                return $tca;
        }
@@ -349,7 +350,7 @@ class TcaMigration {
         * @param array $tca Incoming TCA
         * @return array Migrated TCA
         */
-       protected function migrateIconsForFormFieldWizardsToNewLocation($tca) {
+       protected function migrateIconsForFormFieldWizardsToNewLocation(array $tca) {
                $newTca = $tca;
 
                $oldFileNames = array(
@@ -406,7 +407,7 @@ class TcaMigration {
         * @param array $tca Incoming TCA
         * @return array Migrated TCA
         */
-       protected function migrateExtAndSysextPathToEXTPath($tca) {
+       protected function migrateExtAndSysextPathToEXTPath(array $tca) {
                foreach ($tca as $table => &$tableDefinition) {
                        if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
                                continue;
@@ -451,7 +452,7 @@ class TcaMigration {
         * @param array $tca Incoming TCA
         * @return array Migrated TCA
         */
-       protected function migrateIconsInOptionTags($tca) {
+       protected function migrateIconsInOptionTags(array $tca) {
                $newTca = $tca;
 
                foreach ($newTca as $table => &$tableDefinition) {
@@ -475,7 +476,7 @@ class TcaMigration {
         * @param array $tca Incoming TCA
         * @return array Migrated TCA
         */
-       protected function migrateIconfileRelativePathOrFilenameOnlyToExtReference($tca) {
+       protected function migrateIconfileRelativePathOrFilenameOnlyToExtReference(array $tca) {
                foreach ($tca as $table => &$tableDefinition) {
                        if (!isset($tableDefinition['ctrl']) || !is_array($tableDefinition['ctrl'])) {
                                continue;
@@ -556,4 +557,33 @@ class TcaMigration {
 
                return $newTca;
        }
+
+       /**
+        * Migrate wizard "wizard_element_browser" used in mode "wizard" to use the "wizard_link_browser" instead
+        *
+        * @param array $tca
+        * @return array Migrated TCA
+        */
+       protected function migrateElementBrowserWizardToLinkHandler(array $tca) {
+               foreach ($tca as $table => &$tableDefinition) {
+                       if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
+                               continue;
+                       }
+                       foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) {
+                               if (
+                                       isset($fieldConfig['config']['wizards']['link']['module']['name']) && $fieldConfig['config']['wizards']['link']['module']['name'] === 'wizard_element_browser'
+                                       && isset($fieldConfig['config']['wizards']['link']['module']['urlParameters']['mode']) &&  $fieldConfig['config']['wizards']['link']['module']['urlParameters']['mode'] === 'wizard'
+                               ) {
+                                       $fieldConfig['config']['wizards']['link']['module']['name'] = 'wizard_link_browser';
+                                       unset($fieldConfig['config']['wizards']['link']['module']['urlParameters']['mode']);
+                                       if (empty($fieldConfig['config']['wizards']['link']['module']['urlParameters'])) {
+                                               unset($fieldConfig['config']['wizards']['link']['module']['urlParameters']);
+                                       }
+                                       $this->messages[] = 'Reference to "wizard_element_browser" was migrated to new "wizard_link_browser" for field "' . $fieldName . '" in TCA table "' . $table . '"';
+                               }
+                       }
+               }
+               return $tca;
+       }
+
 }
index 484953d..15d3a1a 100644 (file)
@@ -183,10 +183,7 @@ return array(
                                                'title' => 'LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.link',
                                                'icon' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif',
                                                'module' => array(
-                                                       'name' => 'wizard_element_browser',
-                                                       'urlParameters' => array(
-                                                               'mode' => 'wizard'
-                                                       )
+                                                       'name' => 'wizard_link_browser',
                                                ),
                                                'JSopenParams' => 'width=800,height=600,status=0,menubar=0,scrollbars=1'
                                        )
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-66369-AddedLinkBrowserAPIs.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-66369-AddedLinkBrowserAPIs.rst
new file mode 100644 (file)
index 0000000..1b55d72
--- /dev/null
@@ -0,0 +1,39 @@
+========================================
+Feature: #66369 - Added LinkBrowser APIs
+========================================
+
+Description
+===========
+
+This new feature allows to extend the LinkBrowser with new tabs.
+
+Each tab is handled by a so called LinkHandler, which has to implement the ``\TYPO3\CMS\Recordlist\LinkHandler\LinkHandlerInterface``.
+
+The LinkHandlers are registered in page TSconfig like:
+
+.. code:: typoscript
+
+       file {
+               handler = TYPO3\\CMS\\Recordlist\\LinkHandler\\FileLinkHandler
+               label = LLL:EXT:lang/locallang_browse_links.xlf:file
+               displayAfter = page
+               scanAfter = page
+               configuration {
+                       customConfig = passed to the handler
+               }
+       }
+
+The handlers are displayed as tabs in the link browser.
+The options ``displayBefore`` and ``displayAfter`` define the order of the displayed tabs.
+
+The options ``scanBefore`` and ``scanAfter`` define the order in which handlers are executed when scanning existing links.
+For instance, your links might start with a specific prefix to identify them. Therefore you should register at least before
+the 'url' handler, so your handler can advertise itself as responsible for the given link.
+
+.. code:: php
+
+       $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['LinkBrowser']['hooks'][1444048118] = [
+               'handler' => \Vendor\Ext\MyClass::class,
+               'before' => [], // optional
+               'after' => [] // optional
+       ];
index c2f373c..b1df880 100644 (file)
@@ -812,4 +812,50 @@ class TcaMigrationTest extends UnitTestCase {
                $subject = new TcaMigration();
                $this->assertEquals($expected, $subject->migrate($input));
        }
+
+       /**
+        * @test
+        */
+       public function migrateFixesReferenceToLinkHandler() {
+               $input = [
+                       'aTable' => [
+                               'columns' => [
+                                       'aCol' => [
+                                               'config' => [
+                                                       'wizards' => [
+                                                               'link' => [
+                                                                       'module' => [
+                                                                               'name' => 'wizard_element_browser',
+                                                                               'urlParameters' => [
+                                                                                       'mode' => 'wizard'
+                                                                               ]
+                                                                       ],
+                                                               ],
+                                                       ],
+                                               ],
+                                       ],
+                               ],
+                       ],
+               ];
+               $expected = [
+                       'aTable' => [
+                               'columns' => [
+                                       'aCol' => [
+                                               'config' => [
+                                                       'wizards' => [
+                                                               'link' => [
+                                                                       'module' => [
+                                                                               'name' => 'wizard_link_browser',
+                                                                       ],
+                                                               ],
+                                                       ],
+                                               ],
+                                       ],
+                               ],
+                       ],
+               ];
+
+               $subject = new TcaMigration();
+               $this->assertEquals($expected, $subject->migrate($input));
+       }
 }
index 924d4b7..f3ca1e8 100644 (file)
@@ -22,6 +22,7 @@ use TYPO3\CMS\Core\Database\DatabaseConnection;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
 use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\InaccessibleFolder;
@@ -183,8 +184,11 @@ class FileList extends AbstractRecordList {
 
        /**
         * Construct
+        *
+        * @param FileListController $fileListController
         */
        public function __construct(FileListController $fileListController) {
+               parent::__construct();
                $this->fileListController = $fileListController;
                $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
        }
@@ -290,12 +294,12 @@ class FileList extends AbstractRecordList {
         * Wrapping input string in a link with clipboard command.
         *
         * @param string $string String to be linked - must be htmlspecialchar'ed / prepared before.
-        * @param string $table table - NOT USED
+        * @param string $_ unused
         * @param string $cmd "cmd" value
         * @param string $warning Warning for JS confirm message
         * @return string Linked string
         */
-       public function linkClipboardHeaderIcon($string, $table, $cmd, $warning = '') {
+       public function linkClipboardHeaderIcon($string, $_, $cmd, $warning = '') {
                $onClickEvent = 'document.dblistForm.cmd.value=\'' . $cmd . '\';document.dblistForm.submit();';
                if ($warning) {
                        $onClickEvent = 'if (confirm(' . GeneralUtility::quoteJSvalue($warning) . ')){' . $onClickEvent . '}';
@@ -322,7 +326,7 @@ class FileList extends AbstractRecordList {
                        try {
                                $foldersCount = $storage->countFoldersInFolder($this->folderObject);
                                $filesCount = $storage->countFilesInFolder($this->folderObject);
-                       } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException $e) {
+                       } catch (InsufficientFolderAccessPermissionsException $e) {
                                $foldersCount = 0;
                                $filesCount = 0;
                        }
@@ -384,7 +388,7 @@ class FileList extends AbstractRecordList {
                        $iOut = '';
                        // Directories are added
                        $this->eCounter = $this->firstElementNumber;
-                       list($flag, $code) = $this->fwd_rwd_nav();
+                       list(, $code) = $this->fwd_rwd_nav();
                        $iOut .= $code;
 
                        $iOut .= $this->formatDirList($folders);
@@ -394,7 +398,7 @@ class FileList extends AbstractRecordList {
                        $this->eCounter = $this->firstElementNumber + $this->iLimit <= $this->totalItems
                                ? $this->firstElementNumber + $this->iLimit
                                : $this->totalItems;
-                       list($flag, $code) = $this->fwd_rwd_nav();
+                       list(, $code) = $this->fwd_rwd_nav();
                        $iOut .= $code;
 
                        // Header line is drawn
@@ -537,7 +541,7 @@ class FileList extends AbstractRecordList {
                                                case 'size':
                                                        try {
                                                                $numFiles = $folderObject->getFileCount();
-                                                       } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException $e) {
+                                                       } catch (InsufficientFolderAccessPermissionsException $e) {
                                                                $numFiles = 0;
                                                        }
                                                        $theData[$field] = $numFiles . ' ' . $this->getLanguageService()->getLL(($numFiles === 1 ? 'file' : 'files'), TRUE);
@@ -626,6 +630,8 @@ class FileList extends AbstractRecordList {
         * The URL however is not relative, otherwise GeneralUtility::sanitizeLocalUrl() would say that
         * the URL would be invalid
         *
+        * @param string $altId
+        *
         * @return string URL
         */
        public function listURL($altId = '') {
@@ -697,7 +703,7 @@ class FileList extends AbstractRecordList {
                                                                $languageId = $language['uid'];
                                                                $flagIcon = $language['flagIcon'];
                                                                if (array_key_exists($languageId, $translations)) {
-                                                                       $title = htmlspecialchars(sprintf($GLOBALS['LANG']->getLL('editMetadataForLanguage'), $language['title']));
+                                                                       $title = htmlspecialchars(sprintf($this->getLanguageService()->getLL('editMetadataForLanguage'), $language['title']));
                                                                        // @todo the overlay for the flag needs to be added ($flagIcon . '-overlay')
                                                                        $urlParameters = [
                                                                                'edit' => [
@@ -708,12 +714,8 @@ class FileList extends AbstractRecordList {
                                                                                'returnUrl' => $this->listURL()
                                                                        ];
                                                                        $flagButtonIcon = $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-edit')->render();
-                                                                       $data = array(
-                                                                               'sys_file_metadata' => array($translations[$languageId]['uid'] => 'edit')
-                                                                       );
                                                                        $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
-                                                                       $languageCode .= '<a href="' . htmlspecialchars($url)
-                                                                               . '" class="btn btn-default" title="' . $title . '">'
+                                                                       $languageCode .= '<a href="' . htmlspecialchars($url) . '" class="btn btn-default" title="' . $title . '">'
                                                                                . $flagButtonIcon . '</a>';
                                                                } else {
                                                                        $parameters = [
@@ -725,7 +727,7 @@ class FileList extends AbstractRecordList {
                                                                                '&cmd[sys_file_metadata][' . $metaDataRecord['uid'] . '][localize]=' . $languageId,
                                                                                $returnUrl
                                                                        );
-                                                                       $flagButtonIcon = '<span title="' . htmlspecialchars(sprintf($GLOBALS['LANG']->getLL('createMetadataForLanguage'), $language['title'])) . '">' . $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-new')->render() . '</span>';
+                                                                       $flagButtonIcon = '<span title="' . htmlspecialchars(sprintf($this->getLanguageService()->getLL('createMetadataForLanguage'), $language['title'])) . '">' . $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-new')->render() . '</span>';
                                                                        $languageCode .= '<a href="' . htmlspecialchars($href) . '" class="btn btn-default">' . $flagButtonIcon . '</a> ';
                                                                }
                                                        }
@@ -734,7 +736,7 @@ class FileList extends AbstractRecordList {
                                                        $theData[$field] = ' <div class="localisationData btn-group" data-fileid="' . $fileObject->getUid() . '"' .
                                                                (empty($translations) ? ' style="display: none;"' : '') . '>' . $languageCode . '</div>';
                                                        $theData[$field] .= '<a class="btn btn-default filelist-translationToggler" data-fileid="' . $fileObject->getUid() . '">' .
-                                                               '<span title="' . $GLOBALS['LANG']->getLL('translateMetadata', TRUE) . '">'
+                                                               '<span title="' . $this->getLanguageService()->getLL('translateMetadata', TRUE) . '">'
                                                                . $this->iconFactory->getIcon('mimetypes-x-content-page-language-overlay', Icon::SIZE_SMALL)->render() . '</span>'
                                                                . '</a>';
                                                }
@@ -931,7 +933,7 @@ class FileList extends AbstractRecordList {
                if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('replace')) {
                        $url = BackendUtility::getModuleUrl('file_replace', array('target' => $fullIdentifier, 'uid' => $fileOrFolderObject->getUid()));
                        $replaceOnClick = 'top.content.list_frame.location.href = ' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+top.rawurlencode(top.content.list_frame.document.location.pathname+top.content.list_frame.document.location.search);return false;';
-                       $cells['replace'] = '<a href="#" class="btn btn-default" onclick="' . $replaceOnClick . '"  title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:cm.replace') . '">' . $this->iconFactory->getIcon('actions-edit-replace', Icon::SIZE_SMALL)->render() . '</a>';
+                       $cells['replace'] = '<a href="#" class="btn btn-default" onclick="' . $replaceOnClick . '"  title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.replace') . '">' . $this->iconFactory->getIcon('actions-edit-replace', Icon::SIZE_SMALL)->render() . '</a>';
                }
 
                // rename the file
index ac31a5e..a3f8e56 100644 (file)
@@ -339,10 +339,7 @@ return array(
                                                'title' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:header_link_formlabel',
                                                'icon' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif',
                                                'module' => array(
-                                                       'name' => 'wizard_element_browser',
-                                                       'urlParameters' => array(
-                                                               'mode' => 'wizard'
-                                                       )
+                                                       'name' => 'wizard_link_browser',
                                                ),
                                                'JSopenParams' => 'width=800,height=600,status=0,menubar=0,scrollbars=1'
                                        )
index df2d615..557543d 100644 (file)
@@ -13,6 +13,9 @@ namespace TYPO3\CMS\Impexp\View;
  *
  * The TYPO3 project - inspiring people to share!
  */
+
+use TYPO3\CMS\Backend\Tree\View\BrowseTreeView;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -20,12 +23,13 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 /**
  * Extension of the page tree class. Used to get the tree of pages to export.
  */
-class ExportPageTreeView extends \TYPO3\CMS\Backend\Tree\View\BrowseTreeView {
+class ExportPageTreeView extends BrowseTreeView {
 
        /**
         * Initialization
         */
        public function __construct() {
+               parent::__construct();
                $this->init();
        }
 
@@ -37,7 +41,7 @@ class ExportPageTreeView extends \TYPO3\CMS\Backend\Tree\View\BrowseTreeView {
         * @return string Wrapped title
         */
        public function wrapTitle($title, $v) {
-               return trim($title) === '' ? '<em>[' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title', TRUE) . ']</em>' : htmlspecialchars($title);
+               return trim($title) === '' ? '<em>[' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title', TRUE) . ']</em>' : htmlspecialchars($title);
        }
 
        /**
@@ -49,7 +53,7 @@ class ExportPageTreeView extends \TYPO3\CMS\Backend\Tree\View\BrowseTreeView {
         * @param bool $isOpen
         * @return string Icon HTML
         */
-       public function PM_ATagWrap($icon, $cmd, $bMark = '', $isOpen = '') {
+       public function PM_ATagWrap($icon, $cmd, $bMark = '', $isOpen = FALSE) {
                return $icon;
        }
 
@@ -86,7 +90,7 @@ class ExportPageTreeView extends \TYPO3\CMS\Backend\Tree\View\BrowseTreeView {
                $this->reset();
                $this->ids = $curIds;
                if ($pid > 0) {
-                       $rootRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordWSOL('pages', $pid);
+                       $rootRec = BackendUtility::getRecordWSOL('pages', $pid);
                        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
                        $firstHtml = $iconFactory->getIconForRecord('pages', $rootRec, Icon::SIZE_SMALL)->render();
                } else {
index f409db9..4fba25d 100644 (file)
@@ -154,10 +154,7 @@ return array(
                                                                                                                                <title>Link</title>
                                                                                                                                <icon>EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif</icon>
                                                                                                                                <module type="array">
-                                                                                                                                       <name>wizard_element_browser</name>
-                                                                                                                                       <urlParameters type="array">
-                                                                                                                                               <mode>wizard</mode>
-                                                                                                                                       </urlParameters>
+                                                                                                                                       <name>wizard_link_browser</name>
                                                                                                                                </module>
                                                                                                                                <JSopenParams>width=800,height=600,status=0,menubar=0,scrollbars=1</JSopenParams>
                                                                                                                        </link>
index 06c7c8c..e675b42 100644 (file)
                                                                                <title>LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.browseUrlTitle</title>
                                                                                <icon>EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif</icon>
                                                                                <module type="array">
-                                                                                       <name>wizard_element_browser</name>
+                                                                                       <name>wizard_link_browser</name>
                                                                                        <urlParameters type="array">
-                                                                                               <mode>wizard</mode>
-                                                                                               <act>file|url</act>
+                                                                                               <act>file</act>
                                                                                        </urlParameters>
                                                                                </module>
                                                                                <params type="array">
                                                                                                                <title>LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.browseUrlTitle</title>
                                                                                                                <icon>EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif</icon>
                                                                                                                <module type="array">
-                                                                                                                       <name>wizard_element_browser</name>
+                                                                                                                       <name>wizard_link_browser</name>
                                                                                                                        <urlParameters type="array">
-                                                                                                                               <mode>wizard</mode>
-                                                                                                                               <act>file|url</act>
+                                                                                                                               <act>file</act>
                                                                                                                        </urlParameters>
                                                                                                                </module>
                                                                                                                <params type="array">
                                                                                <title>LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.browseUrlTitle</title>
                                                                                <icon>EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif</icon>
                                                                                <module type="array">
-                                                                                       <name>wizard_element_browser</name>
+                                                                                       <name>wizard_link_browser</name>
                                                                                        <urlParameters type="array">
-                                                                                               <mode>wizard</mode>
-                                                                                               <act>file|url</act>
+                                                                                               <act>file</act>
                                                                                        </urlParameters>
                                                                                </module>
                                                                                <params type="array">
                                                                                                                <title>LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.browseUrlTitle</title>
                                                                                                                <icon>EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif</icon>
                                                                                                                <module type="array">
-                                                                                                                       <name>wizard_element_browser</name>
+                                                                                                                       <name>wizard_link_browser</name>
                                                                                                                        <urlParameters type="array">
-                                                                                                                               <mode>wizard</mode>
-                                                                                                                               <act>file|url</act>
+                                                                                                                               <act>file</act>
                                                                                                                        </urlParameters>
                                                                                                                </module>
                                                                                                                <params type="array">
                                                                                <title>LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.browseUrlTitle</title>
                                                                                <icon>EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif</icon>
                                                                                <module type="array">
-                                                                                       <name>wizard_element_browser</name>
+                                                                                       <name>wizard_link_browser</name>
                                                                                        <urlParameters type="array">
-                                                                                               <mode>wizard</mode>
-                                                                                               <act>file|url</act>
+                                                                                               <act>file</act>
                                                                                        </urlParameters>
                                                                                </module>
                                                                                <params type="array">
                                                                                                                <title>LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.browseUrlTitle</title>
                                                                                                                <icon>EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif</icon>
                                                                                                                <module type="array">
-                                                                                                                       <name>wizard_element_browser</name>
+                                                                                                                       <name>wizard_link_browser</name>
                                                                                                                        <urlParameters type="array">
-                                                                                                                               <mode>wizard</mode>
-                                                                                                                               <act>file|url</act>
+                                                                                                                               <act>file</act>
                                                                                                                        </urlParameters>
                                                                                                                </module>
                                                                                                                <params type="array">
diff --git a/typo3/sysext/recordlist/Classes/Browser/AbstractElementBrowser.php b/typo3/sysext/recordlist/Classes/Browser/AbstractElementBrowser.php
new file mode 100644 (file)
index 0000000..a61003c
--- /dev/null
@@ -0,0 +1,192 @@
+<?php
+namespace TYPO3\CMS\Recordlist\Browser;
+
+/*
+ * 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\Routing\Router;
+use TYPO3\CMS\Backend\Routing\UriBuilder;
+use TYPO3\CMS\Backend\Template\DocumentTemplate;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Lang\LanguageService;
+
+/**
+ * Base class for element browsers
+ *
+ * NOTE: This class should only be used internally. Extensions must implement the ElementBrowserInterface.
+ */
+abstract class AbstractElementBrowser {
+
+       /**
+       * @var DocumentTemplate
+       */
+       protected $doc;
+
+       /**
+        * @var PageRenderer
+        */
+       protected $pageRenderer = NULL;
+
+       /**
+        * URL of current request
+        *
+        * @var string
+        */
+       protected $thisScript = '';
+
+       /**
+        * @var IconFactory
+        */
+       protected $iconFactory;
+
+       /**
+        * Active with TYPO3 Element Browser: Contains the name of the form field for which this window
+        * opens - thus allows us to make references back to the main window in which the form is.
+        * Example value: "data[pages][39][bodytext]|||tt_content|"
+        * or "data[tt_content][NEW3fba56fde763d][image]|||gif,jpg,jpeg,tif,bmp,pcx,tga,png,pdf,ai|"
+        *
+        * Values:
+        * 0: form field name reference, eg. "data[tt_content][123][image]"
+        * 1: htmlArea RTE parameters: editorNo:contentTypo3Language
+        * 2: RTE config parameters: RTEtsConfigParams
+        * 3: allowed types. Eg. "tt_content" or "gif,jpg,jpeg,tif,bmp,pcx,tga,png,pdf,ai"
+        * 4: IRRE uniqueness: target level object-id to perform actions/checks on, eg. "data[79][tt_address][1][<field>][<foreign_table>]"
+        * 5: IRRE uniqueness: name of function in opener window that checks if element is already used, eg. "inline.checkUniqueElement"
+        * 6: IRRE uniqueness: name of function in opener window that performs some additional(!) action, eg. "inline.setUniqueElement"
+        * 7: IRRE uniqueness: name of function in opener window that performs action instead of using addElement/insertElement, eg. "inline.importElement"
+        *
+        * $pArr = explode('|', $this->bparams);
+        * $formFieldName = $pArr[0];
+        * $allowedTablesOrFileTypes = $pArr[3];
+        *
+        * @var string
+        */
+       protected $bparams;
+
+       /**
+        * Construct
+        */
+       public function __construct() {
+               $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
+               $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+               $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
+               $this->pageRenderer->loadJquery();
+               $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Recordlist/ElementBrowser');
+
+               $this->initialize();
+       }
+
+       /**
+        * Main initialization
+        *
+        * @return void
+        */
+       protected function initialize() {
+               $this->determineScriptUrl();
+               $this->initVariables();
+       }
+
+       /**
+        * Sets the script url depending on being a module or script request
+        *
+        * @return void
+        */
+       protected function determineScriptUrl() {
+               if ($routePath = GeneralUtility::_GP('route')) {
+                       $router = GeneralUtility::makeInstance(Router::class);
+                       $route = $router->match($routePath);
+                       $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+                       $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
+               } elseif ($moduleName = GeneralUtility::_GP('M')) {
+                       $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+               } else {
+                       $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
+               }
+       }
+
+       /**
+        * @return void
+        */
+       protected function initVariables() {
+               $this->bparams = GeneralUtility::_GP('bparams');
+               if ($this->bparams === NULL) {
+                       $this->bparams = '';
+               }
+       }
+
+       /**
+        * Initialize document template object
+        *
+        *  @return void
+        */
+       protected function initDocumentTemplate() {
+               $this->doc->bodyTagId = 'typo3-browse-links-php';
+
+               $bodyDataAttributes = array_merge(
+                       $this->getBParamDataAttributes(),
+                       $this->getBodyTagAttributes()
+               );
+               foreach ($bodyDataAttributes as $attributeName => $value) {
+                       $this->doc->bodyTagAdditions .= ' ' . $attributeName . '="' . htmlspecialchars($value) . '"';
+               }
+
+               // unset the default jumpToUrl() function as we ship our own
+               unset($this->doc->JScodeArray['jumpToUrl']);
+       }
+
+       /**
+        * @return string[] Array of body-tag attributes
+        */
+       abstract protected function getBodyTagAttributes();
+
+       /**
+        * Splits parts of $this->bparams and returns needed data attributes for the Javascript
+        *
+        * @return string[] Data attributes for Javascript
+        */
+       protected function getBParamDataAttributes() {
+               list($fieldRef, $rteParams, $rteConfig,, $irreObjectId, $irreCheckUniqueAction, $irreAddAction, $irreInsertAction) = explode('|', $this->bparams);
+
+               return [
+                       'data-this-script-url' => strpos($this->thisScript, '?') === FALSE ? $this->thisScript . '?' : $this->thisScript . '&',
+                       'data-form-field-name' => 'data[' . $fieldRef . '][' . $rteParams . '][' . $rteConfig . ']',
+                       'data-field-reference' => $fieldRef,
+                       'data-field-reference-slashed' => addslashes($fieldRef),
+                       'data-rte-parameters' => $rteParams,
+                       'data-rte-configuration' => $rteConfig,
+                       'data-irre-object-id' => $irreObjectId,
+                       'data-irre-check-unique-action' => $irreCheckUniqueAction,
+                       'data-irre-add-action' => $irreAddAction,
+                       'data-irre-insert-action' => $irreInsertAction,
+               ];
+       }
+
+       /**
+        * @return LanguageService
+        */
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
+       /**
+        * @return BackendUserAuthentication
+        */
+       protected function getBackendUser() {
+               return $GLOBALS['BE_USER'];
+       }
+
+}
diff --git a/typo3/sysext/recordlist/Classes/Browser/DatabaseBrowser.php b/typo3/sysext/recordlist/Classes/Browser/DatabaseBrowser.php
new file mode 100644 (file)
index 0000000..90283bb
--- /dev/null
@@ -0,0 +1,313 @@
+<?php
+namespace TYPO3\CMS\Recordlist\Browser;
+
+/*
+ * 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\RecordList\ElementBrowserRecordList;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Recordlist\Tree\View\ElementBrowserPageTreeView;
+use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
+
+/**
+ * Showing a page tree and allows you to browse for records
+ */
+class DatabaseBrowser extends AbstractElementBrowser implements ElementBrowserInterface, LinkParameterProviderInterface {
+
+       /**
+        * When you click a page title/expand icon to see the content of a certain page, this
+        * value will contain the ID of the expanded page.
+        * If the value is NOT set by GET parameter, then it will be restored from the module session data.
+        *
+        * @var NULL|int
+        */
+       protected $expandPage;
+
+       /**
+        * @return void
+        */
+       protected function initialize() {
+               parent::initialize();
+               $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Recordlist/BrowseDatabase');
+       }
+
+       /**
+        * @return void
+        */
+       protected function initVariables() {
+               parent::initVariables();
+               $this->expandPage = GeneralUtility::_GP('expandPage');
+       }
+
+       /**
+        * Session data for this class can be set from outside with this method.
+        *
+        * @param mixed[] $data Session data array
+        * @return array[] Session data and boolean which indicates that data needs to be stored in session because it's changed
+        */
+       public function processSessionData($data) {
+               if ($this->expandPage !== NULL) {
+                       $data['expandPage'] = $this->expandPage;
+                       $store = TRUE;
+               } else {
+                       $this->expandPage = (int)$data['expandPage'];
+                       $store = FALSE;
+               }
+               return array($data, $store);
+       }
+
+       /**
+        * @return string HTML content
+        */
+       public function render() {
+               $this->setTemporaryDbMounts();
+
+               list(,,,$allowedTables) = explode('|', $this->bparams);
+               $backendUser = $this->getBackendUser();
+
+               // Making the browsable pagetree:
+               /** @var ElementBrowserPageTreeView $pageTree */
+               $pageTree = GeneralUtility::makeInstance(ElementBrowserPageTreeView::class);
+               $pageTree->setLinkParameterProvider($this);
+               $pageTree->ext_pArrPages = $allowedTables === 'pages';
+               $pageTree->ext_showNavTitle = (bool)$backendUser->getTSConfigVal('options.pageTree.showNavTitle');
+               $pageTree->ext_showPageId = (bool)$backendUser->getTSConfigVal('options.pageTree.showPageIdWithTitle');
+               $pageTree->addField('nav_title');
+               $tree = $pageTree->getBrowsableTree();
+
+               $withTree = TRUE;
+               if ($allowedTables !== '' && $allowedTables !== '*') {
+                       $tablesArr = GeneralUtility::trimExplode(',', $allowedTables, TRUE);
+                       $onlyRootLevel = TRUE;
+                       foreach ($tablesArr as $currentTable) {
+                               if (isset($GLOBALS['TCA'][$currentTable])) {
+                                       if (!isset($GLOBALS['TCA'][$currentTable]['ctrl']['rootLevel']) || (int)$GLOBALS['TCA'][$currentTable]['ctrl']['rootLevel'] !== 1) {
+                                               $onlyRootLevel = FALSE;
+                                       }
+                               }
+                       }
+                       if ($onlyRootLevel) {
+                               $withTree = FALSE;
+                               // page to work on is root
+                               $this->expandPage = 0;
+                       }
+               }
+
+               $renderedRecordList = $this->renderTableRecords($allowedTables);
+
+               $this->initDocumentTemplate();
+               $content = $this->doc->startPage('TBE record selector');
+               $content .= $this->doc->getFlashMessages();
+
+               $content .= '
+
+                       <!--
+                               Wrapper table for page tree / record list:
+                       -->
+                       <table border="0" cellpadding="0" cellspacing="0" id="typo3-EBrecords">
+                               <tr>';
+               if ($withTree) {
+                       $content .= '<td class="c-wCell" valign="top">'
+                               . '<h3>' . $this->getLanguageService()->getLL('pageTree', TRUE) . ':</h3>'
+                               . $this->getTemporaryTreeMountCancelNotice() . $tree . '</td>';
+               }
+               $content .= '<td class="c-wCell" valign="top">' . $renderedRecordList . '</td>
+                               </tr>
+                       </table>
+                       ';
+
+               // Add some space
+               $content .= '<br /><br />';
+
+               $content .= $this->doc->endPage();
+               return $this->doc->insertStylesAndJS($content);
+       }
+
+       /**
+        * Check if a temporary tree mount is set and return a cancel button
+        *
+        * @return string HTML code
+        */
+       protected function getTemporaryTreeMountCancelNotice() {
+               if ((int)$this->getBackendUser()->getSessionData('pageTree_temporaryMountPoint') === 0) {
+                       return '';
+               }
+               $link = '<a href="' . htmlspecialchars(GeneralUtility::linkThisScript(array('setTempDBmount' => 0))) . '">'
+                       . $this->getLanguageService()->sl('LLL:EXT:lang/locallang_core.xlf:labels.temporaryDBmount', TRUE) . '</a>';
+
+               return GeneralUtility::makeInstance(FlashMessage::class, $link, '', FlashMessage::INFO)->render();
+       }
+
+       /**
+        * @return void
+        */
+       protected function setTemporaryDbMounts() {
+               $backendUser = $this->getBackendUser();
+
+               // Clear temporary DB mounts
+               $tmpMount = GeneralUtility::_GET('setTempDBmount');
+               if (isset($tmpMount)) {
+                       $backendUser->setAndSaveSessionData('pageTree_temporaryMountPoint', (int)$tmpMount);
+               }
+               // Set temporary DB mounts
+               $alternativeWebmountPoint = (int)$backendUser->getSessionData('pageTree_temporaryMountPoint');
+               if ($alternativeWebmountPoint) {
+                       $alternativeWebmountPoint = GeneralUtility::intExplode(',', $alternativeWebmountPoint);
+                       $backendUser->setWebmounts($alternativeWebmountPoint);
+               } else {
+                       // Setting alternative browsing mounts (ONLY local to browse_links.php this script so they stay "read-only")
+                       $alternativeWebmountPoints = trim($backendUser->getTSConfigVal('options.pageTree.altElementBrowserMountPoints'));
+                       $appendAlternativeWebmountPoints = $backendUser->getTSConfigVal('options.pageTree.altElementBrowserMountPoints.append');
+                       if ($alternativeWebmountPoints) {
+                               $alternativeWebmountPoints = GeneralUtility::intExplode(',', $alternativeWebmountPoints);
+                               $this->getBackendUser()->setWebmounts($alternativeWebmountPoints, $appendAlternativeWebmountPoints);
+                       }
+               }
+       }
+
+       /**
+        * This lists all content elements for the given list of tables
+        *
+        * @param string $tables Comma separated list of tables. Set to "*" if you want all tables.
+        * @return string HTML code
+        */
+       protected function renderTableRecords($tables) {
+               $backendUser = $this->getBackendUser();
+               if ($this->expandPage === NULL  || $this->expandPage < 0 || !$backendUser->isInWebMount($this->expandPage)) {
+                       return '';
+               }
+               // Set array with table names to list:
+               if (trim($tables) === '*') {
+                       $tablesArr = array_keys($GLOBALS['TCA']);
+               } else {
+                       $tablesArr = GeneralUtility::trimExplode(',', $tables, TRUE);
+               }
+
+               $out = '<h3>' . $this->getLanguageService()->getLL('selectRecords', TRUE) . ':</h3>';
+               // Create the header, showing the current page for which the listing is.
+               // Includes link to the page itself, if pages are amount allowed tables.
+               $titleLen = (int)$backendUser->uc['titleLen'];
+               $mainPageRecord = BackendUtility::getRecordWSOL('pages', $this->expandPage);
+               if (is_array($mainPageRecord)) {
+                       $pText = htmlspecialchars(GeneralUtility::fixed_lgd_cs($mainPageRecord['title'], $titleLen));
+
+                       $out .= $this->iconFactory->getIconForRecord('pages', $mainPageRecord, Icon::SIZE_SMALL)->render();
+                       if (in_array('pages', $tablesArr, TRUE)) {
+                               $out .= '<span data-uid="' . htmlspecialchars($mainPageRecord['uid']) . '" data-table="pages" data-title="' . htmlspecialchars($mainPageRecord['title']) . '" data-icon="">';
+                               $out .= '<a href="#" data-close="0">'
+                                       . $this->iconFactory->getIcon('actions-edit-add', Icon::SIZE_SMALL)->render()
+                                       . '</a>'
+                                       . '<a href="#" data-close="1">'
+                                       . $pText
+                                       . '</a>';
+                               $out .= '</span>';
+                       } else {
+                               $out .= $pText;
+                       }
+                       $out .= '<br />';
+               }
+
+               $permsClause = $backendUser->getPagePermsClause(1);
+               $pageInfo = BackendUtility::readPageAccess($this->expandPage, $permsClause);
+
+               /** @var ElementBrowserRecordList $dbList */
+               $dbList = GeneralUtility::makeInstance(ElementBrowserRecordList::class);
+               $dbList->setOverrideUrlParameters($this->getUrlParameters([]));
+               $dbList->thisScript = $this->thisScript;
+               $dbList->thumbs = FALSE;
+               $dbList->localizationView = TRUE;
+               $dbList->setIsEditable(FALSE);
+               $dbList->calcPerms = $backendUser->calcPerms($pageInfo);
+               $dbList->noControlPanels = TRUE;
+               $dbList->clickMenuEnabled = FALSE;
+               $dbList->tableList = implode(',', $tablesArr);
+
+               // a string like "data[pages][79][storage_pid]"
+               list($fieldPointerString) = explode('|', $this->bparams);
+               // parts like: data, pages], 79], storage_pid]
+               $fieldPointerParts = explode('[', $fieldPointerString);
+               $relatingTableName = substr($fieldPointerParts[1], 0, -1);
+               $relatingFieldName = substr($fieldPointerParts[3], 0, -1);
+               if ($relatingTableName && $relatingFieldName) {
+                       $dbList->setRelatingTableAndField($relatingTableName, $relatingFieldName);
+               }
+
+               $dbList->start(
+                       $this->expandPage,
+                       GeneralUtility::_GP('table'),
+                       MathUtility::forceIntegerInRange(GeneralUtility::_GP('pointer'), 0, 100000),
+                       GeneralUtility::_GP('search_field'),
+                       GeneralUtility::_GP('search_levels'),
+                       GeneralUtility::_GP('showLimit')
+               );
+
+               $dbList->setDispFields();
+               $dbList->generateList();
+
+               $out .= $dbList->getSearchBox();
+
+               // Add the HTML for the record list to output variable:
+               $out .= $dbList->HTMLcode;
+
+               // Add support for fieldselectbox in singleTableMode
+               if ($dbList->table) {
+                       $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Recordlist/FieldSelectBox');
+                       $out .= $dbList->fieldSelectBox($dbList->table);
+               }
+
+               return $out;
+       }
+
+       /**
+        * @return string[] Array of body-tag attributes
+        */
+       protected function getBodyTagAttributes() {
+               return [
+                       'data-mode' => 'db'
+               ];
+       }
+
+       /**
+        * @param array $values Array of values to include into the parameters
+        * @return string[] Array of parameters which have to be added to URLs
+        */
+       public function getUrlParameters(array $values) {
+               $pid = isset($values['pid']) ? $values['pid'] : $this->expandPage;
+               return [
+                       'mode' => 'db',
+                       'expandPage' => $pid,
+                       'bparams' => $this->bparams
+               ];
+       }
+
+       /**
+        * @param array $values Values to be checked
+        * @return bool Returns TRUE if the given values match the currently selected item
+        */
+       public function isCurrentlySelectedItem(array $values) {
+               return FALSE;
+       }
+
+       /**
+        * Returns the URL of the current script
+        *
+        * @return string
+        */
+       public function getScriptUrl() {
+               return $this->thisScript;
+       }
+}
index f7bf2e3..873aa6c 100755 (executable)
@@ -14,7 +14,6 @@ namespace TYPO3\CMS\Recordlist\Browser;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Form\FormEngine;
 use TYPO3\CMS\Backend\RecordList\ElementBrowserRecordList;
 use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
@@ -39,7 +38,6 @@ use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\InaccessibleFolder;
 use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
-use TYPO3\CMS\Core\Utility\File\BasicFileUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -250,9 +248,9 @@ class ElementBrowser {
        protected $hookObjects = array();
 
        /**
-        * @var BasicFileUtility
+        * @var string
         */
-       public $fileProcessor;
+       protected $hookName = 'typo3/class.browse_links.php';
 
        /**
         * @var PageRenderer
@@ -267,11 +265,6 @@ class ElementBrowser {
        /**
         * @var string
         */
-       protected $hookName = 'typo3/class.browse_links.php';
-
-       /**
-        * @var string
-        */
        protected $searchWord;
 
        /**
@@ -373,10 +366,6 @@ class ElementBrowser {
                        $this->mode = 'rte';
                }
 
-               // Init fileProcessor
-               $this->fileProcessor = GeneralUtility::makeInstance(BasicFileUtility::class);
-               $this->fileProcessor->init(array(), $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']);
-
                // Rich Text Editor specific configuration:
                if ($this->mode === 'rte') {
                        $this->RTEProperties = $this->getRTEConfig();
@@ -396,8 +385,7 @@ class ElementBrowser {
 
                $pageRenderer = $this->getPageRenderer();
                $pageRenderer->loadJquery();
-               $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/BrowseLinks');
-               $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LegacyTree');
+               $pageRenderer->loadRequireJsModule('TYPO3/CMS/Recordlist/BrowseFiles');
        }
 
        /**
@@ -491,6 +479,7 @@ class ElementBrowser {
                                $this->curUrlInfo = $this->parseCurUrl($this->siteURL . '?id=' . $this->curUrlArray['href'], $this->siteURL);
                        }
                } else {
+                       // RTE only
                        $this->curUrlArray = GeneralUtility::_GP('curUrl');
                        if ($this->curUrlArray['all']) {
                                $this->curUrlArray = GeneralUtility::get_tag_attributes($this->curUrlArray['all']);
@@ -1211,7 +1200,7 @@ class ElementBrowser {
        protected function getFileSelectorHtml($treeClassName = ElementBrowserFolderTreeView::class) {
                /** @var ElementBrowserFolderTreeView $folderTree */
                $folderTree = GeneralUtility::makeInstance($treeClassName);
-               $folderTree->setElementBrowser($this);
+               $folderTree->setLinkParameterProvider($this);
                $folderTree->thisScript = $this->thisScript;
                $tree = $folderTree->getBrowsableTree();
                $backendUser = $this->getBackendUser();
@@ -1302,7 +1291,7 @@ class ElementBrowser {
 
                /** @var ElementBrowserPageTreeView $pageTree */
                $pageTree = GeneralUtility::makeInstance($treeClassName);
-               $pageTree->setElementBrowser($this);
+               $pageTree->setLinkParameterProvider($this);
                $pageTree->thisScript = $this->thisScript;
                $pageTree->ext_showPageId = (bool)$backendUser->getTSConfigVal('options.pageTree.showPageIdWithTitle');
                $pageTree->ext_showNavTitle = (bool)$backendUser->getTSConfigVal('options.pageTree.showNavTitle');
@@ -1329,77 +1318,6 @@ class ElementBrowser {
        }
 
        /**
-        * TYPO3 Element Browser: Showing a page tree and allows you to browse for records
-        *
-        * @return string HTML content for the module
-        */
-       protected function main_db() {
-               // Starting content:
-               $content = $this->doc->startPage('TBE record selector');
-               // Init variable:
-               $pArr = explode('|', $this->bparams);
-               $tables = $pArr[3];
-               $backendUser = $this->getBackendUser();
-
-               // Making the browsable pagetree:
-               /** @var \TYPO3\CMS\Recordlist\Tree\View\ElementBrowserPageTreeView $pageTree */
-               $pageTree = GeneralUtility::makeInstance(\TYPO3\CMS\Recordlist\Tree\View\ElementBrowserPageTreeView::class);
-               $pageTree->setElementBrowser($this);
-               $pageTree->thisScript = $this->thisScript;
-               $pageTree->ext_pArrPages = $tables === 'pages';
-               $pageTree->ext_showNavTitle = (bool)$backendUser->getTSConfigVal('options.pageTree.showNavTitle');
-               $pageTree->ext_showPageId = (bool)$backendUser->getTSConfigVal('options.pageTree.showPageIdWithTitle');
-               $pageTree->addField('nav_title');
-
-               $withTree = TRUE;
-               if (($tables !== '') && ($tables !== '*')) {
-                       $tablesArr = GeneralUtility::trimExplode(',', $tables, TRUE);
-                       $onlyRootLevel = TRUE;
-                       foreach ($tablesArr as $currentTable) {
-                               $tableTca = $GLOBALS['TCA'][$currentTable];
-                               if (isset($tableTca)) {
-                                       if (!isset($tableTca['ctrl']['rootLevel']) || ((int)$tableTca['ctrl']['rootLevel']) != 1) {
-                                               $onlyRootLevel = FALSE;
-                                       }
-                               }
-                       }
-                       if ($onlyRootLevel) {
-                               $withTree = FALSE;
-                               // page to work on will be root
-                               $this->expandPage = 0;
-                       }
-               }
-
-               $tree = $pageTree->getBrowsableTree();
-               // Making the list of elements, if applicable:
-               $cElements = $this->TBE_expandPage($tables);
-               // Putting the things together, side by side:
-               $content .= '
-
-                       <!--
-                               Wrapper table for page tree / record list:
-                       -->
-                       <table border="0" cellpadding="0" cellspacing="0" id="typo3-EBrecords">
-                               <tr>';
-               if ($withTree) {
-                       $content .= '<td class="c-wCell" valign="top">'
-                               . $this->barheader(($this->getLanguageService()->getLL('pageTree') . ':'))
-                               . $this->getTemporaryTreeMountCancelNotice()
-                               . $tree . '</td>';
-               }
-               $content .= '<td class="c-wCell" valign="top">' . $cElements . '</td>
-                               </tr>
-                       </table>
-                       ';
-               // Add some space
-               $content .= '<br /><br />';
-               // End page, return content:
-               $content .= $this->doc->endPage();
-               $content = $this->doc->insertStylesAndJS($content);
-               return $content;
-       }
-
-       /**
         * TYPO3 Element Browser: Showing a folder tree, allowing you to browse for files.
         *
         * @return string HTML content for the module
@@ -1485,7 +1403,7 @@ class ElementBrowser {
                // Create folder tree:
                /** @var ElementBrowserFolderTreeView $folderTree */
                $folderTree = GeneralUtility::makeInstance(ElementBrowserFolderTreeView::class);
-               $folderTree->setElementBrowser($this);
+               $folderTree->setLinkParameterProvider($this);
                $folderTree->thisScript = $this->thisScript;
                $folderTree->ext_noTempRecyclerDirs = $this->mode === 'filedrag';
                $tree = $folderTree->getBrowsableTree();
@@ -1524,8 +1442,8 @@ class ElementBrowser {
                $content .= '<br /><br />';
                // Setup indexed elements:
                $this->doc->JScode .= $this->doc->wrapScriptTags('
-               require(["TYPO3/CMS/Backend/BrowseLinks"], function(BrowseLinks) {
-                       BrowseLinks.addElements(' . json_encode($this->elements) . ');
+               require(["TYPO3/CMS/Recordlist/BrowseFiles"], function(BrowseLinks) {
+                       BrowseFiles.addElements(' . json_encode($this->elements) . ');
                });');
                // Ending page, returning content:
                $content .= $this->doc->endPage();
@@ -1559,7 +1477,7 @@ class ElementBrowser {
                // Create folder tree:
                /** @var ElementBrowserFolderTreeView $folderTree */
                $folderTree = GeneralUtility::makeInstance(ElementBrowserFolderTreeView::class);
-               $folderTree->setElementBrowser($this);
+               $folderTree->setLinkParameterProvider($this);
                $folderTree->thisScript = $this->thisScript;
                $folderTree->ext_noTempRecyclerDirs = $this->mode === 'filedrag';
                $tree = $folderTree->getBrowsableTree();
@@ -1648,7 +1566,7 @@ class ElementBrowser {
                        $c = 0;
                        while ($row = $db->sql_fetch_assoc($res)) {
                                $c++;
-                               $icon = $this->iconFactory->getIconForRecord('tt_content', $row, Icon::SIZE_SMALL)->render();
+                                       $icon = $this->iconFactory->getIconForRecord('tt_content', $row, Icon::SIZE_SMALL)->render();
                                $selected = '';
                                if ($this->curUrlInfo['act'] == 'page' && $this->curUrlInfo['cElement'] == $row['uid']) {
                                        $selected = ' class="active"';
@@ -2394,10 +2312,10 @@ class ElementBrowser {
        }
 
        /**
-        * For TBE: Makes an upload form for uploading files to the filemount the user is browsing.
+        * Makes an upload form for uploading files to the filemount the user is browsing.
         * The files are uploaded to the tce_file.php script in the core which will handle the upload.
         *
-        * @param Folder $folderObject Absolute filepath on server to which to upload.
+        * @param Folder $folderObject
         * @return string HTML for an upload form.
         */
        public function uploadForm(Folder $folderObject) {
@@ -2406,8 +2324,8 @@ class ElementBrowser {
                }
                // Read configuration of upload field count
                $userSetting = $this->getBackendUser()->getTSConfigVal('options.folderTree.uploadFieldsInLinkBrowser');
-               $count = isset($userSetting) ? $userSetting : 1;
-               if ($count === '0') {
+               $count = isset($userSetting) ? (int)$userSetting : 1;
+               if ($count === 0) {
                        return '';
                }
                $pArr = explode('|', $this->bparams);
@@ -2442,17 +2360,18 @@ class ElementBrowser {
                                        </tr>
                                        <tr>
                                                <td class="c-wCell c-hCell">';
-               // Traverse the number of upload fields (default is 3):
+               // Traverse the number of upload fields:
+               $combinedIdentifier = $folderObject->getCombinedIdentifier();
                for ($a = 1; $a <= $count; $a++) {
                        $code .= '<input type="file" multiple="multiple" name="upload_' . $a . '[]"' . $this->doc->formWidth(35)
                                        . ' size="50" />
                                <input type="hidden" name="file[upload][' . $a . '][target]" value="'
-                                       . htmlspecialchars($folderObject->getCombinedIdentifier()) . '" />
+                                       . htmlspecialchars($combinedIdentifier) . '" />
                                <input type="hidden" name="file[upload][' . $a . '][data]" value="' . $a . '" /><br />';
                }
                // Make footer of upload form, including the submit button:
                $redirectValue = $this->getThisScript() . 'act=' . $this->act . '&mode=' . $this->mode
-                       . '&expandFolder=' . rawurlencode($folderObject->getCombinedIdentifier())
+                       . '&expandFolder=' . rawurlencode($combinedIdentifier)
                        . '&bparams=' . rawurlencode($this->bparams)
                        . (is_array($this->P) ? GeneralUtility::implodeArrayForUrl('P', $this->P) : '');
                $code .= '<input type="hidden" name="redirect" value="' . htmlspecialchars($redirectValue) . '" />';
diff --git a/typo3/sysext/recordlist/Classes/Browser/ElementBrowserInterface.php b/typo3/sysext/recordlist/Classes/Browser/ElementBrowserInterface.php
new file mode 100644 (file)
index 0000000..2c05efa
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+namespace TYPO3\CMS\Recordlist\Browser;
+
+/*
+ * 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 ElementBrowserInterface {
+
+       /**
+        * @return string HTML content
+        */
+       public function render();
+
+       /**
+        * Session data for this class can be set from outside with this method.
+        *
+        * @param mixed[] $data Session data array
+        * @return array[] Session data and boolean which indicates that data needs to be stored in session because it's changed
+        */
+       public function processSessionData($data);
+
+}
diff --git a/typo3/sysext/recordlist/Classes/Browser/FileBrowser.php b/typo3/sysext/recordlist/Classes/Browser/FileBrowser.php
new file mode 100644 (file)
index 0000000..24473b4
--- /dev/null
@@ -0,0 +1,485 @@
+<?php
+namespace TYPO3\CMS\Recordlist\Browser;
+
+/*
+ * 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\Tree\View\ElementBrowserFolderTreeView;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Resource\Exception;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\FileInterface;
+use TYPO3\CMS\Core\Resource\FileRepository;
+use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\ProcessedFile;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
+use TYPO3\CMS\Recordlist\View\FolderUtilityRenderer;
+
+/**
+ * Browser for files
+ */
+class FileBrowser extends AbstractElementBrowser implements ElementBrowserInterface, LinkParameterProviderInterface {
+
+       /**
+        * When you click a folder name/expand icon to see the content of a certain file folder,
+        * this value will contain the path of the expanded file folder.
+        * If the value is NOT set, then it will be restored from the module session data.
+        * Example value: "/www/htdocs/typo3/32/3dsplm/fileadmin/css/"
+        *
+        * @var string|NULL
+        */
+       protected $expandFolder;
+
+       /**
+        * @var Folder
+        */
+       protected $selectedFolder;
+
+       /**
+        * Holds information about files
+        *
+        * @var mixed[][]
+        */
+       protected $elements = array();
+
+       /**
+       * @var string
+       */
+       protected $searchWord;
+
+       /**
+        * @var FileRepository
+        */
+       protected $fileRepository;
+
+       /**
+        * @return void
+        */
+       protected function initialize() {
+               parent::initialize();
+               $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Recordlist/BrowseFiles');
+               $this->fileRepository = GeneralUtility::makeInstance(FileRepository::class);
+       }
+
+       /**
+        * @return void
+        */
+       protected function initVariables() {
+               parent::initVariables();
+               $this->expandFolder = GeneralUtility::_GP('expandFolder');
+               $this->searchWord = (string)GeneralUtility::_GP('searchWord');
+       }
+
+       /**
+        * Session data for this class can be set from outside with this method.
+        *
+        * @param mixed[] $data Session data array
+        * @return array[] Session data and boolean which indicates that data needs to be stored in session because it's changed
+        */
+       public function processSessionData($data) {
+               if ($this->expandFolder !== NULL) {
+                       $data['expandFolder'] = $this->expandFolder;
+                       $store = TRUE;
+               } else {
+                       $this->expandFolder = $data['expandFolder'];
+                       $store = FALSE;
+               }
+               return array($data, $store);
+       }
+
+       /**
+        * @return string HTML content
+        */
+       public function render() {
+               $backendUser = $this->getBackendUser();
+
+               // The key number 3 of the bparams contains the "allowed" string. Disallowed is not passed to
+               // the element browser at all but only filtered out in TCEMain afterwards
+               $allowedFileExtensions = explode('|', $this->bparams)[3];
+               if (!empty($allowedFileExtensions) && $allowedFileExtensions !== 'sys_file' && $allowedFileExtensions !== '*') {
+                       // Create new filter object
+                       $filterObject = GeneralUtility::makeInstance(FileExtensionFilter::class);
+                       $filterObject->setAllowedFileExtensions($allowedFileExtensions);
+                       // Set file extension filters on all storages
+                       $storages = $backendUser->getFileStorages();
+                       /** @var $storage \TYPO3\CMS\Core\Resource\ResourceStorage */
+                       foreach ($storages as $storage) {
+                               $storage->addFileAndFolderNameFilter(array($filterObject, 'filterFileList'));
+                       }
+               }
+               // Create upload/create folder forms, if a path is given
+               if ($this->expandFolder) {
+                       $fileOrFolderObject = NULL;
+
+                       // Try to fetch the folder the user had open the last time he browsed files
+                       // Fallback to the default folder in case the last used folder is not existing
+                       try {
+                               $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($this->expandFolder);
+                       } catch (Exception $accessException) {
+                               // We're just catching the exception here, nothing to be done if folder does not exist or is not accessible.
+                       }
+
+                       if ($fileOrFolderObject instanceof Folder) {
+                               // It's a folder
+                               $this->selectedFolder = $fileOrFolderObject;
+                       } elseif ($fileOrFolderObject instanceof FileInterface) {
+                               // It's a file
+                               $this->selectedFolder = $fileOrFolderObject->getParentFolder();
+                       }
+               }
+               // Or get the user's default upload folder
+               if (!$this->selectedFolder) {
+                       try {
+                               $this->selectedFolder = $backendUser->getDefaultUploadFolder();
+                       } catch (\Exception $e) {
+                               // The configured default user folder does not exist
+                       }
+               }
+               // Build the file upload and folder creation form
+               $uploadForm = '';
+               $createFolder = '';
+               if ($this->selectedFolder) {
+                       $pArr = explode('|', $this->bparams);
+                       $allowedExtensions = isset($pArr[3]) ? GeneralUtility::trimExplode(',', $pArr[3], TRUE) : [];
+                       $folderUtilityRenderer = GeneralUtility::makeInstance(FolderUtilityRenderer::class, $this);
+                       $uploadForm = $folderUtilityRenderer->uploadForm($this->selectedFolder, $allowedExtensions);
+                       $createFolder = $folderUtilityRenderer->createFolder($this->selectedFolder);
+               }
+
+               // Getting flag for showing/not showing thumbnails:
+               $noThumbs = $backendUser->getTSConfigVal('options.noThumbsInEB');
+               $_MOD_SETTINGS = array();
+               if (!$noThumbs) {
+                       // MENU-ITEMS, fetching the setting for thumbnails from File>List module:
+                       $_MOD_MENU = array('displayThumbs' => '');
+                       $_MCONF['name'] = 'file_list';
+                       $_MOD_SETTINGS = BackendUtility::getModuleData($_MOD_MENU, GeneralUtility::_GP('SET'), $_MCONF['name']);
+               }
+               $noThumbs = $noThumbs ?: !$_MOD_SETTINGS['displayThumbs'];
+               // Create folder tree:
+               /** @var ElementBrowserFolderTreeView $folderTree */
+               $folderTree = GeneralUtility::makeInstance(ElementBrowserFolderTreeView::class);
+               $folderTree->setLinkParameterProvider($this);
+               $tree = $folderTree->getBrowsableTree();
+               if ($this->selectedFolder) {
+                       $files = $this->renderFilesInFolder($this->selectedFolder, $allowedFileExtensions, $noThumbs);
+               } else {
+                       $files = '';
+               }
+
+               $this->initDocumentTemplate();
+               // Starting content:
+               $content = $this->doc->startPage('TBE file selector');
+               $content .= $this->doc->getFlashMessages();
+
+               // Insert the upload form on top, if so configured
+               if ($backendUser->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
+                       $content .= $uploadForm;
+               }
+               // Putting the parts together, side by side:
+               $content .= '
+
+                       <!--
+                               Wrapper table for folder tree / filelist:
+                       -->
+                       <table border="0" cellpadding="0" cellspacing="0" id="typo3-EBfiles">
+                               <tr>
+                                       <td class="c-wCell" valign="top"><h3>' . $this->getLanguageService()->getLL('folderTree', TRUE) . ':</h3>' . $tree . '</td>
+                                       <td class="c-wCell" valign="top">' . $files . '</td>
+                               </tr>
+                       </table>
+                       ';
+               // Adding create folder + upload forms if applicable:
+               if (!$backendUser->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
+                       $content .= $uploadForm;
+               }
+               $content .= $createFolder;
+               // Add some space
+               $content .= '<br /><br />';
+               // Ending page, returning content:
+               $content .= $this->doc->endPage();
+               return $this->doc->insertStylesAndJS($content);
+       }
+
+       /**
+        * For TYPO3 Element Browser: Expand folder of files.
+        *
+        * @param Folder $folder The folder path to expand
+        * @param string $extensionList List of fileextensions to show
+        * @param bool $noThumbs Whether to show thumbnails or not. If set, no thumbnails are shown.
+        * @return string HTML output
+        */
+       public function renderFilesInFolder(Folder $folder, $extensionList = '', $noThumbs = FALSE) {
+               if (!$folder->checkActionPermission('read')) {
+                       return '';
+               }
+               $lang = $this->getLanguageService();
+               $titleLen = (int)$this->getBackendUser()->uc['titleLen'];
+
+               if ($this->searchWord !== '') {
+                       $files = $this->fileRepository->searchByName($folder, $this->searchWord);
+               } else {
+                       $extensionList = $extensionList === '*' ? '' : $extensionList;
+                       $files = $this->getFilesInFolder($folder, $extensionList);
+               }
+               $filesCount = count($files);
+
+               $lines = array();
+
+               // Create the header of current folder:
+               $folderIcon = $this->iconFactory->getIconForResource($folder, Icon::SIZE_SMALL);
+               $lines[] = '<tr class="t3-row-header"><td colspan="4">'
+                       . $folderIcon . htmlspecialchars(GeneralUtility::fixed_lgd_cs($folder->getIdentifier(), $titleLen))
+                       . '</td></tr>';
+
+               if ($filesCount === 0) {
+                       $lines[] = '
+                               <tr class="file_list_normal">
+                                       <td colspan="4">No files found.</td>
+                               </tr>';
+               }
+
+               foreach ($files as $fileObject) {
+                       $fileExtension = $fileObject->getExtension();
+                       // Thumbnail/size generation:
+                       $imgInfo = array();
+                       if (!$noThumbs && GeneralUtility::inList(strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] . ',' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']), strtolower($fileExtension))) {
+                               $processedFile = $fileObject->process(
+                                       ProcessedFile::CONTEXT_IMAGEPREVIEW,
+                                       array('width' => 64, 'height' => 64)
+                               );
+                               $imageUrl = $processedFile->getPublicUrl(TRUE);
+                               $imgInfo = array(
+                                       $fileObject->getProperty('width'),
+                                       $fileObject->getProperty('height')
+                               );
+                               $pDim = $imgInfo[0] . 'x' . $imgInfo[1] . ' pixels';
+                               $clickIcon = '<img src="' . $imageUrl . '"'
+                                       . ' width="' . $processedFile->getProperty('width') . '"'
+                                       . ' height="' . $processedFile->getProperty('height') . '"'
+                                       . ' hspace="5" vspace="5" border="1" />';
+                       } else {
+                               $clickIcon = '';
+                               $pDim = '';
+                       }
+                       // Create file icon:
+                       $size = ' (' . GeneralUtility::formatSize($fileObject->getSize()) . 'bytes' . ($pDim ? ', ' . $pDim : '') . ')';
+                       $icon = '<span title="' . htmlspecialchars($fileObject->getName() . $size) . '">' . $this->iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL) . '</span>';
+                       // Create links for adding the file:
+                       $filesIndex = count($this->elements);
+                       $this->elements['file_' . $filesIndex] = array(
+                               'type' => 'file',
+                               'table' => 'sys_file',
+                               'uid' => $fileObject->getUid(),
+                               'fileName' => $fileObject->getName(),
+                               'filePath' => $fileObject->getUid(),
+                               'fileExt' => $fileExtension,
+                               'fileIcon' => $icon
+                       );
+                       if ($this->fileIsSelectableInFileList($fileObject, $imgInfo)) {
+                               $ATag = '<a href="#" title="' . htmlspecialchars($fileObject->getName()) . '" data-file-index="' . htmlspecialchars($filesIndex) . '" data-close="0">';
+                               $ATag_alt = '<a href="#" title="' . htmlspecialchars($fileObject->getName()) . '" data-file-index="' . htmlspecialchars($filesIndex) . '" data-close="1">';
+                               $ATag_e = '</a>';
+                               $bulkCheckBox = '<input type="checkbox" class="typo3-bulk-item" name="file_' . $filesIndex . '" value="0" /> ';
+                       } else {
+                               $ATag = '';
+                               $ATag_alt = '';
+                               $ATag_e = '';
+                               $bulkCheckBox = '';
+                       }
+                       // Create link to showing details about the file in a window:
+                       $Ahref = BackendUtility::getModuleUrl('show_item', array(
+                               'type' => 'file',
+                               'table' => '_FILE',
+                               'uid' => $fileObject->getCombinedIdentifier(),
+                               'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
+                       ));
+
+                       // Combine the stuff:
+                       $filenameAndIcon = $bulkCheckBox . $ATag_alt . $icon . htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getName(), $titleLen)) . $ATag_e;
+                       // Show element:
+                       $lines[] = '
+                                       <tr class="file_list_normal">
+                                               <td nowrap="nowrap">' . $filenameAndIcon . '&nbsp;</td>
+                                               <td>' . $ATag . '<span title="' .  $lang->getLL('addToList', TRUE) . '">' . $this->iconFactory->getIcon('actions-edit-add', Icon::SIZE_SMALL)->render() . '</span>' . $ATag_e . '</td>
+                                               <td nowrap="nowrap"><a href="' . htmlspecialchars($Ahref) . '" title="' . $lang->getLL('info', TRUE) . '">' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL) . $lang->getLL('info', TRUE) . '</a></td>
+                                               <td nowrap="nowrap">&nbsp;' . $pDim . '</td>
+                                       </tr>';
+                       if ($pDim) {
+                               $lines[] = '
+                                       <tr>
+                                               <td class="filelistThumbnail" colspan="4">' . $ATag_alt . $clickIcon . $ATag_e . '</td>
+                                       </tr>';
+                       }
+               }
+
+               $out = '<h3>' . $lang->getLL('files', TRUE) . ' ' . $filesCount . ':</h3>';
+               $out .= $this->getFileSearchField();
+               $out .= '<div id="filelist">';
+               $out .= $this->getBulkSelector($filesCount);
+
+               // Wrap all the rows in table tags:
+               $out .= '
+
+       <!--
+               Filelisting
+       -->
+                       <table cellpadding="0" cellspacing="0" id="typo3-filelist">
+                               ' . implode('', $lines) . '
+                       </table>';
+               // Return accumulated content for filelisting:
+               $out .= '</div>';
+               return $out;
+       }
+
+       /**
+        * Get a list of Files in a folder filtered by extension
+        *
+        * @param Folder $folder
+        * @param string $extensionList
+        * @return File[]
+        */
+       protected function getFilesInFolder(Folder $folder, $extensionList) {
+               if ($extensionList !== '') {
+                       /** @var FileExtensionFilter $filter */
+                       $filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
+                       $filter->setAllowedFileExtensions($extensionList);
+                       $folder->setFileAndFolderNameFilters(array(array($filter, 'filterFileList')));
+               }
+               return $folder->getFiles();
+       }
+
+       /**
+        * Get the HTML data required for a bulk selection of files of the TYPO3 Element Browser.
+        *
+        * @param int $filesCount Number of files currently displayed
+        * @return string HTML data required for a bulk selection of files - if $filesCount is 0, nothing is returned
+        */
+       protected function getBulkSelector($filesCount) {
+               if (!$filesCount) {
+                       return '';
+               }
+
+               $lang = $this->getLanguageService();
+               $labelToggleSelection = $lang->sL('LLL:EXT:lang/locallang_browse_links.xlf:toggleSelection', TRUE);
+               $labelImportSelection = $lang->sL('LLL:EXT:lang/locallang_browse_links.xlf:importSelection', TRUE);
+
+               $out = $this->doc->spacer(10) . '<div>' . '<a href="#" id="t3-js-importSelection" title="' . $labelImportSelection . '">'
+                       . $this->iconFactory->getIcon('actions-document-import-t3d', Icon::SIZE_SMALL)
+                       . $labelImportSelection . '</a>&nbsp;&nbsp;&nbsp;'
+                       . '<a href="#" id="t3-js-toggleSelection" title="' . $labelToggleSelection . '">'
+                       . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)
+                       . $labelToggleSelection . '</a>' . '</div>';
+
+               // Getting flag for showing/not showing thumbnails:
+               $noThumbsInEB = $this->getBackendUser()->getTSConfigVal('options.noThumbsInEB');
+               if (!$noThumbsInEB && $this->selectedFolder) {
+                       // MENU-ITEMS, fetching the setting for thumbnails from File>List module:
+                       $_MOD_MENU = array('displayThumbs' => '');
+                       $_MCONF['name'] = 'file_list';
+                       $_MOD_SETTINGS = BackendUtility::getModuleData($_MOD_MENU, GeneralUtility::_GP('SET'), $_MCONF['name']);
+                       $addParams = GeneralUtility::implodeArrayForUrl('' , $this->getUrlParameters(['identifier' => $this->selectedFolder->getCombinedIdentifier()]));
+                       $thumbNailCheck = '<div class="checkbox"><label for="checkDisplayThumbs">'
+                               . BackendUtility::getFuncCheck(
+                                       '',
+                                       'SET[displayThumbs]',
+                                       $_MOD_SETTINGS['displayThumbs'],
+                                       $this->thisScript,
+                                       $addParams,
+                                       'id="checkDisplayThumbs"'
+                               )
+                               . $lang->sL('LLL:EXT:lang/locallang_mod_file_list.xlf:displayThumbs', TRUE) . '</label></div>';
+                       $out .= $this->doc->spacer(5) . $thumbNailCheck . $this->doc->spacer(15);
+               } else {
+                       $out .= $this->doc->spacer(15);
+               }
+               return $out;
+       }
+
+       /**
+        * Get the HTML data required for the file search field of the TYPO3 Element Browser.
+        *
+        * @return string HTML data required for the search field in the file list of the Element Browser
+        */
+       protected function getFileSearchField() {
+               $action = $this->getScriptUrl() . GeneralUtility::implodeArrayForUrl('', $this->getUrlParameters([]));
+               $out = '
+                       <form method="post" action="' . htmlspecialchars($action) . '">
+                               <div class="input-group">
+                                       <input class="form-control" type="text" name="searchWord" value="' . htmlspecialchars($this->searchWord) . '">
+                                       <span class="input-group-btn">
+                                               <button class="btn btn-default" type="submit">' . $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:search', TRUE) .'</button>
+                                       </span>
+                               </div>
+                       </form>';
+               $out .= $this->doc->spacer(15);
+               return $out;
+       }
+
+       /**
+        * Checks if the given file is selectable in the filelist.
+        *
+        * By default all files are selectable. This method may be overwritten in child classes.
+        *
+        * @param FileInterface $file
+        * @param mixed[] $imgInfo Image dimensions from \TYPO3\CMS\Core\Imaging\GraphicalFunctions::getImageDimensions()
+        * @return bool TRUE if file is selectable.
+        */
+       protected function fileIsSelectableInFileList(FileInterface $file, array $imgInfo) {
+               return TRUE;
+       }
+
+       /**
+        * @return string[] Array of body-tag attributes
+        */
+       protected function getBodyTagAttributes() {
+               return [
+                       'data-mode' => 'file',
+                       'data-elements' => json_encode($this->elements)
+               ];
+       }
+
+       /**
+        * @param array $values Array of values to include into the parameters
+        * @return string[] Array of parameters which have to be added to URLs
+        */
+       public function getUrlParameters(array $values) {
+               return [
+                       'mode' => 'file',
+                       'expandFolder' => isset($values['identifier']) ? $values['identifier'] : $this->expandFolder,
+                       'bparams' => $this->bparams
+               ];
+       }
+
+       /**
+        * @param array $values Values to be checked
+        * @return bool Returns TRUE if the given values match the currently selected item
+        */
+       public function isCurrentlySelectedItem(array $values) {
+               return FALSE;
+       }
+
+       /**
+        * Returns the URL of the current script
+        *
+        * @return string
+        */
+       public function getScriptUrl() {
+               return $this->thisScript;
+       }
+
+}
diff --git a/typo3/sysext/recordlist/Classes/Browser/FolderBrowser.php b/typo3/sysext/recordlist/Classes/Browser/FolderBrowser.php
new file mode 100644 (file)
index 0000000..9abfa37
--- /dev/null
@@ -0,0 +1,227 @@
+<?php
+namespace TYPO3\CMS\Recordlist\Browser;
+
+/*
+ * 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\Tree\View\ElementBrowserFolderTreeView;
+use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
+use TYPO3\CMS\Recordlist\View\FolderUtilityRenderer;
+
+/**
+ * Browser for folders
+ */
+class FolderBrowser extends AbstractElementBrowser implements ElementBrowserInterface, LinkParameterProviderInterface {
+
+       /**
+        * When you click a folder name/expand icon to see the content of a certain file folder,
+        * this value will contain the path of the expanded file folder.
+        * If the value is NOT set, then it will be restored from the module session data.
+        * Example value: "/www/htdocs/typo3/32/3dsplm/fileadmin/css/"
+        *
+        * @var string|NULL
+        */
+       protected $expandFolder;
+
+       /**
+        * @return void
+        */
+       protected function initialize() {
+               parent::initialize();
+               $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Recordlist/BrowseFolders');
+       }
+
+       /**
+        * @return void
+        */
+       protected function initVariables() {
+               parent::initVariables();
+               $this->expandFolder = GeneralUtility::_GP('expandFolder');
+       }
+
+       /**
+        * Session data for this class can be set from outside with this method.
+        *
+        * @param mixed[] $data Session data array
+        * @return array[] Session data and boolean which indicates that data needs to be stored in session because it's changed
+        */
+       public function processSessionData($data) {
+               if ($this->expandFolder !== NULL) {
+                       $data['expandFolder'] = $this->expandFolder;
+                       $store = TRUE;
+               } else {
+                       $this->expandFolder = $data['expandFolder'];
+                       $store = FALSE;
+               }
+               return array($data, $store);
+       }
+
+       /**
+        * @return string HTML content
+        */
+       public function render() {
+               $selectedFolder = NULL;
+               if ($this->expandFolder) {
+                       $selectedFolder = ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($this->expandFolder);
+               }
+
+               // Create folder tree:
+               /** @var ElementBrowserFolderTreeView $folderTree */
+               $folderTree = GeneralUtility::makeInstance(ElementBrowserFolderTreeView::class);
+               $folderTree->setLinkParameterProvider($this);
+               $tree = $folderTree->getBrowsableTree();
+
+               $folders = '';
+               if ($selectedFolder) {
+                       $folders = $this->renderFolders($selectedFolder);
+               }
+
+               $this->initDocumentTemplate();
+               $content = $this->doc->startPage('TBE folder selector');
+               $content .= $this->doc->getFlashMessages();
+
+               // Putting the parts together, side by side:
+               $content .= '
+
+                       <!--
+                               Wrapper table for folder tree / folder list:
+                       -->
+                       <table border="0" cellpadding="0" cellspacing="0" id="typo3-EBfiles">
+                               <tr>
+                                       <td class="c-wCell" valign="top"><h3>' . $this->getLanguageService()->getLL('folderTree', TRUE) . ':</h3>' . $tree . '</td>
+                                       <td class="c-wCell" valign="top">' . $folders . '</td>
+                               </tr>
+                       </table>
+                       ';
+
+               // Adding create folder if applicable:
+               if ($selectedFolder) {
+                       $content .= GeneralUtility::makeInstance(FolderUtilityRenderer::class, $this)->createFolder($selectedFolder);
+               }
+
+               // Add some space
+               $content .= '<br /><br />';
+
+               // Ending page, returning content:
+               $content .= $this->doc->endPage();
+               return $this->doc->insertStylesAndJS($content);
+       }
+
+       /**
+        * @param Folder $parentFolder
+        * @return string HTML code
+        */
+       protected function renderFolders(Folder $parentFolder) {
+               if (!$parentFolder->checkActionPermission('read')) {
+                       return '';
+               }
+               $content = '';
+               $lang = $this->getLanguageService();
+               $folders = $parentFolder->getSubfolders();
+               $folderIdentifier = $parentFolder->getCombinedIdentifier();
+
+               // Create headline (showing number of folders):
+               $content .= '<h3>' . sprintf($lang->getLL('folders', TRUE) . ' (%s):', count($folders)) . '</h3>';
+
+               $titleLength = (int)$this->getBackendUser()->uc['titleLen'];
+               // Create the header of current folder:
+               $folderIcon = '<a href="#" data-folder-id="' . htmlspecialchars($folderIdentifier) . '" data-close="1">';
+               $folderIcon .= $this->iconFactory->getIcon('apps-filetree-folder-default', Icon::SIZE_SMALL);
+               $folderIcon .= htmlspecialchars(GeneralUtility::fixed_lgd_cs($parentFolder->getName(), $titleLength));
+               $folderIcon .= '</a>';
+               $content .= $folderIcon . '<br />';
+
+               $lines = array();
+               // Traverse the folder list:
+               foreach ($folders as $subFolder) {
+                       $subFolderIdentifier = $subFolder->getCombinedIdentifier();
+                       // Create folder icon:
+                       $icon = '<span style="width: 16px; height: 16px; display: inline-block;"></span>';
+                       $icon .= '<span title="' . htmlspecialchars($subFolder->getName()) . '">' . $this->iconFactory->getIcon('apps-filetree-folder-default', Icon::SIZE_SMALL) . '</span>';
+                       // Create links for adding the folder:
+                       $aTag = '<a href="#" data-folder-id="' . htmlspecialchars($folderIdentifier) . '" data-close="0">';
+                       $aTag_alt = '<a href="#" data-folder-id="' . htmlspecialchars($folderIdentifier) . '" data-close="1">';
+                       if (strstr($subFolderIdentifier, ',') || strstr($subFolderIdentifier, '|')) {
+                               // In case an invalid character is in the filepath, display error message:
+                               $errorMessage = sprintf($lang->getLL('invalidChar', TRUE), ', |');
+                               $aTag = '<a href="#" class="t3-js-folderIdError" data-message="' . $errorMessage . '">';
+                       }
+                       $aTag_e = '</a>';
+                       // Combine icon and folderpath:
+                       $foldernameAndIcon = $aTag_alt . $icon . htmlspecialchars(GeneralUtility::fixed_lgd_cs($subFolder->getName(), $titleLength)) . $aTag_e;
+                       $lines[] = '
+                               <tr class="bgColor4">
+                                       <td nowrap="nowrap">' . $foldernameAndIcon . '&nbsp;</td>
+                                       <td>' . $aTag . '<span title="' . $lang->getLL('addToList', TRUE) . '">' . $this->iconFactory->getIcon('actions-edit-add', Icon::SIZE_SMALL)->render() . '</span>' . $aTag_e . '</td>
+                                       <td>&nbsp;</td>
+                               </tr>';
+                       $lines[] = '
+                                       <tr>
+                                               <td colspan="3"><span style="width: 1px; height: 3px; display: inline-block;"></span></td>
+                                       </tr>';
+               }
+               // Wrap all the rows in table tags:
+               $content .= '
+
+       <!--
+               Folder listing
+       -->
+                       <table border="0" cellpadding="0" cellspacing="1" id="typo3-folderList">
+                               ' . implode('', $lines) . '
+                       </table>';
+
+               return $content;
+       }
+
+       /**
+        * @return string[] Array of body-tag attributes
+        */
+       protected function getBodyTagAttributes() {
+               return [
+                       'data-mode' => 'folder'
+               ];
+       }
+
+       /**
+        * @param array $values Array of values to include into the parameters
+        * @return string[] Array of parameters which have to be added to URLs
+        */
+       public function getUrlParameters(array $values) {
+               return [
+                       'mode' => 'folder',
+                       'expandFolder' => isset($values['identifier']) ? $values['identifier'] : $this->expandFolder,
+               ];
+       }
+
+       /**
+        * @param array $values Values to be checked
+        * @return bool Returns TRUE if the given values match the currently selected item
+        */
+       public function isCurrentlySelectedItem(array $values) {
+               return FALSE;
+       }
+
+       /**
+        * Returns the URL of the current script
+        *
+        * @return string
+        */
+       public function getScriptUrl() {
+               return $this->thisScript;
+       }
+
+}
index b5cd0b8..88d752d 100644 (file)
@@ -17,10 +17,11 @@ namespace TYPO3\CMS\Recordlist\Controller;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Message\ResponseInterface;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Lang\LanguageService;
-use TYPO3\CMS\Recordlist\Browser\ElementBrowser;
+use TYPO3\CMS\Recordlist\Browser\ElementBrowserInterface;
 
 /**
  * Script class for the Element Browser window.
@@ -31,11 +32,10 @@ class ElementBrowserController {
         * The mode determines the main kind of output of the element browser.
         *
         * There are these options for values:
-        *  - "rte" will show the link selector for the Rich Text Editor (see main_rte())
-        *  - "wizard" will allow you to browse for links (like "rte") which are passed back to FormEngine (see main_rte(TRUE))
-        *  - "db" will allow you to browse for pages or records in the page tree for FormEngine select fields (see main_db())
-        *  - "file"/"filedrag" will allow you to browse for files in the folder mounts for FormEngine file selections (main_file())
-        *  - "folder" will allow you to browse for folders in the folder mounts for FormEngine folder selections (see main_folder())
+        *  - "db" will allow you to browse for pages or records in the page tree for FormEngine select fields
+        *  - "file" will allow you to browse for files in the folder mounts for FormEngine file selections
+        *  - "folder" will allow you to browse for folders in the folder mounts for FormEngine folder selections
+        *  - Other options may be registered via extensions
         *
         * @var string
         */
@@ -63,13 +63,15 @@ class ElementBrowserController {
                $this->init();
        }
 
+       /**
+        * Initialize the controller
+        *
+        * @return void
+        */
        protected function init() {
                $this->getLanguageService()->includeLLFile('EXT:lang/locallang_browse_links.xlf');
 
                $this->mode = GeneralUtility::_GP('mode');
-               if (!$this->mode) {
-                       $this->mode = 'rte';
-               }
        }
 
        /**
@@ -81,6 +83,11 @@ class ElementBrowserController {
         * @return ResponseInterface the response with the content
         */
        public function mainAction(ServerRequestInterface $request, ResponseInterface $response) {
+               // Fallback for old calls, which use mode "wizard" or "rte" for link selection
+               if ($this->mode === 'wizard' || $this->mode === 'rte') {
+                       return $response->withStatus(303)->withHeader('Location', BackendUtility::getModuleUrl('wizard_link_browser', $_GET, FALSE, TRUE));
+               }
+
                $response->getBody()->write($this->main());
                return $response;
        }
@@ -91,8 +98,6 @@ class ElementBrowserController {
         * @return string HTML content
         */
        public function main() {
-               $this->setTemporaryDbMounts();
-
                $content = '';
 
                // Render type by user func
@@ -102,68 +107,44 @@ class ElementBrowserController {
                                $browserRenderObj = GeneralUtility::getUserObj($classRef);
                                if (is_object($browserRenderObj) && method_exists($browserRenderObj, 'isValid') && method_exists($browserRenderObj, 'render')) {
                                        if ($browserRenderObj->isValid($this->mode, $this)) {
-                                               $content .= $browserRenderObj->render($this->mode, $this);
+                                               $content = $browserRenderObj->render($this->mode, $this);
                                                $browserRendered = TRUE;
                                                break;
                                        }
                                }
                        }
                }
+
                // if type was not rendered use default rendering functions
                if (!$browserRendered) {
                        $browser = $this->getElementBrowserInstance();
-                       $browser->init();
+
                        $backendUser = $this->getBackendUser();
                        $modData = $backendUser->getModuleData('browse_links.php', 'ses');
                        list($modData) = $browser->processSessionData($modData);
                        $backendUser->pushModuleData('browse_links.php', $modData);
-                       $content .= $browser->render();
+
+                       $content = $browser->render();
                }
 
                return $content;
        }
 
        /**
-        * @return void
-        */
-       protected function setTemporaryDbMounts() {
-               $backendUser = $this->getBackendUser();
-
-               // Clear temporary DB mounts
-               $tmpMount = GeneralUtility::_GET('setTempDBmount');
-               if (isset($tmpMount)) {
-                       $backendUser->setAndSaveSessionData('pageTree_temporaryMountPoint', (int)$tmpMount);
-               }
-               // Set temporary DB mounts
-               $alternativeWebmountPoint = (int)$backendUser->getSessionData('pageTree_temporaryMountPoint');
-               if ($alternativeWebmountPoint) {
-                       $alternativeWebmountPoint = GeneralUtility::intExplode(',', $alternativeWebmountPoint);
-                       $backendUser->setWebmounts($alternativeWebmountPoint);
-               } else {
-                       switch ((string)$this->mode) {
-                               case 'rte':
-                               case 'db':
-                               case 'wizard':
-                                       // Setting alternative browsing mounts (ONLY local to browse_links.php this script so they stay "read-only")
-                                       $alternativeWebmountPoints = trim($backendUser->getTSConfigVal('options.pageTree.altElementBrowserMountPoints'));
-                                       $appendAlternativeWebmountPoints = $backendUser->getTSConfigVal('options.pageTree.altElementBrowserMountPoints.append');
-                                       if ($alternativeWebmountPoints) {
-                                               $alternativeWebmountPoints = GeneralUtility::intExplode(',', $alternativeWebmountPoints);
-                                               $this->getBackendUser()->setWebmounts($alternativeWebmountPoints, $appendAlternativeWebmountPoints);
-                                       }
-                       }
-               }
-       }
-
-       /**
         * Get instance of ElementBrowser
         *
         * This method shall be overwritten in subclasses
         *
-        * @return ElementBrowser
+        * @return ElementBrowserInterface
+        * @throws \UnexpectedValueException
         */
        protected function getElementBrowserInstance() {
-               return GeneralUtility::makeInstance(ElementBrowser::class);
+               $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ElementBrowsers'][$this->mode];
+               $browser = GeneralUtility::makeInstance($className);
+               if (!$browser instanceof ElementBrowserInterface) {
+                       throw new \UnexpectedValueException('The specified element browser "' . $className . '" does not implement the required ElementBrowserInterface', 1442763890);
+               }
+               return $browser;
        }
 
        /**
diff --git a/typo3/sysext/recordlist/Classes/Controller/LinkBrowserController.php b/typo3/sysext/recordlist/Classes/Controller/LinkBrowserController.php
new file mode 100644 (file)
index 0000000..088ef1c
--- /dev/null
@@ -0,0 +1,704 @@
+<?php
+namespace TYPO3\CMS\Recordlist\Controller;
+
+/*
+ * 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\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use TYPO3\CMS\Backend\Routing\Router;
+use TYPO3\CMS\Backend\Routing\UriBuilder;
+use TYPO3\CMS\Backend\Template\DocumentTemplate;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Service\DependencyOrderingService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
+use TYPO3\CMS\Lang\LanguageService;
+use TYPO3\CMS\Recordlist\LinkHandler\LinkHandlerInterface;
+
+/**
+ * Script class for the Link Browser window.
+ */
+class LinkBrowserController {
+
+       /**
+        * @var DocumentTemplate
+        */
+       protected $doc;
+
+       /**
+        * @var array
+        */
+       protected $parameters;
+
+       /**
+        * URL of current request
+        *
+        * @var string
+        */
+       protected $thisScript = '';
+
+       /**
+        * @var LinkHandlerInterface[]
+        */
+       protected $linkHandlers = [];
+
+       /**
+        * @var string
+        */
+       protected $currentLink = '';
+
+       /**
+        * Link handler responsible for the current active link
+        *
+        * @var LinkHandlerInterface $currentLinkHandler
+        */
+       protected $currentLinkHandler;
+
+       /**
+        * The ID of the currently active link handler
+        *
+        * @var string
+        */
+       protected $currentLinkHandlerId;
+
+       /**
+        * Link handler to be displayed
+        *
+        * @var LinkHandlerInterface $displayedLinkHandler
+        */
+       protected $displayedLinkHandler;
+
+       /**
+        * The ID of the displayed link handler
+        *
+        * This is read from the 'act' GET parameter
+        *
+        * @var string
+        */
+       protected $displayedLinkHandlerId = '';
+
+       /**
+        * List of available link attribute fields
+        *
+        * @var string[]
+        */
+       protected $linkAttributeFields = [];
+
+       /**
+        * Values of the link attributes
+        *
+        * @var string[]
+        */
+       protected $linkAttributeValues = [];
+
+       /**
+        * @var array
+        */
+       protected $hookObjects = [];
+
+       /**
+        * Constructor
+        */
+       public function __construct() {
+               $this->initHookObjects();
+               $this->init();
+       }
+
+       /**
+        * Initialize the controller
+        *
+        * @return void
+        */
+       protected function init() {
+               $this->getLanguageService()->includeLLFile('EXT:lang/locallang_browse_links.xlf');
+       }
+
+       /**
+        * Initialize hook objects implementing the interface
+        *
+        * @throws \UnexpectedValueException
+        * @return void
+        */
+       protected function initHookObjects() {
+               if (
+                       isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['LinkBrowser']['hooks'])
+                       && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['LinkBrowser']['hooks'])
+               ) {
+                       $hooks = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies(
+                               $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['LinkBrowser']['hooks']
+                       );
+                       foreach ($hooks as $key => $hook) {
+                               $this->hookObjects[] = GeneralUtility::makeInstance($hook['handler']);
+                       }
+               }
+       }
+
+       /**
+        * Injects the request object for the current request or subrequest
+        * As this controller goes only through the main() method, it is rather simple for now
+        *
+        * @param ServerRequestInterface $request the current request
+        * @param ResponseInterface $response the prepared response object
+        * @return ResponseInterface the response with the content
+        */
+       public function mainAction(ServerRequestInterface $request, ResponseInterface $response) {
+               $this->determineScriptUrl($request);
+               $this->initVariables($request);
+               $this->loadLinkHandlers();
+               $this->initCurrentUrl();
+
+               $menuData = $this->buildMenuArray();
+               $renderLinkAttributeFields = $this->renderLinkAttributeFields();
+               $browserContent = $this->displayedLinkHandler->render($request);
+
+               $this->initDocumentTemplate();
+               $content = $this->doc->startPage('Link Browser');
+               $content .= $this->doc->getFlashMessages();
+
+               if ($this->currentLink) {
+                       $content .= '<!-- Print current URL -->
+                               <table border="0" cellpadding="0" cellspacing="0" id="typo3-curUrl">
+                                       <tr>
+                                               <td>' . $this->getLanguageService()->getLL('currentLink', TRUE) . ': ' . htmlspecialchars($this->currentLinkHandler->formatCurrentUrl()) . '</td>
+                                       </tr>
+                               </table>';
+               }
+               $content .= $this->doc->getTabMenuRaw($menuData);
+               $content .= $renderLinkAttributeFields;
+
+               $content .= '<div class="linkBrowser-tabContent">' . $browserContent . '</div>';
+               $content .= $this->doc->endPage();
+
+               $response->getBody()->write($this->doc->insertStylesAndJS($content));
+               return $response;
+       }
+
+       /**
+        * Sets the script url depending on being a module or script request
+        *
+        * @param ServerRequestInterface $request
+        *
+        * @throws \TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException
+        * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
+        */
+       protected function determineScriptUrl(ServerRequestInterface $request) {
+               if ($routePath = $request->getQueryParams()['route']) {
+                       $router = GeneralUtility::makeInstance(Router::class);
+                       $route = $router->match($routePath);
+                       $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+                       $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
+               } elseif ($moduleName = $request->getQueryParams()['M']) {
+                       $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+               } else {
+                       $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
+               }
+       }
+
+       /**
+        * @param ServerRequestInterface $request
+        */
+       protected function initVariables(ServerRequestInterface $request) {
+               $queryParams = $request->getQueryParams();
+               $act = isset($queryParams['act']) ? $queryParams['act'] : '';
+               // @deprecated since CMS 7, remove with CMS 8
+               if (strpos($act, '|')) {
+                       GeneralUtility::deprecationLog('Using multiple values for the "act" parameter in the link wizard is deprecated. Only a single value is allowed. Values were: ' . $act);
+                       $act = array_shift(explode('|', $act));
+               }
+               $this->displayedLinkHandlerId = $act;
+               $this->parameters = isset($queryParams['P']) ? $queryParams['P'] : [];
+               $this->linkAttributeValues = isset($queryParams['linkAttributes']) ? $queryParams['linkAttributes'] : [];
+               $this->currentLink = isset($this->parameters['currentValue']) ? trim($this->parameters['currentValue']) : '';
+       }
+
+       /**
+        * @return void
+        * @throws \UnexpectedValueException
+        */
+       protected function loadLinkHandlers() {
+               $linkHandlers = $this->getLinkHandlers();
+               if (empty($linkHandlers)) {
+                       throw new \UnexpectedValueException('No link handlers are configured. Check page TSconfig TCEMAIN.linkHandlers.', 1442787911);
+               }
+
+               $lang = $this->getLanguageService();
+               foreach ($linkHandlers as $identifier => $configuration) {
+                       $identifier = rtrim($identifier, '.');
+                       /** @var LinkHandlerInterface $handler */
+                       $handler = GeneralUtility::makeInstance($configuration['handler']);
+                       $handler->initialize(
+                               $this,
+                               $identifier,
+                               isset($configuration['configuration.']) ? $configuration['configuration.'] : []
+                       );
+
+                       $this->linkHandlers[$identifier] = [
+                               'handlerInstance' => $handler,
+                               'label' => $lang->sL($configuration['label'], TRUE),
+                               'displayBefore' => isset($configuration['displayBefore']) ? GeneralUtility::trimExplode(',', $configuration['displayBefore']) : [],
+                               'displayAfter' => isset($configuration['displayAfter']) ? GeneralUtility::trimExplode(',', $configuration['displayAfter']) : [],
+                               'scanBefore' => isset($configuration['scanBefore']) ? GeneralUtility::trimExplode(',', $configuration['scanBefore']) : [],
+                               'scanAfter' => isset($configuration['scanAfter']) ? GeneralUtility::trimExplode(',', $configuration['scanAfter']) : [],
+                               'addParams' => isset($configuration['addParams']) ? $configuration['addParams'] : '',
+                       ];
+               }
+       }
+
+       /**
+        * Reads the configured link handlers from page TSconfig
+        *
+        * @return array
+        */
+       protected function getLinkHandlers() {
+               $pageTSconfig = BackendUtility::getPagesTSconfig($this->getCurrentPageId());
+               $pageTSconfig = $this->getBackendUser()->getTSConfig('TCEMAIN.linkHandler.', $pageTSconfig);
+               $linkHandlers = (array)$pageTSconfig['properties'];
+
+               foreach ($this->hookObjects as $hookObject) {
+                       if (method_exists($hookObject, 'modifyLinkHandlers')) {
+                               $linkHandlers = $hookObject->modifyLinkHandlers($linkHandlers, $this->currentLink);
+                       }
+               }
+
+               return $linkHandlers;
+       }
+
+       /**
+        * Initialize $this->currentLink and $this->currentLinkHandler
+        *
+        * @return void
+        */
+       protected function initCurrentUrl() {
+               if (!$this->currentLink) {
+                       return;
+               }
+
+               $currentLinkParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($this->currentLink);
+               $currentLinkParts['params'] = $currentLinkParts['additionalParams'];
+               unset($currentLinkParts['additionalParams']);
+
+               $orderedHandlers = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($this->linkHandlers, 'scanBefore', 'scanAfter');
+
+               // find responsible handler for current link
+               foreach ($orderedHandlers as $key => $configuration) {
+                       /** @var LinkHandlerInterface $handler */
+                       $handler = $configuration['handlerInstance'];
+                       if ($handler->canHandleLink($currentLinkParts)) {
+                               $this->currentLinkHandler = $handler;
+                               $this->currentLinkHandlerId = $key;
+                               break;
+                       }
+               }
+               // reset the link if we have no handler for it
+               if (!$this->currentLinkHandler) {
+                       $this->currentLink = '';
+               }
+
+               unset($currentLinkParts['url']);
+               // overwrite any preexisting
+               foreach ($currentLinkParts as $key => $part) {
+                       $this->linkAttributeValues[$key] = $part;
+               }
+       }
+
+       /**
+        * Initialize document template object
+        *
+        *  @return void
+        */
+       protected function initDocumentTemplate() {
+               $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
+               $this->doc->bodyTagId = 'typo3-browse-links-php';
+
+               if (!$this->areFieldChangeFunctionsValid() && !$this->areFieldChangeFunctionsValid(TRUE)) {
+                       $this->parameters['fieldChangeFunc'] = array();
+               }
+               unset($this->parameters['fieldChangeFunc']['alert']);
+               $update = [];
+               foreach ($this->parameters['fieldChangeFunc'] as $v) {
+                       $update[] = 'parent.opener.' . $v;
+               }
+               $inlineJS = implode(LF, $update);
+
+               $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
+               $pageRenderer->loadJquery();
+               $pageRenderer->loadRequireJsModule('TYPO3/CMS/Recordlist/LinkBrowser', 'function(LinkBrowser) {
+                       LinkBrowser.updateFunctions = function() {' . $inlineJS . '};
+               }');
+
+               foreach ($this->getBodyTagAttributes() as $attributeName => $value) {
+                       $this->doc->bodyTagAdditions .= ' ' . $attributeName . '="' . htmlspecialchars($value) . '"';
+               }
+
+               // Finally, add the accumulated JavaScript to the template object:
+               // also unset the default jumpToUrl() function before
+               unset($this->doc->JScodeArray['jumpToUrl']);
+       }
+
+       /**
+        * Returns an array definition of the top menu
+        *
+        * @return mixed[][]
+        */
+       protected function buildMenuArray() {
+               $allowedItems = $this->getAllowedItems();
+               if ($this->displayedLinkHandlerId && !in_array($this->displayedLinkHandlerId, $allowedItems, TRUE)) {
+                       $this->displayedLinkHandlerId = '';
+               }
+
+               $allowedHandlers = array_flip($allowedItems);
+               $menuDef = array();
+               foreach ($this->linkHandlers as $identifier => $configuration) {
+                       if (!isset($allowedHandlers[$identifier])) {
+                               continue;
+                       }
+
+                       /** @var LinkHandlerInterface $handlerInstance */
+                       $handlerInstance = $configuration['handlerInstance'];
+                       $isActive = $this->displayedLinkHandlerId === $identifier || !$this->displayedLinkHandlerId && $handlerInstance === $this->currentLinkHandler;
+                       if ($isActive) {
+                               $this->displayedLinkHandler = $handlerInstance;
+                               if (!$this->displayedLinkHandlerId) {
+                                       $this->displayedLinkHandlerId = $this->currentLinkHandlerId;
+                               }
+                       }
+
+                       if ($configuration['addParams']) {
+                               $addParams = $configuration['addParams'];
+                       } else {
+                               $parameters = GeneralUtility::implodeArrayForUrl('', $this->getUrlParameters(['act' => $identifier]));
+                               $addParams = 'onclick="jumpToUrl(' . GeneralUtility::quoteJSvalue('?' . ltrim($parameters, '&')) . ');return false;"';
+                       }
+                       $menuDef[$identifier] = [
+                               'isActive' => $isActive,
+                               'label' => $configuration['label'],
+                               'url' => '#',
+                               'addParams' => $addParams,
+                               'before' => $configuration['displayBefore'],
+                               'after' => $configuration['displayAfter']
+                       ];
+               }
+
+               $menuDef = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($menuDef);
+
+               // if there is no active tab
+               if (!$this->displayedLinkHandler) {
+                       // empty the current link
+                       $this->currentLink = '';
+                       $this->currentLinkHandler = NULL;
+                       $this->currentLinkHandler = '';
+                       // select first tab
+                       reset($menuDef);
+                       $this->displayedLinkHandlerId = key($menuDef);
+                       $this->displayedLinkHandler = $this->linkHandlers[$this->displayedLinkHandlerId]['handlerInstance'];
+                       $menuDef[$this->displayedLinkHandlerId]['isActive'] = TRUE;
+               }
+
+               return $menuDef;
+       }
+
+       /**
+        * Get the allowed items or tabs
+        *
+        * @return string[]
+        */
+       protected function getAllowedItems() {
+               $allowedItems = array_keys($this->linkHandlers);
+
+               foreach ($this->hookObjects as $hookObject) {
+                       if (method_exists($hookObject, 'modifyAllowedItems')) {
+                               $allowedItems = $hookObject->modifyAllowedItems($allowedItems, $this->currentLink);
+                       }
+               }
+
+               // Initializing the action value, possibly removing blinded values etc:
+               $blindLinkOptions = isset($this->parameters['params']['blindLinkOptions'])
+                       ? GeneralUtility::trimExplode(',', $this->parameters['params']['blindLinkOptions'])
+                       : [];
+               $allowedItems = array_diff($allowedItems, $blindLinkOptions);
+
+               return $allowedItems;
+       }
+
+       /**
+        * Get the allowed link attributes
+        *
+        * @return string[]
+        */
+       protected function getAllowedLinkAttributes() {
+               $allowedLinkAttributes = $this->displayedLinkHandler->getLinkAttributes();
+
+               // Removing link fields if configured
+               $blindLinkFields = isset($this->parameters['params']['blindLinkFields'])
+                       ? GeneralUtility::trimExplode(',', $this->parameters['params']['blindLinkFields'], TRUE)
+                       : [];
+               $allowedLinkAttributes = array_diff($allowedLinkAttributes, $blindLinkFields);
+
+               return $allowedLinkAttributes;
+       }
+
+       /**
+        * Renders the link attributes for the selected link handler
+        *
+        * @return string
+        */
+       public function renderLinkAttributeFields() {
+
+               $fieldRenderingDefinitions = $this->getLinkAttributeFieldDefinitions();
+               $this->linkAttributeFields = $this->getAllowedLinkAttributes();
+
+               $content = '';
+               foreach ($this->linkAttributeFields as $attribute) {
+                       $content .= $fieldRenderingDefinitions[$attribute];
+               }
+
+               // add update button if appropriate
+               if ($this->currentLink && $this->displayedLinkHandler === $this->currentLinkHandler && $this->currentLinkHandler->isUpdateSupported()) {
+                       $content .= '
+                               <form action="" name="lparamsform" id="lparamsform">
+                                       <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkParams">
+                                       <tr><td>
+                                               <input class="btn btn-default t3-js-linkCurrent" type="submit" value="' . $this->getLanguageService()->getLL('update', TRUE) . '" />
+                                       </td></tr>
+                                       </table>
+                               </form><br /><br />';
+               }
+
+               return $content;
+       }
+
+       /**
+        * Create an array of link attribute field rendering definitions
+        *
+        * @return string[]
+        */
+       protected function getLinkAttributeFieldDefinitions() {
+               $lang = $this->getLanguageService();
+
+               $fieldRenderingDefinitions = [];
+               $fieldRenderingDefinitions['target'] = '
+                       <!--
+                               Selecting target for link:
+                       -->
+                               <form action="" name="ltargetform" id="ltargetform">
+                                       <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkTarget">
+                                               <tr>
+                                                       <td style="width: 96px;">' . $lang->getLL('target', TRUE) . ':</td>
+                                                       <td>
+                                                               <input type="text" name="ltarget" id="linkTarget" value="' . htmlspecialchars($this->linkAttributeValues['target']) . '" />
+                                                               <select name="ltarget_type" id="targetPreselect">
+                                                                       <option value=""></option>
+                                                                       <option value="_top">' . $lang->getLL('top', TRUE) . '</option>
+                                                                       <option value="_blank">' . $lang->getLL('newWindow', TRUE) . '</option>
+                                                               </select>
+                                                       </td>
+                                               </tr>
+                                       </table>
+                               </form>';
+
+               $fieldRenderingDefinitions['title'] = '
+                               <!--
+                                       Selecting title for link:
+                               -->
+                               <form action="" name="ltitleform" id="ltitleform">
+                                       <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkTitle">
+                                               <tr>
+                                                       <td style="width: 96px;">' . $lang->getLL('title', TRUE) . '</td>
+                                                       <td><input type="text" name="ltitle" class="typo3-link-input" value="' . htmlspecialchars($this->linkAttributeValues['title']) . '" /></td>
+                                               </tr>
+                                       </table>
+                               </form>
+                       ';
+
+               $fieldRenderingDefinitions['class'] = '
+                               <!--
+                                       Selecting class for link:
+                               -->
+                               <form action="" name="lclassform" id="lclassform">
+                                       <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkClass">
+                                               <tr>
+                                                       <td style="width: 96px;">' . $lang->getLL('class', TRUE) . '</td>
+                                                       <td><input type="text" name="lclass" class="typo3-link-input" value="' . htmlspecialchars($this->linkAttributeValues['class']) . '" /></td>
+                                               </tr>
+                                       </table>
+                               </form>
+                       ';
+
+               $fieldRenderingDefinitions['params'] = '
+                               <!--
+                                       Selecting params for link:
+                               -->
+                               <form action="" name="lparamsform" id="lparamsform">
+                                       <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkParams">
+                                               <tr>
+                                                       <td style="width: 96px;">' . $lang->getLL('params', TRUE) . '</td>
+                                                       <td><input type="text" name="lparams" class="typo3-link-input" value="' . htmlspecialchars($this->linkAttributeValues['params']) . '" /></td>
+                                               </tr>
+                                       </table>
+                               </form>
+                       ';
+
+               return $fieldRenderingDefinitions;
+       }
+
+       /**
+        * @param array $overrides
+        *
+        * @return array Array of parameters which have to be added to URLs
+        */
+       public function getUrlParameters(array $overrides = NULL) {
+               return [
+                       'act' => isset($overrides['act']) ? $overrides['act'] : $this->displayedLinkHandlerId
+               ];
+       }
+
+       /**
+        * @return string[] Array of body-tag attributes
+        */
+       protected function getBodyTagAttributes() {
+               $parameters = [];
+               $parameters['uid'] = $this->parameters['uid'];
+               $parameters['pid'] = $this->parameters['pid'];
+               $parameters['itemName'] = $this->parameters['itemName'];
+               $parameters['formName'] = $this->parameters['formName'];
+               $parameters['fieldChangeFunc'] = $this->parameters['fieldChangeFunc'];
+               $parameters['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($this->parameters['fieldChangeFunc']));
+               $parameters['params']['allowedExtensions'] = isset($this->parameters['params']['allowedExtensions']) ? $this->parameters['params']['allowedExtensions'] : '';
+               $parameters['params']['blindLinkOptions'] = isset($this->parameters['params']['blindLinkOptions']) ? $this->parameters['params']['blindLinkOptions'] : '';
+               $parameters['params']['blindLinkFields'] = isset($this->parameters['params']['blindLinkFields']) ? $this->parameters['params']['blindLinkFields']: '';
+               $addPassOnParams = GeneralUtility::implodeArrayForUrl('P', $parameters);
+
+               $attributes = $this->displayedLinkHandler->getBodyTagAttributes();
+               return array_merge(
+                       $attributes,
+                       [
+                               'data-this-script-url' => strpos($this->thisScript, '?') === FALSE ? $this->thisScript . '?' : $this->thisScript . '&',
+                               'data-url-parameters' => json_encode($this->getUrlParameters()),
+                               'data-parameters' => json_encode($this->parameters),
+                               'data-add-on-params' => $addPassOnParams,
+                               'data-link-attribute-fields' => json_encode($this->linkAttributeFields)
+                       ]
+               );
+       }
+
+       /**
+        * Determines whether submitted field change functions are valid
+        * and are coming from the system and not from an external abuse.
+        *
+        * @param bool $handleFlexformSections Whether to handle flexform sections differently
+        * @return bool Whether the submitted field change functions are valid
+        */
+       protected function areFieldChangeFunctionsValid($handleFlexformSections = FALSE) {
+               $result = FALSE;
+               if (isset($this->parameters['fieldChangeFunc']) && is_array($this->parameters['fieldChangeFunc']) && isset($this->parameters['fieldChangeFuncHash'])) {
+                       $matches = array();
+                       $pattern = '#\\[el\\]\\[(([^]-]+-[^]-]+-)(idx\\d+-)([^]]+))\\]#i';
+                       $fieldChangeFunctions = $this->parameters['fieldChangeFunc'];
+                       // Special handling of flexform sections:
+                       // Field change functions are modified in JavaScript, thus the hash is always invalid
+                       if ($handleFlexformSections && preg_match($pattern, $this->parameters['itemName'], $matches)) {
+                               $originalName = $matches[1];
+                               $cleanedName = $matches[2] . $matches[4];
+                               foreach ($fieldChangeFunctions as &$value) {
+                                       $value = str_replace($originalName, $cleanedName, $value);
+                               }
+                               unset($value);
+                       }
+                       $result = $this->parameters['fieldChangeFuncHash'] === GeneralUtility::hmac(serialize($fieldChangeFunctions));
+               }
+               return $result;
+       }
+
+       /**
+        * Encode a typolink via ajax
+        *
+        * This avoids to implement the encoding functionality again in JS for the browser.
+        *
+        * @param ServerRequestInterface $request
+        * @param ResponseInterface $response
+        * @return ResponseInterface
+        */
+       public static function encodeTypoLink(ServerRequestInterface $request, ResponseInterface $response) {
+               $typoLinkParts = $request->getQueryParams();
+               if (isset($typoLinkParts['params'])) {
+                       $typoLinkParts['additionalParams'] = $typoLinkParts['params'];
+                       unset($typoLinkParts['params']);
+               }
+
+               $typoLink = GeneralUtility::makeInstance(TypoLinkCodecService::class)->encode($typoLinkParts);
+
+               $response->getBody()->write(json_encode(['typoLink' => $typoLink]));
+               return $response;
+       }
+
+       /**
+        * Return the ID of current page
+        *
+        * @return int
+        */
+       protected function getCurrentPageId() {
+               $pageId = 0;
+               $P = $this->parameters;
+               if (isset($P['pid'])) {
+                       $pageId = $P['pid'];
+               } elseif (isset($P['itemName'])) {
+                       // parse data[<table>][<uid>]
+                       if (preg_match('~data\[([^]]*)\]\[([^]]*)\]~', $P['itemName'], $matches)) {
+                               $recordArray = BackendUtility::getRecord($matches['1'], $matches['2']);
+                               if (is_array($recordArray)) {
+                                       $pageId = $recordArray['pid'];
+                               }
+                       }
+               }
+               return $pageId;
+       }
+
+       /**
+        * @return array
+        */
+       public function getParameters() {
+               return $this->parameters;
+       }
+
+       /**
+        * @return string
+        */
+       public function getDisplayedLinkHandlerId() {
+               return $this->displayedLinkHandlerId;
+       }
+
+       /**
+        * @return string
+        */
+       public function getScriptUrl() {
+               return $this->thisScript;
+       }
+
+       /**
+        * @return LanguageService
+        */
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
+       /**
+        * @return BackendUserAuthentication
+        */
+       protected function getBackendUser() {
+               return $GLOBALS['BE_USER'];
+       }
+
+}
diff --git a/typo3/sysext/recordlist/Classes/LinkHandler/AbstractLinkHandler.php b/typo3/sysext/recordlist/Classes/LinkHandler/AbstractLinkHandler.php
new file mode 100644 (file)
index 0000000..0a2adcc
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+namespace TYPO3\CMS\Recordlist\LinkHandler;
+
+/*
+ * 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\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Lang\LanguageService;
+use TYPO3\CMS\Recordlist\Controller\LinkBrowserController;
+
+/**
+ * Base class for link handlers
+ *
+ * NOTE: This class should only be used internally. Extensions must implement the LinkHandlerInterface.
+ */
+abstract class AbstractLinkHandler {
+
+       /**
+        * Available additional link attributes
+        *
+        * 'rel' only works in RTE, still we have to declare support for it.
+        *
+        * @var string[]
+        */
+       protected $linkAttributes = [ 'target', 'title', 'class', 'params', 'rel' ];
+
+       /**
+        * @var bool
+        */
+       protected $updateSupported = TRUE;
+
+       /**
+        * @var LinkBrowserController
+        */
+       protected $linkBrowser;
+
+       /**
+        * @var IconFactory
+        */
+       protected $iconFactory;
+
+       /**
+        * Constructor
+        */
+       public function __construct() {
+       }
+
+       /**
+        * Initialize the handler
+        *
+        * @param LinkBrowserController $linkBrowser
+        * @param string $identifier
+        * @param array $configuration Page TSconfig
+        *
+        * @return void
+        */
+       public function initialize(LinkBrowserController $linkBrowser, $identifier, array $configuration) {
+               $this->linkBrowser = $linkBrowser;
+               $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+       }
+
+       /**
+        * @return array
+        */
+       public function getLinkAttributes() {
+               return $this->linkAttributes;
+       }
+
+       /**
+        * Return TRUE if the handler supports to update a link.
+        *
+        * This is useful for e.g. file or page links, when only attributes are changed.
+        *
+        * @return bool
+        */
+       public function isUpdateSupported() {
+               return $this->updateSupported;
+       }
+
+       /**
+        * @return void
+        */
+       protected function setTemporaryDbMounts() {
+               $backendUser = $this->getBackendUser();
+
+               // Clear temporary DB mounts
+               $tmpMount = GeneralUtility::_GET('setTempDBmount');
+               if (isset($tmpMount)) {
+                       $backendUser->setAndSaveSessionData('pageTree_temporaryMountPoint', (int)$tmpMount);
+               }
+               // Set temporary DB mounts
+               $alternativeWebmountPoint = (int)$backendUser->getSessionData('pageTree_temporaryMountPoint');
+               if ($alternativeWebmountPoint) {
+                       $alternativeWebmountPoint = GeneralUtility::intExplode(',', $alternativeWebmountPoint);
+                       $backendUser->setWebmounts($alternativeWebmountPoint);
+               } else {
+                       // Setting alternative browsing mounts (ONLY local to browse_links.php this script so they stay "read-only")
+                       $alternativeWebmountPoints = trim($backendUser->getTSConfigVal('options.pageTree.altElementBrowserMountPoints'));
+                       $appendAlternativeWebmountPoints = $backendUser->getTSConfigVal('options.pageTree.altElementBrowserMountPoints.append');
+                       if ($alternativeWebmountPoints) {
+                               $alternativeWebmountPoints = GeneralUtility::intExplode(',', $alternativeWebmountPoints);
+                               $this->getBackendUser()->setWebmounts($alternativeWebmountPoints, $appendAlternativeWebmountPoints);
+                       }
+               }
+       }
+
+       /**
+        * @return BackendUserAuthentication
+        */
+       protected function getBackendUser() {
+               return $GLOBALS['BE_USER'];
+       }
+
+       /**
+        * @return LanguageService
+        */
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
+}
diff --git a/typo3/sysext/recordlist/Classes/LinkHandler/FileLinkHandler.php b/typo3/sysext/recordlist/Classes/LinkHandler/FileLinkHandler.php
new file mode 100644 (file)
index 0000000..d792212
--- /dev/null
@@ -0,0 +1,327 @@
+<?php
+namespace TYPO3\CMS\Recordlist\LinkHandler;
+
+/*
+ * 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\ServerRequestInterface;
+use TYPO3\CMS\Backend\Tree\View\ElementBrowserFolderTreeView;
+use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\FileInterface;
+use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Resource\ResourceInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\StringUtility;
+use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
+use TYPO3\CMS\Recordlist\View\FolderUtilityRenderer;
+
+/**
+ * Link handler for files
+ */
+class FileLinkHandler extends AbstractLinkHandler implements LinkHandlerInterface, LinkParameterProviderInterface {
+
+       /**
+        * Parts of the current link
+        *
+        * @var array
+        */
+       protected $linkParts = [];
+
+       /**
+        * @var string
+        */
+       protected $expectedClass = File::class;
+
+       /**
+        * @var string
+        */
+       protected $mode = 'file';
+
+       /**
+        * @var string
+        */
+       protected $expandFolder;
+
+       /**
+        * @var string
+        */
+       protected $additionalFolderClass = '';
+
+       /**
+        * Checks if this is the handler for the given link
+        *
+        * The handler may store this information locally for later usage.
+        *
+        * @param array $linkParts Link parts as returned from TypoLinkCodecService
+        *
+        * @return bool
+        */
+       public function canHandleLink(array $linkParts) {
+               if (!$linkParts['url']) {
+                       return FALSE;
+               }
+               $url = rawurldecode($linkParts['url']);
+
+               if (StringUtility::beginsWith($url, 'file:') && !StringUtility::beginsWith($url, 'file://')) {
+                       $rel = substr($url, 5);
+                       try {
+                               // resolve FAL-api "file:UID-of-sys_file-record" and "file:combined-identifier"
+                               $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($rel);
+                               if (is_a($fileOrFolderObject, $this->expectedClass)) {
+                                       $this->linkParts = $linkParts;
+                                       $this->linkParts['url'] = $rel;
+                                       $this->linkParts['name'] = $fileOrFolderObject->getName();
+                                       return TRUE;
+                               }
+                       } catch (FileDoesNotExistException $e) {
+                       }
+               }
+               return FALSE;
+       }
+
+       /**
+        * Format the current link for HTML output
+        *
+        * @return string
+        */
+       public function formatCurrentUrl() {
+               return $this->linkParts['name'];
+       }
+
+       /**
+        * Render the link handler
+        *
+        * @param ServerRequestInterface $request
+        *
+        * @return string
+        */
+       public function render(ServerRequestInterface $request) {
+               GeneralUtility::makeInstance(PageRenderer::class)->loadRequireJsModule('TYPO3/CMS/Recordlist/FileLinkHandler');
+
+               $this->expandFolder = isset($request->getQueryParams()['expandFolder']) ? $request->getQueryParams()['expandFolder'] : null;
+               if (!empty($this->linkParts) && !isset($this->expandFolder)) {
+                       $this->expandFolder = $this->linkParts['url'];
+               }
+
+               /** @var ElementBrowserFolderTreeView $folderTree */
+               $folderTree = GeneralUtility::makeInstance(ElementBrowserFolderTreeView::class);
+               $folderTree->setLinkParameterProvider($this);
+               $tree = $folderTree->getBrowsableTree();
+
+               // Create upload/create folder forms, if a path is given
+               $selectedFolder = FALSE;
+               if ($this->expandFolder) {
+                       $fileOrFolderObject = NULL;
+                       try {
+                               $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($this->expandFolder);
+                       } catch (\Exception $e) {
+                               // No path is selected
+                       }
+
+                       if ($fileOrFolderObject instanceof Folder) {
+                               // It's a folder
+                               $selectedFolder = $fileOrFolderObject;
+                       } elseif ($fileOrFolderObject instanceof FileInterface) {
+                               // It's a file
+                               try {
+                                       $selectedFolder = $fileOrFolderObject->getParentFolder();
+                               } catch (\Exception $e) {
+                                       // Accessing the parent folder failed for some reason. e.g. permissions
+                               }
+                       }
+               }
+
+               $backendUser = $this->getBackendUser();
+               // If no folder is selected, get the user's default upload folder
+               if (!$selectedFolder) {
+                       try {
+                               $selectedFolder = $backendUser->getDefaultUploadFolder();
+                       } catch (\Exception $e) {
+                               // The configured default user folder does not exist
+                       }
+               }
+               // Build the file upload and folder creation form
+               $uploadForm = '';
+               $createFolder = '';
+               $content = '';
+               if ($selectedFolder) {
+                       $folderUtilityRenderer = GeneralUtility::makeInstance(FolderUtilityRenderer::class, $this);
+                       $uploadForm = $this->mode === 'file' ? $folderUtilityRenderer->uploadForm($selectedFolder, []) : '';
+                       $createFolder = $folderUtilityRenderer->createFolder($selectedFolder);
+               }
+               // Insert the upload form on top, if so configured
+               if ($backendUser->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
+                       $content .= $uploadForm;
+               }
+
+               // Render the filelist if there is a folder selected
+               $files = '';
+               if ($selectedFolder) {
+                       $parameters = $this->linkBrowser->getUrlParameters();
+                       $allowedExtensions = isset($parameters['allowedExtensions']) ? $parameters['allowedExtensions'] : '';
+                       $files = $this->expandFolder($selectedFolder, $allowedExtensions);
+               }
+               // Create folder tree:
+               $content .= '
+                               <!--
+                                       Wrapper table for folder tree / file/folder list:
+                               -->
+                                               <table border="0" cellpadding="0" cellspacing="0" id="typo3-linkFiles">
+                                                       <tr>
+                                                               <td class="c-wCell" valign="top"><h3>' . $this->getLanguageService()->getLL('folderTree') . ':</h3>' . $tree . '</td>
+                                                               <td class="c-wCell" valign="top">' . $files . '</td>
+                                                       </tr>
+                                               </table>
+                                               ';
+               // Adding create folder + upload form if applicable
+               if (!$backendUser->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
+                       $content .= $uploadForm;
+               }
+               $content .=  '<br />' . $createFolder . '<br />';
+               return $content;
+       }
+
+       /**
+        * For RTE: This displays all files from folder. No thumbnails shown
+        *
+        * @param Folder $folder The folder path to expand
+        * @param string $extensionList List of file extensions to show
+        * @return string HTML output
+        */
+       public function expandFolder(Folder $folder, $extensionList = '') {
+               if (!$folder->checkActionPermission('read')) {
+                       return '';
+               }
+               $out = '<h3>' . htmlspecialchars($this->getTitle()) . ':</h3>';
+
+               // Create header element; The folder from which files are listed.
+               $titleLen = (int)$this->getBackendUser()->uc['titleLen'];
+               $folderIcon = $this->iconFactory->getIconForResource($folder, Icon::SIZE_SMALL);
+               $folderIcon .= htmlspecialchars(GeneralUtility::fixed_lgd_cs($folder->getIdentifier(), $titleLen));
+
+               $currentIdentifier = !empty($this->linkParts) ? $this->linkParts['url'] : '';
+               $selected = $currentIdentifier === $folder->getCombinedIdentifier() ? $this->additionalFolderClass : '';
+               $out .= '
+                       <span class="' . $selected . '" title="' . htmlspecialchars($folder->getIdentifier()) . '">
+                               ' . $folderIcon . '
+                       </span>
+                       ';
+               // Get files from the folder:
+               $folderContent = $this->getFolderContent($folder, $extensionList);
+               if (!empty($folderContent)) {
+                       $out .= '<ul class="list-tree list-tree-root">';
+                       foreach ($folderContent as $fileOrFolderObject) {
+                               list($fileIdentifier, $icon) = $this->renderItem($fileOrFolderObject);
+                               $selected = $currentIdentifier === $fileIdentifier ? ' class="active"' : ''; // @todo this class has no styling yet!
+                               $out .=
+                                       '<li' . $selected . '>
+                                               <a href="#" class="t3-js-fileLink" title="' . htmlspecialchars($fileOrFolderObject->getName()) . '" data-file="file:' . htmlspecialchars($fileIdentifier) . '">
+                                                       ' .     $icon . '
+                                                       ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileOrFolderObject->getName(), $titleLen)) . '
+                                               </a>
+                                       </li>';
+                       }
+                       $out .= '</ul>';
+               }
+               return $out;
+       }
+
+       /**
+        * @return string
+        */
+       protected function getTitle() {
+               return $this->getLanguageService()->getLL('files');
+       }
+
+       /**
+        * @param Folder $folder
+        * @param string $extensionList
+        *
+        * @return FileInterface[]
+        */
+       protected function getFolderContent(Folder $folder, $extensionList) {
+               if ($extensionList !== '') {
+                       /** @var FileExtensionFilter $filter */
+                       $filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
+                       $filter->setAllowedFileExtensions($extensionList);
+                       $folder->setFileAndFolderNameFilters(array(array($filter, 'filterFileList')));
+               }
+               return $folder->getFiles();
+       }
+
+       /**
+        * Renders a single item displayed in the current folder
+        *
+        * @param ResourceInterface $fileOrFolderObject
+        *
+        * @return array
+        * @throws \InvalidArgumentException
+        */
+       protected function renderItem(ResourceInterface $fileOrFolderObject) {
+               if (!$fileOrFolderObject instanceof File) {
+                       throw new \InvalidArgumentException('Expected File object, got "' . get_class($fileOrFolderObject) . '" object.', 1443651368);
+               }
+               $fileIdentifier = $fileOrFolderObject->getUid();
+               // Get size and icon:
+               $size = ' (' . GeneralUtility::formatSize($fileOrFolderObject->getSize()) . 'bytes)';
+               $icon = '<span title="' . htmlspecialchars($fileOrFolderObject->getName() . $size) . '">'
+                       . $this->iconFactory->getIconForResource($fileOrFolderObject, Icon::SIZE_SMALL)
+                       . '</span>';
+               return [$fileIdentifier, $icon];
+       }
+
+       /**
+        * @return string[] Array of body-tag attributes
+        */
+       public function getBodyTagAttributes() {
+               return [
+                       'data-current-file' => empty($this->linkParts) ? '' : 'file:' . $this->linkParts['url']
+               ];
+       }
+
+       /**
+        * @param array $values Array of values to include into the parameters or which might influence the parameters
+        *
+        * @return string[] Array of parameters which have to be added to URLs
+        */
+       public function getUrlParameters(array $values) {
+               $parameters = [
+                       'expandFolder' => isset($values['identifier']) ? $values['identifier'] : (string)$this->expandFolder
+               ];
+               return array_merge($this->linkBrowser->getUrlParameters($values), $parameters);
+       }
+
+       /**
+        * @param array $values Values to be checked
+        *
+        * @return bool Returns TRUE if the given values match the currently selected item
+        */
+       public function isCurrentlySelectedItem(array $values) {
+               return FALSE;
+       }
+
+       /**
+        * Returns the URL of the current script
+        *
+        * @return string
+        */
+       public function getScriptUrl() {
+               return $this->linkBrowser->getScriptUrl();
+       }
+
+}
diff --git a/typo3/sysext/recordlist/Classes/LinkHandler/FolderLinkHandler.php b/typo3/sysext/recordlist/Classes/LinkHandler/FolderLinkHandler.php
new file mode 100644 (file)
index 0000000..7eaaa7c
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+namespace TYPO3\CMS\Recordlist\LinkHandler;
+
+/*
+ * 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\Imaging\Icon;
+use TYPO3\CMS\Core\Resource\FileInterface;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\InaccessibleFolder;
+use TYPO3\CMS\Core\Resource\ResourceInterface;
+
+/**
+ * Link handler for folder links
+ */
+class FolderLinkHandler extends FileLinkHandler {
+
+       /**
+        * @var string
+        */
+       protected $mode = 'folder';
+
+       /**
+        * @var string
+        */
+       protected $additionalFolderClass = 'bg-success';
+
+       /**
+        * @var string
+        */
+       protected $expectedClass = Folder::class;
+
+       /**
+        * @return string
+        */
+       protected function getTitle() {
+               return $this->getLanguageService()->getLL('folders');
+       }
+
+       /**
+        * @param Folder $folder
+        * @param string $extensionList
+        * @return FileInterface[]
+        */
+       protected function getFolderContent(Folder $folder, $extensionList) {
+               return $folder->getSubfolders();
+       }
+
+       /**
+        * Renders a single item displayed in the current folder
+        *
+        * @param ResourceInterface $fileOrFolderObject
+        *
+        * @return array
+        * @throws \InvalidArgumentException
+        */
+       protected function renderItem(ResourceInterface $fileOrFolderObject) {
+               if (!$fileOrFolderObject instanceof Folder) {
+                       throw new \InvalidArgumentException('Expected Folder object, got "' . get_class($fileOrFolderObject) . '" object.', 1443651369);
+               }
+               $fileIdentifier = $fileOrFolderObject->getCombinedIdentifier();
+               $overlay = NULL;
+               if ($fileOrFolderObject instanceof InaccessibleFolder) {
+                       $overlay = array('status-overlay-locked' => array());
+               }
+               $icon = '<span title="' . htmlspecialchars($fileOrFolderObject->getName()) . '">'
+                       . $this->iconFactory->getIcon('apps-filetree-folder-default', Icon::SIZE_SMALL, $overlay)->render()
+                       . '</span>';
+               return [$fileIdentifier, $icon];
+       }
+
+}
diff --git a/typo3/sysext/recordlist/Classes/LinkHandler/LinkHandlerInterface.php b/typo3/sysext/recordlist/Classes/LinkHandler/LinkHandlerInterface.php
new file mode 100644 (file)
index 0000000..42e349d
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+namespace TYPO3\CMS\Recordlist\LinkHandler;
+
+/*
+ * 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\ServerRequestInterface;
+use TYPO3\CMS\Recordlist\Controller\LinkBrowserController;
+
+/**
+ * Interface for link handlers displayed in the LinkBrowser
+ */
+interface LinkHandlerInterface {
+
+       /**
+        * @return array
+        */
+       public function getLinkAttributes();
+
+       /**
+        * Initialize the handler
+        *
+        * @param LinkBrowserController $linkBrowser
+        * @param string $identifier
+        * @param array $configuration Page TSconfig
+        *
+        * @return void
+        */
+       public function initialize(LinkBrowserController $linkBrowser, $identifier, array $configuration);
+
+       /**
+        * Checks if this is the handler for the given link
+        *
+        * The handler may store this information locally for later usage.
+        *
+        * @param array $linkParts Link parts as returned from TypoLinkCodecService
+        *
+        * @return bool
+        */
+       public function canHandleLink(array $linkParts);
+
+       /**
+        * Format the current link for HTML output
+        *
+        * @return string
+        */
+       public function formatCurrentUrl();
+
+       /**
+        * Render the link handler
+        *
+        * @param ServerRequestInterface $request
+        *
+        * @return string
+        */
+       public function render(ServerRequestInterface $request);
+
+       /**
+        * Return TRUE if the handler supports to update a link.
+        *
+        * This is useful for file or page links, when only attributes are changed.
+        *
+        * @return bool
+        */
+       public function isUpdateSupported();
+
+       /**
+        * @return string[] Array of body-tag attributes
+        */
+       public function getBodyTagAttributes();
+
+}
diff --git a/typo3/sysext/recordlist/Classes/LinkHandler/MailLinkHandler.php b/typo3/sysext/recordlist/Classes/LinkHandler/MailLinkHandler.php
new file mode 100644 (file)
index 0000000..5b7f8c8
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+namespace TYPO3\CMS\Recordlist\LinkHandler;
+
+/*
+ * 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\ServerRequestInterface;
+use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Recordlist\Controller\LinkBrowserController;
+
+/**
+ * Link handler for email links
+ */
+class MailLinkHandler extends AbstractLinkHandler implements LinkHandlerInterface {
+
+       /**
+        * Parts of the current link
+        *
+        * @var array
+        */
+       protected $linkParts = [];
+
+       /**
+        * We don't support updates since there is no difference to simply set the link again.
+        *
+        * @var bool
+        */
+       protected $updateSupported = FALSE;
+
+       /**
+        * Constructor
+        */
+       public function __construct() {
+               parent::__construct();
+               // remove unsupported link attribute
+               unset($this->linkAttributes[array_search('target', $this->linkAttributes, TRUE)]);
+       }
+
+       /**
+        * Checks if this is the handler for the given link
+        *
+        * The handler may store this information locally for later usage.
+        *
+        * @param array $linkParts Link parts as returned from TypoLinkCodecService
+        *
+        * @return bool
+        */
+       public function canHandleLink(array $linkParts) {
+               if ($linkParts['url'] && strpos($linkParts['url'], '@')) {
+                       $this->linkParts = $linkParts;
+                       return TRUE;
+               }
+               return FALSE;
+       }
+
+       /**
+        * Format the current link for HTML output
+        *
+        * @return string
+        */
+       public function formatCurrentUrl() {
+               return $this->linkParts['url'];
+       }
+
+       /**
+        * Render the link handler
+        *
+        * @param ServerRequestInterface $request
+        *
+        * @return string
+        */
+       public function render(ServerRequestInterface $request) {
+               GeneralUtility::makeInstance(PageRenderer::class)->loadRequireJsModule('TYPO3/CMS/Recordlist/MailLinkHandler');
+
+               $lang = $this->getLanguageService();
+               $extUrl = '
+                       <!--
+                               Enter mail address:
+                       -->
+                       <form action="" id="lmailform">
+                               <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkMail">
+                                       <tr>
+                                               <td style="width: 96px;">' . $lang->getLL('emailAddress', TRUE) . ':</td>
+                                               <td>
+                                                       <input type="text" name="lemail" size="20" value="'
+                                                               . htmlspecialchars(!empty($this->linkParts) ? $this->linkParts['url'] : '')
+                                                               . '" />
+                                                       <input class="btn btn-default" type="submit" value="' . $lang->getLL('setLink', TRUE) . '" />
+                                               </td>
+                                       </tr>
+                               </table>
+                       </form>';
+               return $extUrl;
+       }
+
+       /**
+        * @return string[] Array of body-tag attributes
+        */
+       public function getBodyTagAttributes() {
+               return [];
+       }
+
+}
diff --git a/typo3/sysext/recordlist/Classes/LinkHandler/PageLinkHandler.php b/typo3/sysext/recordlist/Classes/LinkHandler/PageLinkHandler.php
new file mode 100644 (file)
index 0000000..3237338
--- /dev/null
@@ -0,0 +1,308 @@
+<?php
+namespace TYPO3\CMS\Recordlist\LinkHandler;
+
+/*
+ * 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\ServerRequestInterface;
+use TYPO3\CMS\Backend\Tree\View\ElementBrowserPageTreeView;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Database\DatabaseConnection;
+use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
+
+/**
+ * Link handler for page (and content) links
+ */
+class PageLinkHandler extends AbstractLinkHandler implements LinkHandlerInterface, LinkParameterProviderInterface {
+
+       /**
+        * @var int
+        */
+       protected $expandPage = 0;
+
+       /**
+        * Parts of the current link
+        *
+        * @var array
+        */
+       protected $linkParts = [];
+
+       /**
+        * Checks if this is the handler for the given link
+        *
+        * The handler may store this information locally for later usage.
+        *
+        * @param array $linkParts Link parts as returned from TypoLinkCodecService
+        *
+        * @return bool
+        */
+       public function canHandleLink(array $linkParts) {
+               if (!$linkParts['url']) {
+                       return FALSE;
+               }
+
+               $id = $linkParts['url'];
+               $parts = explode('#', $id);
+               if (count($parts) > 1) {
+                       $id = $parts[0];
+                       $anchor = $parts[1];
+               } else {
+                       $anchor = '';
+               }
+               // Checking if the id-parameter is an alias.
+               if (!MathUtility::canBeInterpretedAsInteger($id)) {
+                       $records = BackendUtility::getRecordsByField('pages', 'alias', $id);
+                       if (empty($records)) {
+                               return FALSE;
+                       }
+                       $id = (int)$records[0]['uid'];
+               }
+               $pageRow = BackendUtility::getRecordWSOL('pages', $id);
+               if (!$pageRow) {
+                       return FALSE;
+               }
+
+               $this->linkParts = $linkParts;
+               $this->linkParts['pageid'] = $id;
+               $this->linkParts['anchor'] = $anchor;
+
+               return TRUE;
+       }
+
+       /**
+        * Format the current link for HTML output
+        *
+        * @return string
+        */
+       public function formatCurrentUrl() {
+               $lang = $this->getLanguageService();
+               $titleLen = (int)$this->getBackendUser()->uc['titleLen'];
+
+               $id = $this->linkParts['pageid'];
+               $pageRow = BackendUtility::getRecordWSOL('pages', $id);
+
+               return $lang->getLL('page', TRUE)
+                       . ' \'' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($pageRow['title'], $titleLen)) . '\''
+                       . ' (ID:' . $id . ($this->linkParts['anchor'] ? ', #' . $this->linkParts['anchor'] : '') . ')';
+       }
+
+       /**
+        * Render the link handler
+        *
+        * @param ServerRequestInterface $request
+        *
+        * @return string
+        */
+       public function render(ServerRequestInterface $request) {
+               GeneralUtility::makeInstance(PageRenderer::class)->loadRequireJsModule('TYPO3/CMS/Recordlist/PageLinkHandler');
+
+               $this->expandPage = isset($request->getQueryParams()['expandPage']) ? (int)$request->getQueryParams()['expandPage'] : 0;
+               $this->setTemporaryDbMounts();
+
+               $backendUser = $this->getBackendUser();
+
+               /** @var ElementBrowserPageTreeView $pageTree */
+               $pageTree = GeneralUtility::makeInstance(ElementBrowserPageTreeView::class);
+               $pageTree->setLinkParameterProvider($this);
+               $pageTree->ext_showPageId = (bool)$backendUser->getTSConfigVal('options.pageTree.showPageIdWithTitle');
+               $pageTree->ext_showNavTitle = (bool)$backendUser->getTSConfigVal('options.pageTree.showNavTitle');
+               $pageTree->addField('nav_title');
+               $tree = $pageTree->getBrowsableTree();
+
+               return '
+
+                               <!--
+                                       Wrapper table for page tree / record list:
+                               -->
+                               <table border="0" cellpadding="0" cellspacing="0" id="typo3-linkPages">
+                                       <tr>
+                                               <td class="c-wCell" valign="top"><h3>' . $this->getLanguageService()->getLL('pageTree') . ':</h3>'
+                                                       . $this->getTemporaryTreeMountCancelNotice() . $tree . '</td>
+                                               <td class="c-wCell" valign="top">' . $this->expandPage($this->expandPage) . '</td>
+                                       </tr>
+                               </table>';
+       }
+
+       /**
+        * This displays all content elements on a page and lets you create a link to the element.
+        *
+        * @param int $expPageId Page uid to expand
+        *
+        * @return string HTML output. Returns content only if the ->expandPage value is set (pointing to a page uid to show tt_content records from ...)
+        */
+       public function expandPage($expPageId) {
+               // If there is an anchor value (content element reference) in the element reference, then force an ID to expand:
+               if (!$expPageId && isset($this->linkParts['anchor'])) {
+                       // Set to the current link page id.
+                       $expPageId = $this->linkParts['pageid'];
+               }
+               // Draw the record list IF there is a page id to expand:
+               if (!$expPageId || !MathUtility::canBeInterpretedAsInteger($expPageId) || !$this->getBackendUser()->isInWebMount($expPageId)) {
+                       return '';
+               }
+
+               // Set header:
+               $out = '<h3>' . $this->getLanguageService()->getLL('contentElements') . ':</h3>';
+               // Create header for listing, showing the page title/icon:
+               $mainPageRec = BackendUtility::getRecordWSOL('pages', $expPageId);
+               $db = $this->getDatabaseConnection();
+               $out .= '
+                       <ul class="list-tree list-tree-root list-tree-root-clean">
+                               <li class="list-tree-control-open">
+                                       <span class="list-tree-group">
+                                               <span class="list-tree-icon">' . $this->iconFactory->getIconForRecord('pages', $mainPageRec, Icon::SIZE_SMALL)->render() . '</span>
+                                               <span class="list-tree-title">' . htmlspecialchars(BackendUtility::getRecordTitle('pages', $mainPageRec, TRUE)) . '</span>
+                                       </span>
+                                       <ul>
+                       ';
+
+               // Look up tt_content elements from the expanded page:
+               $res = $db->exec_SELECTquery(
+                       'uid,header,hidden,starttime,endtime,fe_group,CType,colPos,bodytext',
+                       'tt_content',
+                       'pid=' . (int)$expPageId . BackendUtility::deleteClause('tt_content')
+                       . BackendUtility::versioningPlaceholderClause('tt_content'),
+                       '',
+                       'colPos,sorting'
+               );
+               // Traverse list of records:
+               $c = 0;
+               while ($row = $db->sql_fetch_assoc($res)) {
+                       $c++;
+                       $icon = $this->iconFactory->getIconForRecord('tt_content', $row, Icon::SIZE_SMALL)->render();
+                       $selected = '';
+                       if (!empty($this->linkParts) && (int)$this->linkParts['anchor'] === (int)$row['uid']) {
+                               $selected = ' class="active"';
+                       }
+                       // Putting list element HTML together:
+                       $out .= '
+                               <li' . $selected . '>
+                                       <span class="list-tree-group">
+                                               <span class="list-tree-icon">
+                                                       ' . $icon . '
+                                               </span>
+                                               <span class="list-tree-title">
+                                                       <a href="#" class="t3-js-pageLink" data-id="' . (int)$expPageId . '" data-anchor="#' . (int)$row['uid'] . '">
+                                                               ' . htmlspecialchars(BackendUtility::getRecordTitle('tt_content', $row, TRUE)) . '
+                                                       </a>
+                                               </span>
+                                       </span>
+                               </li>
+                               ';
+               }
+               $out .= '
+                                       </ul>
+                               </li>
+                       </ul>
+                       ';
+
+               return $out;
+       }
+
+       /**
+        * Check if a temporary tree mount is set and return a cancel button
+        *
+        * @return string HTML code
+        */
+       protected function getTemporaryTreeMountCancelNotice() {
+               if ((int)$this->getBackendUser()->getSessionData('pageTree_temporaryMountPoint') === 0) {
+                       return '';
+               }
+               $link = '<a href="' . htmlspecialchars(GeneralUtility::linkThisScript(array('setTempDBmount' => 0))) . '">'
+                       . $this->getLanguageService()->sl('LLL:EXT:lang/locallang_core.xlf:labels.temporaryDBmount', TRUE) . '</a>';
+
+               return GeneralUtility::makeInstance(FlashMessage::class, $link, '', FlashMessage::INFO)->render();
+       }
+
+       /**
+        * @return void
+        */
+       protected function setTemporaryDbMounts() {
+               $backendUser = $this->getBackendUser();
+
+               // Clear temporary DB mounts
+               $tmpMount = GeneralUtility::_GET('setTempDBmount');
+               if (isset($tmpMount)) {
+                       $backendUser->setAndSaveSessionData('pageTree_temporaryMountPoint', (int)$tmpMount);
+               }
+               // Set temporary DB mounts
+               $alternativeWebmountPoint = (int)$backendUser->getSessionData('pageTree_temporaryMountPoint');
+               if ($alternativeWebmountPoint) {
+                       $alternativeWebmountPoint = GeneralUtility::intExplode(',', $alternativeWebmountPoint);
+                       $backendUser->setWebmounts($alternativeWebmountPoint);
+               } else {
+                       // Setting alternative browsing mounts (ONLY local to browse_links.php this script so they stay "read-only")
+                       $alternativeWebmountPoints = trim($backendUser->getTSConfigVal('options.pageTree.altElementBrowserMountPoints'));
+                       $appendAlternativeWebmountPoints = $backendUser->getTSConfigVal('options.pageTree.altElementBrowserMountPoints.append');
+                       if ($alternativeWebmountPoints) {
+                               $alternativeWebmountPoints = GeneralUtility::intExplode(',', $alternativeWebmountPoints);
+                               $this->getBackendUser()->setWebmounts($alternativeWebmountPoints, $appendAlternativeWebmountPoints);
+                       }
+               }
+       }
+
+       /**
+        * @return string[] Array of body-tag attributes
+        */
+       public function getBodyTagAttributes() {
+               if (empty($this->linkParts)) {
+                       return [];
+               }
+               return [
+                       'data-current-link' => $this->linkParts['pageid'] . '#' . $this->linkParts['anchor']
+               ];
+       }
+
+       /**
+        * @param array $values Array of values to include into the parameters or which might influence the parameters
+        *
+        * @return string[] Array of parameters which have to be added to URLs
+        */
+       public function getUrlParameters(array $values) {
+               $parameters = [
+                       'expandPage' => isset($values['pid']) ? (int)$values['pid'] : $this->expandPage
+               ];
+               return array_merge($this->linkBrowser->getUrlParameters($values), $parameters);
+       }
+
+       /**
+        * @param array $values Values to be checked
+        *
+        * @return bool Returns TRUE if the given values match the currently selected item
+        */
+       public function isCurrentlySelectedItem(array $values) {
+               return !empty($this->linkParts) && (int)$this->linkParts['pageid'] === (int)$values['pid'];
+       }
+
+       /**
+        * Returns the URL of the current script
+        *
+        * @return string
+        */
+       public function getScriptUrl() {
+               return $this->linkBrowser->getScriptUrl();
+       }
+
+       /**
+        * @return DatabaseConnection
+        */
+       protected function getDatabaseConnection() {
+               return $GLOBALS['TYPO3_DB'];
+       }
+
+}
diff --git a/typo3/sysext/recordlist/Classes/LinkHandler/UrlLinkHandler.php b/typo3/sysext/recordlist/Classes/LinkHandler/UrlLinkHandler.php
new file mode 100644 (file)
index 0000000..e46a48c
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+namespace TYPO3\CMS\Recordlist\LinkHandler;
+
+/*
+ * 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\ServerRequestInterface;
+use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Recordlist\Controller\LinkBrowserController;
+
+/**
+ * Link handler for external URLs
+ */
+class UrlLinkHandler extends AbstractLinkHandler implements LinkHandlerInterface {
+
+       /**
+        * Parts of the current link
+        *
+        * @var array
+        */
+       protected $linkParts = [];
+
+       /**
+        * We don't support updates since there is no difference to simply set the link again.
+        *
+        * @var bool
+        */
+       protected $updateSupported = FALSE;
+
+       /**
+        * Constructor
+        */
+       public function __construct() {
+               parent::__construct();
+               // remove unsupported link attribute
+               unset($this->linkAttributes[array_search('params', $this->linkAttributes, TRUE)]);
+       }
+
+       /**
+        * Checks if this is the handler for the given link
+        *
+        * The handler may store this information locally for later usage.
+        *
+        * @param array $linkParts Link parts as returned from TypoLinkCodecService
+        *
+        * @return bool
+        */
+       public function canHandleLink(array $linkParts) {
+               if (!$linkParts['url']) {
+                       return FALSE;
+               }
+               if (strpos($linkParts['url'], '://') === FALSE) {
+                       $linkParts['url'] = 'http://' . $linkParts['url'];
+               }
+               $this->linkParts = $linkParts;
+
+               return TRUE;
+       }
+
+       /**
+        * Format the current link for HTML output
+        *
+        * @return string
+        */
+       public function formatCurrentUrl() {
+               return $this->linkParts['url'];
+       }
+
+       /**
+        * Render the link handler
+        *
+        * @param ServerRequestInterface $request
+        *
+        * @return string
+        */
+       public function render(ServerRequestInterface $request) {
+               GeneralUtility::makeInstance(PageRenderer::class)->loadRequireJsModule('TYPO3/CMS/Recordlist/UrlLinkHandler');
+
+               $extUrl = '
+                               <!--
+                                       Enter External URL:
+                               -->
+                                       <form action="" id="lurlform">
+                                               <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkURL">
+                                                       <tr>
+                                                               <td style="width: 96px;">URL:</td>
+                                                               <td>
+                                                                       <input type="text" name="lurl" size="30" value="'
+                                                                               . htmlspecialchars(!empty($this->linkParts) ? $this->linkParts['url'] : 'http://')
+                                                                               . '" /> '
+                                                                       . '<input class="btn btn-default" type="submit" value="' . $this->getLanguageService()->getLL('setLink', TRUE) . '" />
+                                                               </td>
+                                                       </tr>
+                                               </table>
+                                       </form>';
+               return $extUrl;
+       }
+
+       /**
+        * @return string[] Array of body-tag attributes
+        */
+       public function getBodyTagAttributes() {
+               return [];
+       }
+
+}
index f340926..aab1d0b 100644 (file)
@@ -935,7 +935,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList {
 
                $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
 
-               return BackendUtility::getModuleUrl(GeneralUtility::_GP('M'), $urlParameters);
+               return $this->getThisScript() . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&');
        }
 
        /**
index de7bf59..a1038d1 100644 (file)
@@ -820,7 +820,7 @@ class DatabaseRecordList extends AbstractDatabaseRecordList {
                }
                // Add row to CSV list:
                if ($this->csvOutput) {
-                       $this->addToCSV($row, $table);
+                       $this->addToCSV($row);
                }
                // Add classes to table cells
                $this->addElement_tdCssClass[$titleCol] = 'col-title' . $localizationMarkerClass;
diff --git a/typo3/sysext/recordlist/Classes/Tree/View/DummyLinkParameterProvider.php b/typo3/sysext/recordlist/Classes/Tree/View/DummyLinkParameterProvider.php
new file mode 100644 (file)
index 0000000..6253c2d
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+namespace TYPO3\CMS\Recordlist\Tree\View;
+
+/*
+ * 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!
+ */
+
+/**
+ * This class is a dummy class used for the FileSystemNavigationFrameController
+ */
+class DummyLinkParameterProvider implements LinkParameterProviderInterface {
+
+       /**
+        * @var array
+        */
+       protected $parameters = [];
+
+       /**
+        * @var string
+        */
+       protected $thisScript;
+
+       /**
+        * @param string $mode
+        * @param string $act
+        * @param string $thisScript
+        */
+       public function __construct($mode, $act, $thisScript) {
+               if ($mode) {
+                       $this->parameters['mode'] = $mode;
+               }
+               if ($act) {
+                       $this->parameters['act'] = $act;
+               }
+               $this->thisScript = $thisScript;
+       }
+
+       /**
+        * @param array $values Array of values to include into the parameters or which might influence the parameters
+        *
+        * @return string[] Array of parameters which have to be added to URLs
+        */
+       public function getUrlParameters(array $values) {
+               return $this->parameters;
+       }
+
+       /**
+        * @param array $values Values to be checked
+        *
+        * @return bool Returns TRUE if the given values match the currently selected item
+        */
+       public function isCurrentlySelectedItem(array $values) {
+               return FALSE;
+       }
+
+       /**
+        * Returns the URL of the current script
+        *
+        * @return string
+        */
+       public function getScriptUrl() {
+               return $this->thisScript;
+       }
+
+}
index dc07c27..bf04735 100644 (file)
@@ -38,19 +38,22 @@ class ElementBrowserPageTreeView extends \TYPO3\CMS\Backend\Tree\View\ElementBro
         * Wrapping the title in a link, if applicable.
         *
         * @param string $title Title, ready for output.
-        * @param array $v The record
+        * @param array $row The record
         * @param bool $ext_pArrPages If set, pages clicked will return immediately, otherwise reload page.
         * @return string Wrapping title string.
         */
-       public function wrapTitle($title, $v, $ext_pArrPages) {
-               if ($ext_pArrPages) {
+       public function wrapTitle($title, $row, $ext_pArrPages) {
+               if ($ext_pArrPages && $row['uid']) {
                        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-                       $ficon = $iconFactory->getIconForRecord('pages', $v, Icon::SIZE_SMALL)->render();
-                       $onClick = 'return insertElement(\'pages\', \'' . $v['uid'] . '\', \'db\', ' . GeneralUtility::quoteJSvalue($v['title']) . ', \'\', \'\', ' . GeneralUtility::quoteJSvalue($ficon) . ',\'\',1);';
-               } else {
-                       $onClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'act=' . $this->elementBrowser->act . '&mode=' . $this->elementBrowser->mode . '&expandPage=' . $v['uid']) . ');';
+                       $ficon = $iconFactory->getIconForRecord('pages', $row, Icon::SIZE_SMALL)->render();
+                       $out = '<span data-uid="' . htmlspecialchars($row['uid']) . '" data-table="pages" data-title="' . htmlspecialchars($row['title']) . '" data-icon="' . htmlspecialchars($ficon) . '">';
+                       $out .= '<a href="#" data-close="1">' . $title . '</a>';
+                       $out .= '</span>';
+                       return $out;
                }
-               return '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' . $title . '</a>';
+
+               $parameters = GeneralUtility::implodeArrayForUrl('', $this->linkParameterProvider->getUrlParameters(['pid' => $row['uid']]));
+               return '<a href="#" onclick="return jumpToUrl(' . htmlspecialchars(GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim($parameters, '&'))) . ');">' . $title . '</a>';
        }
 
 }
diff --git a/typo3/sysext/recordlist/Classes/Tree/View/LinkParameterProviderInterface.php b/typo3/sysext/recordlist/Classes/Tree/View/LinkParameterProviderInterface.php
new file mode 100644 (file)
index 0000000..64b7472
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+namespace TYPO3\CMS\Recordlist\Tree\View;
+
+/*
+ * 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 LinkParameterProviderInterface {
+
+       /**
+        * Returns the URL of the current script
+        *
+        * @return string
+        */
+       public function getScriptUrl();
+
+       /**
+        * Provides an array or GET parameters for URL generation
+        *
+        * @param array $values Array of values to include into the parameters or which might influence the parameters
+        *
+        * @return string[] Array of parameters which have to be added to URLs
+        */
+       public function getUrlParameters(array $values);
+
+       /**
+        * Check if given value is currently the selected item
+        *
+        * This method is only used in the page tree.
+        *
+        * @param array $values Values to be checked
+        *
+        * @return bool Returns TRUE if the given values match the currently selected item
+        */
+       public function isCurrentlySelectedItem(array $values);
+
+}
diff --git a/typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php b/typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php
new file mode 100644 (file)
index 0000000..f1bd968
--- /dev/null
@@ -0,0 +1,239 @@
+<?php
+namespace TYPO3\CMS\Recordlist\View;
+
+/*
+ * 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\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Lang\LanguageService;
+use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
+
+/**
+ * Renders utility forms used in the views for files/folders of Element and Link Browser
+ */
+class FolderUtilityRenderer  {
+
+       /**
+        * @var LinkParameterProviderInterface
+        */
+       protected $parameterProvider;
+
+       /**
+        * @param LinkParameterProviderInterface $parameterProvider
+        */
+       public function __construct(LinkParameterProviderInterface $parameterProvider) {
+               $this->parameterProvider = $parameterProvider;
+       }
+
+       /**
+        * For TBE: Makes a form for creating new folders in the filemount the user is browsing.
+        * The folder creation request is sent to the tce_file.php script in the core which will handle the creation.
+        *
+        * @param Folder $folderObject Absolute filepath on server in which to create the new folder.
+        *
+        * @return string HTML for the create folder form.
+        */
+       public function createFolder(Folder $folderObject) {
+               if (!$folderObject->checkActionPermission('write')) {
+                       return '';
+               }
+               $backendUser = $this->getBackendUser();
+               if (!$backendUser->isAdmin() && !$backendUser->getTSConfigVal('options.createFoldersInEB')) {
+                       return '';
+               }
+               // Don't show Folder-create form if it's denied
+               if ($backendUser->getTSConfigVal('options.folderTree.hideCreateFolder')) {
+                       return '';
+               }
+               $lang = $this->getLanguageService();
+               $code = '
+
+                       <!--
+                               Form, for creating new folders:
+                       -->
+                       <form action="' . htmlspecialchars(BackendUtility::getModuleUrl('tce_file')) . '" method="post" name="editform2" id="typo3-crFolderForm">
+                               <table border="0" cellpadding="0" cellspacing="0" id="typo3-crFolder">
+                                       <tr>
+                                               <td><h3>' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:file_newfolder.php.pagetitle', TRUE) . ':</h3></td>
+                                       </tr>
+                                       <tr>
+                                               <td class="c-wCell c-hCell"><strong>' . $lang->getLL('path', TRUE) . ':</strong> '
+                                                       . htmlspecialchars($folderObject->getIdentifier()) . '</td>
+                                       </tr>
+                                       <tr>
+                                               <td class="c-wCell c-hCell">';
+               // Create the new-folder name field:
+               $a = 1;
+               $code .= '<input size="20" type="text" name="file[newfolder][' . $a . '][data]" />'
+                       . '<input type="hidden" name="file[newfolder][' . $a . '][target]" value="'
+                &nb