first commit

This commit is contained in:
salam committed 2026-10-19 08:18:39 +02:00
commit 1218dd1517
12716 files changed
+989233

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -0,0 +1,177 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Site\PseudoSiteFinder;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Generate a page-tree, browsable.
*/
class BrowseTreeView extends AbstractTreeView
{
/**
* @var array
*/
public $fieldArray = [
'uid',
'pid',
'title',
'doktype',
'nav_title',
'mount_pid',
'php_tree_stop',
't3ver_id',
't3ver_state',
'hidden',
'starttime',
'endtime',
'fe_group',
'module',
'extendToSubpages',
'nav_hide',
't3ver_wsid',
't3ver_move_id',
'is_siteroot'
];
/**
* override to use this treeName
* @var string
*/
public $treeName = 'browsePages';
/**
* override to use this table
* @var string
*/
public $table = 'pages';
/**
* override to use this domIdPrefix
* @var string
*/
public $domIdPrefix = 'pages';
/**
* @var bool
*/
public $ext_showNavTitle = false;
/**
* Initialize, setting what is necessary for browsing pages.
* Using the current user.
*
* @param string $clause Additional clause for selecting pages.
* @param string $orderByFields record ORDER BY field
*/
public function init($clause = '', $orderByFields = '')
{
$backendUser = $this->getBackendUser();
// This will hide records from display - it has nothing to do with user rights!!
$clauseExcludePidList = '';
$pidList = $backendUser->getTSConfig()['options.']['hideRecords.']['pages'] ?? '';
if (!empty($pidList)) {
if ($pidList = implode(',', GeneralUtility::intExplode(',', $pidList))) {
$clauseExcludePidList = ' AND pages.uid NOT IN (' . $pidList . ')';
}
}
// This is very important for making trees of pages: Filtering out deleted pages, pages with no access to and sorting them correctly:
parent::init(' AND deleted=0 AND sys_language_uid=0 AND ' . $backendUser->getPagePermsClause(Permission::PAGE_SHOW) . ' ' . $clause . $clauseExcludePidList, 'sorting');
$this->title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
$this->MOUNTS = $backendUser->returnWebmounts();
if ($pidList) {
// Remove mountpoint if explicitly set in options.hideRecords.pages (see above)
$hideList = explode(',', $pidList);
$this->MOUNTS = array_diff($this->MOUNTS, $hideList);
}
}
/**
* Creates title attribute content for pages.
* Uses API function in \TYPO3\CMS\Backend\Utility\BackendUtility which will retrieve lots of useful information for pages.
*
* @param array $row The table row.
* @return string
*/
public function getTitleAttrib($row)
{
return BackendUtility::titleAttribForPages($row, '1=1 ' . $this->clause, 0);
}
/**
* Wrapping the image tag, $icon, for the row, $row (except for mount points)
*
* @param string $icon The image tag for the icon
* @param array $row The row for the current element
* @return string The processed icon input value.
* @internal
*/
public function wrapIcon($icon, $row)
{
// Wrap icon in click-menu link.
$theIcon = '';
if (!$this->ext_IconMode) {
$theIcon = BackendUtility::wrapClickMenuOnIcon($icon, $this->treeName, $this->getId($row), 0);
} elseif ($this->ext_IconMode === 'titlelink') {
$aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($row)) . ',this,'
. GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($row)) . ',' . $this->bank . ');';
$theIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
}
return $theIcon;
}
/**
* Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
* Do NOT htmlspecialchar the string from this function - has already been done.
*
* @param array $row The input row array (where the key "title" is used for the title)
* @param int $titleLen Title length (30)
* @return string The title.
*/
public function getTitleStr($row, $titleLen = 30)
{
if ($this->ext_showNavTitle && isset($row['nav_title']) && trim($row['nav_title']) !== '') {
$title = parent::getTitleStr(['title' => $row['nav_title']], $titleLen);
} else {
$title = parent::getTitleStr($row, $titleLen);
}
if (!empty($row['is_siteroot'])
&& $this->getBackendUser()->getTSConfig()['options.']['pageTree.']['showDomainNameWithTitle'] ?? false
) {
$pageId = (int)$row['uid'];
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
try {
$site = $siteFinder->getSiteByRootPageId($pageId);
$title .= ' [' . (string)$site->getBase() . ']';
} catch (SiteNotFoundException $e) {
// No site found, let's see if it is a legacy-pseudo-site
$pseudoSiteFinder = GeneralUtility::makeInstance(PseudoSiteFinder::class);
try {
$site = $pseudoSiteFinder->getSiteByRootPageId($pageId);
$title .= ' [' . trim((string)$site->getBase(), '/') . ']';
} catch (SiteNotFoundException $e) {
// No pseudo-site found either
}
}
}
return $title;
}
}
@@ -0,0 +1,65 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Local position map class when creating new Content Elements
*
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
class ContentCreationPagePositionMap extends PagePositionMap
{
/**
* @var bool
*/
public $dontPrintPageInsertIcons = 1;
/**
* Wrapping the title of the record - here we just return it.
*
* @param string $str The title value.
* @param array $row The record row.
* @return string Wrapped title string.
*/
public function wrapRecordTitle($str, $row)
{
return $str;
}
/**
* Create on-click event value.
*
* @param array $row The record.
* @param string $vv Column position value.
* @param int $moveUid Move uid
* @param int $pid PID value.
* @param int $sys_lang System language
* @return string
*/
public function onClickInsertRecord($row, $vv, $moveUid, $pid, $sys_lang = 0)
{
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$location = (string)$uriBuilder->buildUriFromRoute('record_edit', [
'edit[tt_content][' . (is_array($row) ? -$row['uid'] : $pid) . ']' => 'new',
'defVals[tt_content][colPos]' => $vv,
'defVals[tt_content][sys_language_uid]' => $sys_lang,
'returnUrl' => GeneralUtility::_GP('returnUrl')
]);
return $this->clientContext . '.location.href=' . GeneralUtility::quoteJSvalue($location) . '+document.editForm.defValues.value; return false;';
}
}
@@ -0,0 +1,116 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Position map class for content elements within the page module
*
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
class ContentLayoutPagePositionMap extends PagePositionMap
{
/**
* @var bool
*/
public $dontPrintPageInsertIcons = 1;
/**
* @var string
*/
public $l_insertNewRecordHere = 'newContentElement';
/**
* Wrapping the title of the record.
*
* @param string $str The title value.
* @param array $row The record row.
* @return string Wrapped title string.
*/
public function wrapRecordTitle($str, $row)
{
$aOnClick = 'jumpToUrl(' . GeneralUtility::quoteJSvalue($this->linkToCurrentModule(['edit_record' => 'tt_content:' . $row['uid']])) . ');return false;';
return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $str . '</a>';
}
/**
* Wrapping the column header
*
* @param string $str Header value
* @param string $vv Column info.
* @return string
* @see printRecordMap()
*/
public function wrapColumnHeader($str, $vv)
{
$aOnClick = 'jumpToUrl(' . GeneralUtility::quoteJSvalue($this->linkToCurrentModule(['edit_record' => '_EDIT_COL:' . $vv])) . ');return false;';
return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $str . '</a>';
}
/**
* Create on-click event value.
*
* @param array $row The record.
* @param string $vv Column position value.
* @param int $moveUid Move uid
* @param int $pid PID value.
* @param int $sys_lang System language
* @return string
*/
public function onClickInsertRecord($row, $vv, $moveUid, $pid, $sys_lang = 0)
{
if (is_array($row)) {
$linkInformation = 'tt_content:new/-' . $row['uid'] . '/' . $row['colPos'];
} else {
$linkInformation = 'tt_content:new/' . $pid . '/' . $vv;
}
$location = $this->linkToCurrentModule(['edit_record' => $linkInformation]);
return 'jumpToUrl(' . GeneralUtility::quoteJSvalue($location) . ');return false;';
}
/**
* Wrapping the record header (from getRecordHeader())
*
* @param string $str HTML content
* @param array $row Record array.
* @return string HTML content
*/
public function wrapRecordHeader($str, $row)
{
if ($row['uid'] == $this->moveUid) {
/** @var IconFactory $iconFactory */
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
return $iconFactory->getIcon('status-status-current', Icon::SIZE_SMALL)->render() . $str;
}
return $str;
}
/**
* Returns URL to the current script.
* In particular the "popView" and "new_unique_uid" Get vars are unset.
*
* @param array $params Parameters array, merged with global GET vars.
* @return string URL
*/
protected function linkToCurrentModule($params)
{
unset($params['popView']);
unset($params['new_unique_uid']);
return GeneralUtility::linkThisScript($params);
}
}
@@ -0,0 +1,63 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
/**
* Position map class for moving content elements.
*
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
class ContentMovingPagePositionMap extends PagePositionMap
{
/**
* @var int
*/
public $dontPrintPageInsertIcons = 1;
/**
* Page tree implementation class name
*
* @var string
*/
protected $pageTreeClassName = PageTreeView::class;
/**
* Wrapping page title.
*
* @param string $str Page title.
* @param array $rec Page record (?)
* @return string Wrapped title.
*/
public function linkPageTitle($str, $rec)
{
$url = \TYPO3\CMS\Core\Utility\GeneralUtility::linkThisScript(['uid' => (int)$rec['uid'], 'moveUid' => $this->moveUid]);
return '<a href="' . htmlspecialchars($url) . '">' . $str . '</a>';
}
/**
* Wrapping the title of the record.
*
* @param string $str The title value.
* @param array $row The record row.
* @return string Wrapped title string.
*/
public function wrapRecordTitle($str, $row)
{
if ($this->moveUid == $row['uid']) {
$str = '<strong>' . $str . '</strong>';
}
return parent::wrapRecordTitle($str, $row);
}
}
@@ -0,0 +1,160 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
/**
* Base extension class which generates the folder tree.
* Used directly by the RTE.
* also used for the linkpicker on files
*
* Browsable folder tree, used in Element Browser and RTE (for which it will be extended)
* previously located inside typo3/class.browse_links.php
*/
class ElementBrowserFolderTreeView extends FolderTreeView
{
/**
* @var string
*/
public $ext_IconMode = 'titlelink';
/**
* @var LinkParameterProviderInterface
*/
protected $linkParameterProvider;
/**
* @param LinkParameterProviderInterface $linkParameterProvider
*/
public function setLinkParameterProvider(LinkParameterProviderInterface $linkParameterProvider)
{
$this->linkParameterProvider = $linkParameterProvider;
$this->thisScript = $linkParameterProvider->getScriptUrl();
}
/**
* Wrapping the folder icon
*
* @param string $icon The image tag for the icon
* @param Folder $folderObject The row for the current element
*
* @return string The processed icon input value.
* @internal
*/
public function wrapIcon($icon, $folderObject)
{
// Add title attribute to input icon tag
$theFolderIcon = '';
// Wrap icon in link (in ElementBrowser only the "titlelink" is used).
if ($this->ext_IconMode === 'titlelink') {
$parameters = HttpUtility::buildQueryString(
$this->linkParameterProvider->getUrlParameters(['identifier' => $folderObject->getCombinedIdentifier()])
);
$aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . $parameters) . ');';
$theFolderIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
}
return $theFolderIcon;
}
/**
* Wrapping the title in a link, if applicable.
*
* @param string $title Title, ready for output.
* @param Folder $folderObject The record
* @param int $bank Bank pointer (which mount point number)
* @return string Wrapping title string.
*/
public function wrapTitle($title, $folderObject, $bank = 0)
{
$parameters = HttpUtility::buildQueryString(
$this->linkParameterProvider->getUrlParameters(['identifier' => $folderObject->getCombinedIdentifier()])
);
return '<a href="#" onclick="return jumpToUrl(' . htmlspecialchars(GeneralUtility::quoteJSvalue($this->getThisScript() . $parameters)) . ');">' . $title . '</a>';
}
/**
* Returns TRUE if the input "record" contains a folder which can be linked.
*
* @param Folder $folderObject Object with information about the folder element. Contains keys like title, uid, path, _title
* @return bool TRUE
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
*/
public function ext_isLinkable(Folder $folderObject)
{
trigger_error('This method is obsolete and will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
return true;
}
/**
* @param string $cmd
* @param bool $isOpen
* @return string
*/
protected function renderPMIconAndLink($cmd, $isOpen)
{
if (static::class === __CLASS__) {
return $this->PMiconATagWrap('', $cmd, !$isOpen);
}
return parent::renderPMIconAndLink($cmd, $isOpen);
}
/**
* Wrap the plus/minus icon in a link
*
* @param string $icon HTML string to wrap, probably an image tag.
* @param string $cmd Command for 'PM' get var
* @param bool|string $bMark If set, the link will have an anchor point (=$bMark) and a name attribute (=$bMark)
* @param bool $isOpen check if the item has children
* @return string Link-wrapped input string
* @internal
*/
public function PM_ATagWrap($icon, $cmd, $bMark = '', $isOpen = false)
{
$anchor = $bMark ? '#' . $bMark : '';
$name = $bMark ? ' name=' . $bMark : '';
$urlParameters = $this->linkParameterProvider->getUrlParameters([]);
$urlParameters['PM'] = $cmd;
$aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . HttpUtility::buildQueryString($urlParameters)) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');';
return '<a href="#"' . htmlspecialchars($name) . ' onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
}
/**
* Wrap the plus/minus icon in a link
*
* @param string $icon HTML string to wrap, probably an image tag.
* @param string $cmd Command for 'PM' get var
* @param bool $isExpand Whether to be expanded
* @return string Link-wrapped input string
* @internal
*/
public function PMiconATagWrap($icon, $cmd, $isExpand = true)
{
if (empty($this->scope)) {
$this->scope = [
'class' => static::class,
'script' => $this->thisScript,
'browser' => $this->linkParameterProvider->getUrlParameters([]),
];
}
return parent::PMiconATagWrap($icon, $cmd, $isExpand);
}
}
@@ -0,0 +1,199 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
use TYPO3\CMS\Core\LinkHandling\LinkService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
use TYPO3\CMS\Frontend\Page\PageRepository;
use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
/**
* Class which generates the selectable page tree
*
* Browsable tree, used in PagePositionMaps (move elements), the Link Wizard and the Database Browser (for which it will be extended)
*/
class ElementBrowserPageTreeView extends BrowseTreeView
{
/**
* whether the page ID should be shown next to the title, activate through
* userTSconfig (options.pageTree.showPageIdWithTitle)
*
* @var bool
*/
public $ext_showPageId = false;
/**
* @var bool
*/
public $ext_pArrPages = true;
/**
* @var LinkParameterProviderInterface
*/
protected $linkParameterProvider;
/**
* Constructor. Just calling init()
*/
public function __construct()
{
parent::__construct();
$this->init();
$this->clause = ' AND doktype <> ' . PageRepository::DOKTYPE_RECYCLER . $this->clause;
}
/**
* @param LinkParameterProviderInterface $linkParameterProvider
*/
public function setLinkParameterProvider(LinkParameterProviderInterface $linkParameterProvider)
{
$this->linkParameterProvider = $linkParameterProvider;
$this->thisScript = $linkParameterProvider->getScriptUrl();
}
/**
* Wrapping the title in a link, if applicable.
*
* @param string $title Title, (must be ready for output, that means it must be htmlspecialchars()'ed).
* @param array $v The record
* @param bool $ext_pArrPages (ignored)
* @return string Wrapping title string.
*/
public function wrapTitle($title, $v, $ext_pArrPages = false)
{
if ($this->ext_isLinkable($v['doktype'], $v['uid'])) {
$url = GeneralUtility::makeInstance(LinkService::class)->asString(['type' => LinkService::TYPE_PAGE, 'pageuid' => (int)$v['uid']]);
return '<span class="list-tree-title"><a href="' . htmlspecialchars($url) . '" class="t3js-pageLink">' . $title . '</a></span>';
}
return '<span class="list-tree-title text-muted">' . $title . '</span>';
}
/**
* Create the page navigation tree in HTML
*
* @param array|string $treeArr Tree array
* @return string HTML output.
*/
public function printTree($treeArr = '')
{
$titleLen = (int)$GLOBALS['BE_USER']->uc['titleLen'];
if (!is_array($treeArr)) {
$treeArr = $this->tree;
}
$out = '';
// We need to count the opened <ul>'s every time we dig into another level,
// so we know how many we have to close when all children are done rendering
$closeDepth = [];
foreach ($treeArr as $treeItem) {
if ($treeItem['isMountPointPath']) {
$out .= '<li class="list-tree-path">' . $treeItem['title'] . '</li>';
continue;
}
$classAttr = $treeItem['row']['_CSSCLASS'];
if ($treeItem['isFirst']) {
$out .= '<ul class="list-tree">';
}
// Add CSS classes to the list item
if ($treeItem['hasSub']) {
$classAttr .= ' list-tree-control-open';
}
$selected = '';
if ($this->linkParameterProvider->isCurrentlySelectedItem(['pid' => (int)$treeItem['row']['uid']])) {
$selected = ' bg-success';
$classAttr .= ' active';
}
$urlParameters = $this->linkParameterProvider->getUrlParameters(['pid' => (int)$treeItem['row']['uid']]);
$aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . HttpUtility::buildQueryString($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) . '"' : '') . '>
<span class="list-tree-group' . $selected . '">
' . $cEbullet . $treeItem['HTML'] . $this->wrapTitle($this->getTitleStr($treeItem['row'], $titleLen), $treeItem['row'], $this->ext_pArrPages) . '
</span>
';
if (!$treeItem['hasSub']) {
$out .= '</li>';
}
// We have to remember if this is the last one
// on level X so the last child on level X+1 closes the <ul>-tag
if ($treeItem['isLast']) {
$closeDepth[$treeItem['invertedDepth']] = 1;
}
// If this is the last one and does not have subitems, we need to close
// the tree as long as the upper levels have last items too
if ($treeItem['isLast'] && !$treeItem['hasSub']) {
for ($i = $treeItem['invertedDepth']; $closeDepth[$i] == 1; $i++) {
$closeDepth[$i] = 0;
$out .= '</ul></li>';
}
}
}
return '<ul class="list-tree list-tree-root">' . $out . '</ul>';
}
/**
* Returns TRUE if a doktype can be linked.
*
* @param int $doktype Doktype value to test
* @param int $uid uid to test.
* @return bool
*/
public function ext_isLinkable($doktype, $uid)
{
return $uid && $doktype < PageRepository::DOKTYPE_SPACER;
}
/**
* Wrap the plus/minus icon in a link
*
* @param string $icon HTML string to wrap, probably an image tag.
* @param string $cmd Command for 'PM' get var
* @param string $bMark If set, the link will have an anchor point (=$bMark) and a name attribute (=$bMark)
* @param bool $isOpen
* @return string Link-wrapped input string
*/
public function PM_ATagWrap($icon, $cmd, $bMark = '', $isOpen = false)
{
$anchor = $bMark ? '#' . $bMark : '';
$name = $bMark ? ' name=' . $bMark : '';
$urlParameters = $this->linkParameterProvider->getUrlParameters([]);
$urlParameters['PM'] = $cmd;
$aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . HttpUtility::buildQueryString($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>';
}
/**
* Wrapping the image tag, $icon, for the row, $row
*
* @param string $icon The image tag for the icon
* @param array $row The row for the current element
* @return string The processed icon input value.
*/
public function wrapIcon($icon, $row)
{
if ($this->ext_showPageId) {
$icon .= '[' . $row['uid'] . ']&nbsp;';
}
return $icon;
}
}
@@ -0,0 +1,717 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageService;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Resource\FolderInterface;
use TYPO3\CMS\Core\Resource\InaccessibleFolder;
use TYPO3\CMS\Core\Resource\ResourceStorage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Generate a folder tree,
* specially made for browsing folders in the File module
*
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
class FolderTreeView extends AbstractTreeView
{
/**
* The users' file Storages
*
* @var ResourceStorage[]
*/
protected $storages;
/**
* @var array
*/
protected $storageHashNumbers;
/**
* Indicates, whether the AJAX call was successful,
* i.e. the requested page has been found
*
* @var bool
*/
protected $ajaxStatus = false;
/**
* @var array
*/
protected $scope;
/**
* @var IconFactory
*/
protected $iconFactory;
/**
* override to not use a title attribute
* @var string
*/
public $titleAttrib = '';
/**
* override to use this treeName
* does not need to be set in __construct()
* @var string
*/
public $treeName = 'folder';
/**
* override to use this domIdPrefix
* @var string
*/
public $domIdPrefix = 'folder';
/**
* Constructor function of the class
*/
public function __construct()
{
parent::__construct();
$this->init();
$this->storages = $this->BE_USER->getFileStorages();
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
}
/**
* Generate the plus/minus icon for the browsable tree.
*
* @param Folder $folderObject Entry folder object
* @param int $subFolderCounter The current entry number
* @param int $totalSubFolders The total number of entries. If equal to $a, a "bottom" element is returned.
* @param int $nextCount The number of sub-elements to the current element.
* @param bool $isExpanded The element was expanded to render subelements if this flag is set.
*
* @return string Image tag with the plus/minus icon.
* @internal
* @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
*/
public function PMicon($folderObject, $subFolderCounter, $totalSubFolders, $nextCount, $isExpanded)
{
$icon = '';
if ($nextCount) {
$cmd = $this->generateExpandCollapseParameter($this->bank, !$isExpanded, $folderObject);
$icon = $this->PMiconATagWrap($icon, $cmd, !$isExpanded);
}
return $icon;
}
/**
* Wrap the plus/minus icon in a link
*
* @param string $icon HTML string to wrap, probably an image tag.
* @param string $cmd Command for 'PM' get var
* @param bool $isExpand Whether to be expanded
* @return string Link-wrapped input string
* @internal
*/
public function PMiconATagWrap($icon, $cmd, $isExpand = true)
{
if (empty($this->scope)) {
$this->scope = [
'class' => static::class,
'script' => $this->thisScript,
];
}
if ($this->thisScript) {
// Activates dynamic AJAX based tree
$scopeData = json_encode($this->scope);
$scopeHash = GeneralUtility::hmac($scopeData);
$js = htmlspecialchars('Tree.load(' . GeneralUtility::quoteJSvalue($cmd) . ', ' . (int)$isExpand . ', this, ' . GeneralUtility::quoteJSvalue($scopeData) . ', ' . GeneralUtility::quoteJSvalue($scopeHash) . ');');
return '<a class="list-tree-control' . (!$isExpand ? ' list-tree-control-open' : ' list-tree-control-closed') . '" onclick="' . $js . '"><i class="fa"></i></a>';
}
return $icon;
}
/**
* @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
* @param Folder $folderObject The row for the current element
*
* @return string The processed icon input value.
* @internal
*/
public function wrapIcon($icon, $folderObject)
{
// Add title attribute to input icon tag
$theFolderIcon = '';
// Wrap icon in click-menu link.
if (!$this->ext_IconMode) {
// Check storage access to wrap with click menu
if (!$folderObject instanceof InaccessibleFolder) {
$tableName = $this->getTableNameForClickMenu($folderObject);
$theFolderIcon = BackendUtility::wrapClickMenuOnIcon($icon, $tableName, $folderObject->getCombinedIdentifier(), 'tree');
}
} elseif ($this->ext_IconMode === 'titlelink') {
$aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ',' . $this->bank . ');';
$theFolderIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
}
return $theFolderIcon;
}
/**
* Wrapping $title in a-tags.
*
* @param string $title Title string
* @param Folder $folderObject the folder record
* @param int $bank Bank pointer (which mount point number)
*
* @return string
* @internal
*/
public function wrapTitle($title, $folderObject, $bank = 0)
{
// Check storage access to wrap with click menu
if ($folderObject instanceof InaccessibleFolder) {
return $title;
}
$aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ', this, ' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ', ' . $bank . ');';
$tableName = $this->getTableNameForClickMenu($folderObject);
$clickMenuParts = BackendUtility::wrapClickMenuOnIcon('', $tableName, $folderObject->getCombinedIdentifier(), 'tree', '', '', true);
return '<a href="#" title="' . htmlspecialchars(strip_tags($title)) . '" onclick="' . htmlspecialchars($aOnClick) . '" ' . GeneralUtility::implodeAttributes($clickMenuParts) . '>' . $title . '</a>';
}
/**
* Returns the id from the record - for folders, this is an md5 hash.
*
* @param Folder $folderObject The folder object
*
* @return int The "uid" field value.
*/
public function getId($folderObject)
{
return GeneralUtility::md5int($folderObject->getCombinedIdentifier());
}
/**
* Returns jump-url parameter value.
*
* @param Folder $folderObject The folder object
*
* @return string The jump-url parameter.
*/
public function getJumpToParam($folderObject)
{
return rawurlencode($folderObject->getCombinedIdentifier());
}
/**
* Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
* '_title' is used for setting an alternative title for folders.
*
* @param array $row The input row array (where the key "_title" is used for the title)
* @param int $titleLen Title length (30)
* @return string The title
*/
public function getTitleStr($row, $titleLen = 30)
{
return $row['_title'] ?? parent::getTitleStr($row, $titleLen);
}
/**
* Returns the value for the image "title" attribute
*
* @param Folder $folderObject The folder to be used
*
* @return string The attribute value (is htmlspecialchared() already)
*/
public function getTitleAttrib($folderObject)
{
return htmlspecialchars($folderObject->getName());
}
/**
* Will create and return the HTML code for a browsable tree of folders.
* Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
*
* @return string HTML code for the browsable tree
*/
public function getBrowsableTree()
{
// Get stored tree structure AND updating it if needed according to incoming PM GET var.
$this->initializePositionSaving();
// Init done:
$treeItems = [];
// Traverse mounts:
foreach ($this->storages as $storageObject) {
$this->getBrowseableTreeForStorage($storageObject);
// Add tree:
$treeItems = array_merge($treeItems, $this->tree);
}
return $this->printTree($treeItems);
}
/**
* Get a tree for one storage
*
* @param ResourceStorage $storageObject
*/
public function getBrowseableTreeForStorage(ResourceStorage $storageObject)
{
// If there are filemounts, show each, otherwise just the rootlevel folder
$fileMounts = $storageObject->getFileMounts();
$rootLevelFolders = [];
if (!empty($fileMounts)) {
foreach ($fileMounts as $fileMountInfo) {
$rootLevelFolders[] = [
'folder' => $fileMountInfo['folder'],
'name' => $fileMountInfo['title']
];
}
} elseif ($this->BE_USER->isAdmin()) {
$rootLevelFolders[] = [
'folder' => $storageObject->getRootLevelFolder(),
'name' => $storageObject->getName()
];
}
// Clean the tree
$this->reset();
// Go through all "root level folders" of this tree (can be the rootlevel folder or any file mount points)
foreach ($rootLevelFolders as $rootLevelFolderInfo) {
/** @var Folder $rootLevelFolder */
$rootLevelFolder = $rootLevelFolderInfo['folder'];
$rootLevelFolderName = $rootLevelFolderInfo['name'];
$folderHashSpecUID = GeneralUtility::md5int($rootLevelFolder->getCombinedIdentifier());
$this->specUIDmap[$folderHashSpecUID] = $rootLevelFolder->getCombinedIdentifier();
// Hash key
$storageHashNumber = $this->getShortHashNumberForStorage($storageObject, $rootLevelFolder);
// Set first:
$this->bank = $storageHashNumber;
$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 {
$firstHtml = $this->renderPMIconAndLink($cmd, $isOpen);
}
// Mark a storage which is not online, as offline
// maybe someday there will be a special icon for this
if ($storageObject->isOnline() === false) {
$rootLevelFolderName .= ' (' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_file.xlf:sys_file_storage.isOffline') . ')';
}
// Preparing rootRec for the mount
$icon = $this->iconFactory->getIconForResource($rootLevelFolder, Icon::SIZE_SMALL, null, ['mount-root' => true]);
$firstHtml .= $this->wrapIcon($icon, $rootLevelFolder);
$row = [
'uid' => $folderHashSpecUID,
'title' => $rootLevelFolderName,
'path' => $rootLevelFolder->getCombinedIdentifier(),
'folder' => $rootLevelFolder
];
// Add the storage root to ->tree
$this->tree[] = [
'HTML' => $firstHtml,
'row' => $row,
'bank' => $this->bank,
// hasSub is TRUE when the root of the storage is expanded
'hasSub' => $isOpen && $storageObject->isBrowsable(),
'invertedDepth' => 1000,
];
// If the mount is expanded, go down:
if ($isOpen && $storageObject->isBrowsable()) {
// Set depth:
$this->getFolderTree($rootLevelFolder, 999);
}
}
}
/**
* Fetches the data for the tree
*
* @param Folder $folderObject the folderobject
* @param int $depth Max depth (recursivity limit)
* @param string $type HTML-code prefix for recursive calls.
*
* @return int The count of items on the level
* @see getBrowsableTree()
*/
public function getFolderTree(Folder $folderObject, $depth = 999, $type = '')
{
$depth = (int)$depth;
// This generates the directory tree
/* array of \TYPO3\CMS\Core\Resource\Folder */
if ($folderObject instanceof InaccessibleFolder) {
$subFolders = [];
} else {
$subFolders = $folderObject->getSubfolders();
$subFolders = \TYPO3\CMS\Core\Resource\Utility\ListUtility::resolveSpecialFolderNames($subFolders);
uksort($subFolders, 'strnatcasecmp');
}
$totalSubFolders = count($subFolders);
$HTML = '';
$subFolderCounter = 0;
$treeKey = '';
/** @var Folder $subFolder */
foreach ($subFolders as $subFolderName => $subFolder) {
$subFolderCounter++;
// Reserve space.
$this->tree[] = [];
// Get the key for this space
end($this->tree);
$isLocked = $subFolder instanceof InaccessibleFolder;
$treeKey = key($this->tree);
$specUID = GeneralUtility::md5int($subFolder->getCombinedIdentifier());
$this->specUIDmap[$specUID] = $subFolder->getCombinedIdentifier();
$row = [
'uid' => $specUID,
'path' => $subFolder->getCombinedIdentifier(),
'title' => $subFolderName,
'folder' => $subFolder
];
// Make a recursive call to the next level
if (!$isLocked && $depth > 1 && $this->expandNext($specUID)) {
$nextCount = $this->getFolderTree($subFolder, $depth - 1, $type);
// Set "did expand" flag
$isOpen = 1;
} else {
$nextCount = $isLocked ? 0 : $this->getNumberOfSubfolders($subFolder);
// Clear "did expand" flag
$isOpen = 0;
}
// Set HTML-icons, if any:
if ($this->makeHTML) {
$HTML = $this->PMicon($subFolder, $subFolderCounter, $totalSubFolders, $nextCount, $isOpen);
$type = '';
$role = $subFolder->getRole();
if ($role !== FolderInterface::ROLE_DEFAULT) {
$row['_title'] = '<strong>' . $subFolderName . '</strong>';
}
$icon = '<span title="' . htmlspecialchars($subFolderName) . '">'
. $this->iconFactory->getIconForResource($subFolder, Icon::SIZE_SMALL, null, ['folder-open' => (bool)$isOpen])
. '</span>';
$HTML .= $this->wrapIcon($icon, $subFolder);
}
// Finally, add the row/HTML content to the ->tree array in the reserved key.
$this->tree[$treeKey] = [
'row' => $row,
'HTML' => $HTML,
'hasSub' => $nextCount && $this->expandNext($specUID),
'isFirst' => $subFolderCounter == 1,
'isLast' => false,
'invertedDepth' => $depth,
'bank' => $this->bank
];
}
if ($subFolderCounter > 0) {
$this->tree[$treeKey]['isLast'] = true;
}
return $totalSubFolders;
}
/**
* Compiles the HTML code for displaying the structure found inside the ->tree array
*
* @param array|string $treeItems "tree-array" - if blank string, the internal ->tree array is used.
* @return string The HTML code for the tree
*/
public function printTree($treeItems = '')
{
$doExpand = false;
$doCollapse = false;
$ajaxOutput = '';
$titleLength = (int)$this->BE_USER->uc['titleLen'];
if (!is_array($treeItems)) {
$treeItems = $this->tree;
}
if (empty($treeItems)) {
$message = GeneralUtility::makeInstance(
FlashMessage::class,
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.message'),
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.title'),
FlashMessage::INFO
);
/** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
/** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
$defaultFlashMessageQueue->enqueue($message);
return $defaultFlashMessageQueue->renderFlashMessages();
}
$expandedFolderHash = '';
$invertedDepthOfAjaxRequestedItem = 0;
$out = '<ul class="list-tree list-tree-root">';
// Evaluate AJAX request
if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
list(, $expandCollapseCommand, $expandedFolderHash, ) = $this->evaluateExpandCollapseParameter();
if ($expandCollapseCommand == 1) {
$doExpand = true;
} else {
$doCollapse = true;
}
}
// We need to count the opened <ul>'s every time we dig into another level,
// so we know how many we have to close when all children are done rendering
$closeDepth = [];
foreach ($treeItems as $treeItem) {
/** @var Folder $folderObject */
$folderObject = $treeItem['row']['folder'];
$classAttr = $treeItem['row']['_CSSCLASS'] ?? '';
$folderIdentifier = $folderObject->getCombinedIdentifier();
// this is set if the AJAX request has just opened this folder (via the PM command)
$isExpandedFolderIdentifier = $expandedFolderHash == GeneralUtility::md5int($folderIdentifier);
$idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($folderObject) . '_' . $treeItem['bank']);
$itemHTML = '';
// If this item is the start of a new level,
// then a new level <ul> is needed, but not in ajax mode
if (!empty($treeItem['isFirst']) && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
$itemHTML = '<ul class="list-tree">';
}
// Add CSS classes to the list item
if (!empty($treeItem['hasSub'])) {
$classAttr .= ' list-tree-control-open';
}
$itemHTML .= '
<li id="' . $idAttr . '" ' . ($classAttr ? ' class="' . trim($classAttr) . '"' : '') . '><span class="list-tree-group">' . $treeItem['HTML'] . $this->wrapTitle($this->getTitleStr($treeItem['row'], $titleLength), $folderObject, $treeItem['bank']) . '</span>';
if (empty($treeItem['hasSub'])) {
$itemHTML .= '</li>';
}
// We have to remember if this is the last one
// on level X so the last child on level X+1 closes the <ul>-tag
if (!empty($treeItem['isLast']) && !($doExpand && $isExpandedFolderIdentifier)) {
$closeDepth[$treeItem['invertedDepth']] = 1;
}
// If this is the last one and does not have subitems, we need to close
// the tree as long as the upper levels have last items too
if (!empty($treeItem['isLast']) && empty($treeItem['hasSub']) && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
for ($i = $treeItem['invertedDepth']; !empty($closeDepth[$i]); $i++) {
$closeDepth[$i] = 0;
$itemHTML .= '</ul></li>';
}
}
// Ajax request: collapse
if ($doCollapse && $isExpandedFolderIdentifier) {
$this->ajaxStatus = true;
return $itemHTML;
}
// Ajax request: expand
if ($doExpand && $isExpandedFolderIdentifier) {
$ajaxOutput .= $itemHTML;
$invertedDepthOfAjaxRequestedItem = $treeItem['invertedDepth'];
} elseif ($invertedDepthOfAjaxRequestedItem) {
if ($treeItem['invertedDepth'] && ($treeItem['invertedDepth'] < $invertedDepthOfAjaxRequestedItem)) {
$ajaxOutput .= $itemHTML;
} else {
$this->ajaxStatus = true;
return $ajaxOutput;
}
}
$out .= $itemHTML;
}
// If this is an AJAX request, output directly
if ($ajaxOutput) {
$this->ajaxStatus = true;
return $ajaxOutput;
}
// Finally close the first ul
$out .= '</ul>';
return $out;
}
/**
* Returns table name for click menu
*
* @param Folder $folderObject
* @return string
*/
protected function getTableNameForClickMenu(Folder $folderObject)
{
if (strpos($folderObject->getRole(), FolderInterface::ROLE_MOUNT) !== false) {
$tableName = 'sys_filemounts';
} elseif ($folderObject->getIdentifier() === $folderObject->getStorage()->getRootLevelFolder()->getIdentifier()) {
$tableName = 'sys_file_storage';
} else {
$tableName = 'sys_file';
}
return $tableName;
}
/**
* Counts the number of directories in a file path.
*
* @param Folder $folderObject File path.
*
* @return int
*/
public function getNumberOfSubfolders(Folder $folderObject)
{
$subFolders = $folderObject->getSubfolders();
return count($subFolders);
}
/**
* Get stored tree structure AND updating it if needed according to incoming PM GET var.
*
* @internal
*/
public function initializePositionSaving()
{
// Get stored tree structure:
$this->stored = unserialize($this->BE_USER->uc['browseTrees'][$this->treeName], ['allowed_classes' => false]);
$this->getShortHashNumberForStorage();
// PM action:
// (If an plus/minus icon has been clicked,
// the PM GET var is sent and we must update the stored positions in the tree):
// 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
list($storageHashNumber, $doExpand, $numericFolderHash, $treeName) = $this->evaluateExpandCollapseParameter();
if ($treeName && $treeName == $this->treeName) {
if (in_array($storageHashNumber, $this->storageHashNumbers)) {
if ($doExpand == 1) {
// Set
$this->stored[$storageHashNumber][$numericFolderHash] = 1;
} else {
// Clear
unset($this->stored[$storageHashNumber][$numericFolderHash]);
}
$this->savePosition();
}
}
}
/**
* Helper method to map md5-hash to shorter number
*
* @param ResourceStorage $storageObject
* @param Folder $startingPointFolder
*
* @return int
*/
protected function getShortHashNumberForStorage(ResourceStorage $storageObject = null, Folder $startingPointFolder = null)
{
if (!$this->storageHashNumbers) {
$this->storageHashNumbers = [];
foreach ($this->storages as $storageUid => $storage) {
$fileMounts = $storage->getFileMounts();
if (!empty($fileMounts)) {
foreach ($fileMounts as $fileMount) {
$nkey = hexdec(substr(GeneralUtility::md5int($fileMount['folder']->getCombinedIdentifier()), 0, 4));
$this->storageHashNumbers[$storageUid . $fileMount['folder']->getCombinedIdentifier()] = $nkey;
}
} else {
$folder = $storage->getRootLevelFolder();
$nkey = hexdec(substr(GeneralUtility::md5int($folder->getCombinedIdentifier()), 0, 4));
$this->storageHashNumbers[$storageUid . $folder->getCombinedIdentifier()] = $nkey;
}
}
}
if ($storageObject) {
if ($startingPointFolder) {
return $this->storageHashNumbers[$storageObject->getUid() . $startingPointFolder->getCombinedIdentifier()];
}
return $this->storageHashNumbers[$storageObject->getUid()];
}
return null;
}
/**
* Gets the values from the Expand/Collapse Parameter (&PM)
* previously known as "PM" (plus/minus)
* PM action:
* (If an plus/minus icon has been clicked,
* the PM GET var is sent and we must update the stored positions in the tree):
* 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
*
* @param string $PM The "plus/minus" command
* @return array
*/
protected function evaluateExpandCollapseParameter($PM = null)
{
if ($PM === null) {
$PM = GeneralUtility::_GP('PM');
// IE takes anchor as parameter
if (($PMpos = strpos($PM, '#')) !== false) {
$PM = substr($PM, 0, $PMpos);
}
}
// Take the first three parameters
list($mountKey, $doExpand, $folderIdentifier) = array_pad(explode('_', $PM, 3), 3, null);
// In case the folder identifier contains "_", we just need to get the fourth/last parameter
list($folderIdentifier, $treeName) = array_pad(GeneralUtility::revExplode('_', $folderIdentifier, 2), 2, null);
return [
$mountKey,
$doExpand,
$folderIdentifier,
$treeName
];
}
/**
* Generates the "PM" string to sent to expand/collapse items
*
* @param string $mountKey The mount key / storage UID
* @param bool $doExpand Whether to expand/collapse
* @param Folder $folderObject The folder object
* @param string $treeName The name of the tree
*
* @return string
*/
protected function generateExpandCollapseParameter($mountKey = null, $doExpand = false, Folder $folderObject = null, $treeName = null)
{
$parts = [
$mountKey ?? $this->bank,
$doExpand == 1 ? 1 : 0,
$folderObject !== null ? GeneralUtility::md5int($folderObject->getCombinedIdentifier()) : '',
$treeName ?? $this->treeName
];
return implode('_', $parts);
}
/**
* Gets the AJAX status.
*
* @return bool
*/
public function getAjaxStatus()
{
return $this->ajaxStatus;
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,36 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
/**
* Extension for the tree class that generates the tree of pages in the page-wizard mode
*
* @see \TYPO3\CMS\Backend\Controller\NewRecordController
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
class NewRecordPageTreeView extends PageTreeView
{
/**
* Determines whether to expand a branch or not.
* Here the branch is expanded if the current id matches the global id for the listing/new
*
* @param int $id The ID (page id) of the element
* @return bool Returns TRUE if the IDs matches
*/
public function expandNext($id)
{
return $id == $GLOBALS['SOBE']->id;
}
}
@@ -0,0 +1,79 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Position map class for moving pages,
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
class PageMovingPagePositionMap extends PagePositionMap
{
/**
* @var string
*/
public $l_insertNewPageHere = 'movePageToHere';
/**
* Page tree implementation class name
*
* @var string
*/
protected $pageTreeClassName = PageTreeView::class;
/**
* Creates the onclick event for the insert-icons.
*
* @param int $pid The pid.
* @param int $newPagePID New page id.
* @return string Onclick attribute content
*/
public function onClickEvent($pid, $newPagePID)
{
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
return 'window.location.href=' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('tce_db', [
'cmd[pages][' . $this->moveUid . '][' . $this->moveOrCopy . ']' => $pid,
'redirect' => $this->R_URI,
])) . ';return false;';
}
/**
* Wrapping page title.
*
* @param string $str Page title.
* @param array $rec Page record (?)
* @return string Wrapped title.
*/
public function linkPageTitle($str, $rec)
{
$url = GeneralUtility::linkThisScript(['uid' => (int)$rec['uid'], 'moveUid' => $this->moveUid]);
return '<a href="' . htmlspecialchars($url) . '">' . $str . '</a>';
}
/**
* Wrap $t_code in bold IF the $dat uid matches $id
*
* @param string $t_code Title string
* @param array $dat Information array with record array inside.
* @param int $id The current id.
* @return string The title string.
*/
public function boldTitle($t_code, $dat, $id)
{
return parent::boldTitle($t_code, $dat, $this->moveUid);
}
}
@@ -0,0 +1,663 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Position map class - generating a page tree / content element list which links for inserting (copy/move) of records.
* Used for pages / tt_content element wizards of various kinds.
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
class PagePositionMap
{
use PublicPropertyDeprecationTrait;
/**
* Properties which have been moved to protected status from public
*
* @var array
*/
protected $deprecatedPublicProperties = [
'getModConfigCache' => 'Using $getModConfigCache of class PagePositionMap is discouraged. This property will be removed in TYPO3 v10.0.',
'modConfigStr' => 'Using $$modConfigStr of class PagePositionMap is discouraged. This property will be removed in TYPO3 v10.0.',
];
// EXTERNAL, static:
/**
* @var string
*/
public $moveOrCopy = 'move';
/**
* @var int
*/
public $dontPrintPageInsertIcons = 0;
// How deep the position page tree will go.
/**
* @var int
*/
public $depth = 2;
// Can be set to the sys_language uid to select content elements for.
/**
* @var string
*/
public $cur_sys_language;
// INTERNAL, dynamic:
// Request uri
/**
* @var string
*/
public $R_URI = '';
// Element id.
/**
* @var string
*/
public $elUid = '';
// tt_content element uid to move.
/**
* @var string
*/
public $moveUid = '';
// Caching arrays:
/**
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
*/
protected $getModConfigCache = [];
/**
* @var array
*/
public $checkNewPageCache = [];
// Label keys:
/**
* @var string
*/
public $l_insertNewPageHere = 'insertNewPageHere';
/**
* @var string
*/
public $l_insertNewRecordHere = 'insertNewRecordHere';
/**
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
*/
protected $modConfigStr = 'mod.web_list.newPageWiz';
/**
* Page tree implementation class name
*
* @var string
*/
protected $pageTreeClassName = ElementBrowserPageTreeView::class;
/**
* @var IconFactory
*/
protected $iconFactory;
/**
* @var string
*/
protected $clientContext;
/**
* Constructor allowing to set pageTreeImplementation
*
* @param string $pageTreeClassName
* @param string $clientContext JavaScript context of view client (either 'window' or 'list_frame')
*/
public function __construct(string $pageTreeClassName = null, string $clientContext = 'window')
{
if ($pageTreeClassName !== null) {
$this->pageTreeClassName = $pageTreeClassName;
}
$this->clientContext = $clientContext;
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
}
/*************************************
*
* Page position map:
*
**************************************/
/**
* Creates a "position tree" based on the page tree.
*
* @param int $id Current page id
* @param array $pageinfo Current page record.
* @param string $perms_clause Page selection permission clause.
* @param string $R_URI Current REQUEST_URI
* @return string HTML code for the tree.
*/
public function positionTree($id, $pageinfo, $perms_clause, $R_URI)
{
// Make page tree object:
/** @var \TYPO3\CMS\Backend\Tree\View\PageTreeView $pageTree */
$pageTree = GeneralUtility::makeInstance($this->pageTreeClassName);
$pageTree->init(' AND ' . $perms_clause);
$pageTree->addField('pid');
// Initialize variables:
$this->R_URI = $R_URI;
$this->elUid = $id;
// Create page tree, in $this->depth levels.
$pageTree->getTree($pageinfo['pid'], $this->depth);
// Initialize variables:
$saveLatestUid = [];
$latestInvDepth = $this->depth;
// Traverse the tree:
$lines = [];
foreach ($pageTree->tree as $cc => $dat) {
if ($latestInvDepth > $dat['invertedDepth']) {
$margin = 'style="margin-left: ' . ($dat['invertedDepth'] * 16 + 9) . 'px;"';
$lines[] = '<ul class="list-tree" ' . $margin . '>';
}
// Make link + parameters.
$latestInvDepth = $dat['invertedDepth'];
$saveLatestUid[$latestInvDepth] = $dat;
if (isset($pageTree->tree[$cc - 1])) {
$prev_dat = $pageTree->tree[$cc - 1];
// If current page, subpage?
if ($prev_dat['row']['uid'] == $id) {
// 1) It must be allowed to create a new page and 2) If there are subpages there is no need to render a subpage icon here - it'll be done over the subpages...
if (!$this->dontPrintPageInsertIcons && $this->checkNewPageInPid($id) && !($prev_dat['invertedDepth'] > $pageTree->tree[$cc]['invertedDepth'])) {
end($lines);
$margin = 'style="margin-left: ' . (($dat['invertedDepth'] - 1) * 16 + 9) . 'px;"';
$lines[] = '<ul class="list-tree" ' . $margin . '><li><span class="text-nowrap"><a href="#" onclick="' . htmlspecialchars($this->onClickEvent($id, $id)) . '"><i class="t3-icon fa fa-long-arrow-left" title="' . $this->insertlabel() . '"></i></a></span></li></ul>';
}
}
// If going down
if ($prev_dat['invertedDepth'] > $pageTree->tree[$cc]['invertedDepth']) {
$prevPid = $pageTree->tree[$cc]['row']['pid'];
} elseif ($prev_dat['invertedDepth'] < $pageTree->tree[$cc]['invertedDepth']) {
// If going up
// First of all the previous level should have an icon:
if (!$this->dontPrintPageInsertIcons && $this->checkNewPageInPid($prev_dat['row']['pid'])) {
$prevPid = -$prev_dat['row']['uid'];
end($lines);
$lines[] = '<li><span class="text-nowrap"><a href="#" onclick="' . htmlspecialchars($this->onClickEvent($prevPid, $prev_dat['row']['pid'])) . '"><i class="t3-icon fa fa-long-arrow-left" title="' . $this->insertlabel() . '"></i></a></span></li>';
}
// Then set the current prevPid
$prevPid = -$prev_dat['row']['pid'];
if ($prevPid !== $dat['row']['pid']) {
$lines[] = '</ul>';
}
} else {
// In on the same level
$prevPid = -$prev_dat['row']['uid'];
}
} else {
// First in the tree
$prevPid = $dat['row']['pid'];
}
// print arrow on the same level
if (!$this->dontPrintPageInsertIcons && $this->checkNewPageInPid($dat['row']['pid'])) {
$lines[] = '<span class="text-nowrap"><a href="#" onclick="' . htmlspecialchars($this->onClickEvent($prevPid, $dat['row']['pid'])) . '"><i class="t3-icon fa fa-long-arrow-left" title="' . $this->insertlabel() . '"></i></a></span>';
}
// The line with the icon and title:
$toolTip = BackendUtility::getRecordToolTip($dat['row'], 'pages');
$icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord('pages', $dat['row'], Icon::SIZE_SMALL)->render() . '</span>';
$lines[] = '<span class="text-nowrap">' . $icon . $this->linkPageTitle($this->boldTitle(htmlspecialchars(GeneralUtility::fixed_lgd_cs($dat['row']['title'], $this->getBackendUser()->uc['titleLen'])), $dat, $id), $dat['row']) . '</span>';
}
// If the current page was the last in the tree:
$prev_dat = end($pageTree->tree);
if ($prev_dat['row']['uid'] == $id) {
if (!$this->dontPrintPageInsertIcons && $this->checkNewPageInPid($id)) {
$lines[] = '<ul class="list-tree" style="margin-left: 25px"><li><span class="text-nowrap"><a href="#" onclick="' . htmlspecialchars($this->onClickEvent($id, $id)) . '"><i class="t3-icon fa fa-long-arrow-left" title="' . $this->insertlabel() . '"></i></a></span></li></ul>';
}
}
for ($a = $latestInvDepth; $a <= $this->depth; $a++) {
$dat = $saveLatestUid[$a];
$prevPid = -$dat['row']['uid'];
if (!$this->dontPrintPageInsertIcons && $this->checkNewPageInPid($dat['row']['pid'])) {
if ($latestInvDepth < $dat['invertedDepth']) {
$lines[] = '</ul>';
}
$lines[] = '<span class="text-nowrap"><a href="#" onclick="' . htmlspecialchars($this->onClickEvent($prevPid, $dat['row']['pid'])) . '"><i class="t3-icon fa fa-long-arrow-left" title="' . $this->insertlabel() . '"></i></a></span>';
}
}
$code = '<ul class="list-tree">';
foreach ($lines as $line) {
if ((strpos($line, '<ul') === 0) || (strpos($line, '</ul') === 0)) {
$code .= $line;
} else {
$code .= '<li>' . $line . '</li>';
}
}
$code .= '</ul>';
return $code;
}
/**
* Wrap $t_code in bold IF the $dat uid matches $id
*
* @param string $t_code Title string
* @param array $dat Information array with record array inside.
* @param int $id The current id.
* @return string The title string.
*/
public function boldTitle($t_code, $dat, $id)
{
if ($dat['row']['uid'] == $id) {
$t_code = '<strong>' . $t_code . '</strong>';
}
return $t_code;
}
/**
* Creates the onclick event for the insert-icons.
*
* TSconfig mod.newPageWizard.override may contain an alternative module / route which can be
* used instead of the normal create new page wizard.
*
* @param int $pid The pid.
* @param int $newPagePID New page id.
* @return string Onclick attribute content
*/
public function onClickEvent($pid, $newPagePID)
{
$TSconfig = BackendUtility::getPagesTSconfig($newPagePID)['mod.']['newPageWizard.'] ?? [];
if (isset($TSconfig['override']) && !empty($TSconfig['override'])) {
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$url = $uriBuilder->buildUriFromRoute(
$TSconfig['override'],
[
'positionPid' => $pid,
'newPageId' => $newPagePID,
'cmd' => 'crPage',
'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
]
);
return $this->clientContext . '.location.href=' . GeneralUtility::quoteJSvalue((string)$url) . ';';
}
$params = '&edit[pages][' . $pid . ']=new&returnNewPageId=1';
return BackendUtility::editOnClick($params, '', $this->R_URI);
}
/**
* Get label, htmlspecialchars()'ed
*
* @return string The localized label for "insert new page here
*/
public function insertlabel()
{
return htmlspecialchars($this->getLanguageService()->getLL($this->l_insertNewPageHere));
}
/**
* Wrapping page title.
*
* @param string $str Page title.
* @param array $rec Page record (?)
* @return string Wrapped title.
*/
public function linkPageTitle($str, $rec)
{
return $str;
}
/**
* Checks if the user has permission to created pages inside of the $pid page.
* Uses caching so only one regular lookup is made - hence you can call the function multiple times without worrying about performance.
*
* @param int $pid Page id for which to test.
* @return bool
*/
public function checkNewPageInPid($pid)
{
if (!isset($this->checkNewPageCache[$pid])) {
$pidInfo = BackendUtility::getRecord('pages', $pid);
$this->checkNewPageCache[$pid] = $this->getBackendUser()->isAdmin() || $this->getBackendUser()->doesUserHaveAccess($pidInfo, 8);
}
return $this->checkNewPageCache[$pid];
}
/**
* Returns module configuration for a pid.
*
* @param int $pid Page id for which to get the module configuration.
* @return array The properties of the module configuration for the page id.
* @see onClickEvent()
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
*/
public function getModConfig($pid)
{
trigger_error('PagePositionMap->getModConfig() will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
if (!isset($this->getModConfigCache[$pid])) {
// Acquiring TSconfig for this PID:
$this->getModConfigCache[$pid]['properties'] = BackendUtility::getPagesTSconfig($pid)['mod.']['web_list.']['newPageWiz.'] ?? [];
}
return $this->getModConfigCache[$pid]['properties'];
}
/*************************************
*
* Content element positioning:
*
**************************************/
/**
* Creates HTML for inserting/moving content elements.
*
* @param int $pid page id onto which to insert content element.
* @param int $moveUid Move-uid (tt_content element uid?)
* @param string $colPosList List of columns to show
* @param bool $showHidden If not set, then hidden/starttime/endtime records are filtered out.
* @param string $R_URI Request URI
* @return string HTML
*/
public function printContentElementColumns($pid, $moveUid, $colPosList, $showHidden, $R_URI)
{
$this->R_URI = $R_URI;
$this->moveUid = $moveUid;
$colPosArray = GeneralUtility::trimExplode(',', $colPosList, true);
$lines = [];
foreach ($colPosArray as $kk => $vv) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
if ($showHidden) {
$queryBuilder->getRestrictions()
->removeByType(HiddenRestriction::class)
->removeByType(StartTimeRestriction::class)
->removeByType(EndTimeRestriction::class);
}
$queryBuilder
->select('*')
->from('tt_content')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)),
$queryBuilder->expr()->eq('colPos', $queryBuilder->createNamedParameter($vv, \PDO::PARAM_INT))
)
->orderBy('sorting');
if ((string)$this->cur_sys_language !== '') {
$queryBuilder->andWhere(
$queryBuilder->expr()->eq(
'sys_language_uid',
$queryBuilder->createNamedParameter($this->cur_sys_language, \PDO::PARAM_INT)
)
);
}
$res = $queryBuilder->execute();
$lines[$vv] = [];
$lines[$vv][] = $this->insertPositionIcon('', $vv, $kk, $moveUid, $pid);
while ($row = $res->fetch()) {
BackendUtility::workspaceOL('tt_content', $row);
if (is_array($row)) {
$lines[$vv][] = $this->wrapRecordHeader($this->getRecordHeader($row), $row);
$lines[$vv][] = $this->insertPositionIcon($row, $vv, $kk, $moveUid, $pid);
}
}
}
return $this->printRecordMap($lines, $colPosArray, $pid);
}
/**
* Creates the table with the content columns
*
* @param array $lines Array with arrays of lines for each column
* @param array $colPosArray Column position array
* @param int $pid The id of the page
* @return string HTML
*/
public function printRecordMap($lines, $colPosArray, $pid = 0)
{
$count = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(count($colPosArray), 1);
$backendLayout = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getSelectedBackendLayout', $pid, $this);
if (isset($backendLayout['__config']['backend_layout.'])) {
$this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
$table = '<div class="table-fit"><table class="table table-condensed table-bordered table-vertical-top">';
$colCount = (int)$backendLayout['__config']['backend_layout.']['colCount'];
$rowCount = (int)$backendLayout['__config']['backend_layout.']['rowCount'];
$table .= '<colgroup>';
for ($i = 0; $i < $colCount; $i++) {
$table .= '<col style="width:' . 100 / $colCount . '%"></col>';
}
$table .= '</colgroup>';
$table .= '<tbody>';
$tcaItems = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getColPosListItemsParsed', $pid, $this);
// Cycle through rows
for ($row = 1; $row <= $rowCount; $row++) {
$rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.'];
if (!isset($rowConfig)) {
continue;
}
$table .= '<tr>';
for ($col = 1; $col <= $colCount; $col++) {
$columnConfig = $rowConfig['columns.'][$col . '.'];
if (!isset($columnConfig)) {
continue;
}
// Which tt_content colPos should be displayed inside this cell
$columnKey = (int)$columnConfig['colPos'];
$head = '';
foreach ($tcaItems as $item) {
if ($item[1] == $columnKey) {
$head = htmlspecialchars($this->getLanguageService()->sL($item[0]));
}
}
// Render the grid cell
$table .= '<td'
. (isset($columnConfig['colspan']) ? ' colspan="' . $columnConfig['colspan'] . '"' : '')
. (isset($columnConfig['rowspan']) ? ' rowspan="' . $columnConfig['rowspan'] . '"' : '')
. ' class="col-nowrap col-min'
. (!isset($columnConfig['colPos']) ? ' warning' : '')
. (isset($columnConfig['colPos']) && !$head ? ' danger' : '') . '">';
// Render header
$table .= '<p>';
if (isset($columnConfig['colPos']) && $head) {
$table .= '<strong>' . $this->wrapColumnHeader($head, '') . '</strong>';
} elseif ($columnConfig['colPos']) {
$table .= '<em>' . $this->wrapColumnHeader($this->getLanguageService()->getLL('noAccess'), '') . '</em>';
} else {
$table .= '<em>' . $this->wrapColumnHeader(($this->getLanguageService()->sL($columnConfig['name']) ?: '') . ' (' . $this->getLanguageService()->getLL('notAssigned') . ')', '') . '</em>';
}
$table .= '</p>';
// Render lines
if (isset($columnConfig['colPos']) && $head && !empty($lines[$columnKey])) {
$table .= '<ul class="list-unstyled">';
foreach ($lines[$columnKey] as $line) {
$table .= '<li>' . $line . '</li>';
}
$table .= '</ul>';
}
$table .= '</td>';
}
$table .= '</tr>';
}
$table .= '</tbody>';
$table .= '</table></div>';
} else {
// Traverse the columns here:
$row = '';
foreach ($colPosArray as $kk => $vv) {
$row .= '<td class="col-nowrap col-min" width="' . round(100 / $count) . '%">';
$row .= '<p><strong>' . $this->wrapColumnHeader(htmlspecialchars($this->getLanguageService()->sL(BackendUtility::getLabelFromItemlist('tt_content', 'colPos', $vv))), $vv) . '</strong></p>';
if (!empty($lines[$vv])) {
$row .= '<ul class="list-unstyled">';
foreach ($lines[$vv] as $line) {
$row .= '<li>' . $line . '</li>';
}
$row .= '</ul>';
}
$row .= '</td>';
}
$table = '
<!--
Map of records in columns:
-->
<div class="table-fit">
<table class="table table-condensed table-bordered table-vertical-top">
<tr>' . $row . '</tr>
</table>
</div>
';
}
return $table;
}
/**
* Wrapping the column header
*
* @param string $str Header value
* @param string $vv Column info.
* @return string
* @see printRecordMap()
*/
public function wrapColumnHeader($str, $vv)
{
return $str;
}
/**
* Creates a linked position icon.
*
* @param mixed $row Element row. If this is an array the link will cause an insert after this content element, otherwise
* the link will insert at the first position in the column
* @param string $vv Column position value.
* @param int $kk Column key.
* @param int $moveUid Move uid
* @param int $pid PID value.
* @return string
*/
public function insertPositionIcon($row, $vv, $kk, $moveUid, $pid)
{
if (is_array($row) && !empty($row['uid'])) {
// Use record uid for the hash when inserting after this content element
$uid = $row['uid'];
} else {
// No uid means insert at first position in the column
$uid = '';
}
$cc = hexdec(substr(md5($uid . '-' . $vv . '-' . $kk), 0, 4));
return '<a href="#" onclick="' . htmlspecialchars($this->onClickInsertRecord($row, $vv, $moveUid, $pid, $this->cur_sys_language)) . '" data-dismiss="modal">' . '<i class="t3-icon fa fa-long-arrow-left" name="mImgEnd' . $cc . '" title="' . htmlspecialchars($this->getLanguageService()->getLL($this->l_insertNewRecordHere)) . '"></i></a>';
}
/**
* Create on-click event value.
*
* @param mixed $row The record. If this is not an array with the record data the insert will be for the first position
* in the column
* @param string $vv Column position value.
* @param int $moveUid Move uid
* @param int $pid PID value.
* @param int $sys_lang System language (not used currently)
* @return string
*/
public function onClickInsertRecord($row, $vv, $moveUid, $pid, $sys_lang = 0)
{
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
if (is_array($row)) {
$location = $uriBuilder->buildUriFromRoute('tce_db', [
'cmd[tt_content][' . $moveUid . '][' . $this->moveOrCopy . ']' => '-' . $row['uid'],
'redirect' => $this->R_URI,
]);
} else {
$location = $uriBuilder->buildUriFromRoute('tce_db', [
'cmd[tt_content][' . $moveUid . '][' . $this->moveOrCopy . ']' => $pid,
'data[tt_content][' . $moveUid . '][colPos]' => $vv,
'redirect' => $this->R_URI,
]);
}
// returns to prev. page
return $this->clientContext . '.location.href=' . GeneralUtility::quoteJSvalue((string)$location) . ';return false;';
}
/**
* Wrapping the record header (from getRecordHeader())
*
* @param string $str HTML content
* @param string $row Record array.
* @return string HTML content
*/
public function wrapRecordHeader($str, $row)
{
return $str;
}
/**
* Create record header (includes the record icon, record title etc.)
*
* @param array $row Record row.
* @return string HTML
*/
public function getRecordHeader($row)
{
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$toolTip = BackendUtility::getRecordToolTip($row, 'tt_content');
$line = '<span ' . $toolTip . ' title="' . BackendUtility::getRecordIconAltText($row, 'tt_content') . '">' . $iconFactory->getIconForRecord('tt_content', $row, Icon::SIZE_SMALL)->render() . '</span>';
$line .= BackendUtility::getRecordTitle('tt_content', $row, true);
return $this->wrapRecordTitle($line, $row);
}
/**
* Wrapping the title of the record.
*
* @param string $str The title value.
* @param array $row The record row.
* @return string Wrapped title string.
*/
public function wrapRecordTitle($str, $row)
{
return '<a href="' . htmlspecialchars(GeneralUtility::linkThisScript(['uid' => (int)$row['uid'], 'moveUid' => ''])) . '">' . $str . '</a>';
}
/**
* Returns the BackendUser
*
* @return BackendUserAuthentication
*/
protected function getBackendUser()
{
return $GLOBALS['BE_USER'];
}
/**
* Returns the LanguageService
*
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,146 @@
<?php
namespace TYPO3\CMS\Backend\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!
*/
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Generate a page-tree, non-browsable.
*/
class PageTreeView extends AbstractTreeView
{
/**
* @var array
*/
public $fieldArray = [
'uid',
'pid',
'is_siteroot',
'title',
'doktype',
'nav_title',
'mount_pid',
'php_tree_stop',
't3ver_id',
't3ver_state',
'hidden',
'starttime',
'endtime',
'fe_group',
'module',
'extendToSubpages',
'nav_hide'
];
/**
* override to use this treeName
* @var string
*/
public $treeName = 'pages';
/**
* override to use this table
* @var string
*/
public $table = 'pages';
/**
* @var bool
*/
public $ext_showNavTitle = false;
/**
* Init function
* REMEMBER to feed a $clause which will filter out non-readable pages!
*
* @param string $clause Part of where query which will filter out non-readable pages.
* @param string $orderByFields Record ORDER BY field
*/
public function init($clause = '', $orderByFields = '')
{
parent::init(' AND deleted=0 AND sys_language_uid=0 ' . $clause, 'sorting');
}
/**
* Returns TRUE/FALSE if the next level for $id should be expanded - and all levels should, so we always return 1.
*
* @param int $id ID (uid) to test for (see extending classes where this is checked against session data)
* @return bool
*/
public function expandNext($id)
{
return 1;
}
/**
* Generate the plus/minus icon for the browsable tree.
* In this case, there is no plus-minus icon displayed.
*
* @param array $row Record for the entry
* @param int $a The current entry number
* @param int $c The total number of entries. If equal to $a, a 'bottom' element is returned.
* @param int $nextCount The number of sub-elements to the current element.
* @param bool $isExpand The element was expanded to render subelements if this flag is set.
* @return string Image tag with the plus/minus icon.
* @internal
* @see AbstractTreeView::PMicon()
*/
public function PMicon($row, $a, $c, $nextCount, $isExpand)
{
return '<span class="treeline-icon treeline-icon-join' . ($a == $c ? 'bottom' : '') . '"></span>';
}
/**
* Get stored tree structure AND updating it if needed according to incoming PM GET var.
* - Here we just set it to nothing since we want to just render the tree, nothing more.
*
* @internal
*/
public function initializePositionSaving()
{
$this->stored = [];
}
/**
* Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
* Do NOT htmlspecialchar the string from this function - has already been done.
*
* @param array $row The input row array (where the key "title" is used for the title)
* @param int $titleLen Title length (30)
* @return string The title.
*/
public function getTitleStr($row, $titleLen = 30)
{
$lang = $this->getLanguageService();
if ($this->ext_showNavTitle && isset($row['nav_title']) && trim($row['nav_title']) !== '') {
$title = '<span title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:title')) . ' '
. htmlspecialchars(trim($row['title'])) . '">'
. htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['nav_title'], $titleLen))
. '</span>';
} else {
$title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLen));
if (isset($row['nav_title']) && trim($row['nav_title']) !== '') {
$title = '<span title="'
. htmlspecialchars($lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.nav_title'))
. ' ' . htmlspecialchars(trim($row['nav_title'])) . '">' . $title
. '</span>';
}
$title = trim($row['title']) === ''
? '<em>[' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</em>'
: $title;
}
return $title;
}
}