first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace TYPO3\CMS\Backend\Backend\Avatar;
|
||||
|
||||
/*
|
||||
* 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\Cache\CacheManager;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Service\DependencyOrderingService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Main class to render an avatar image of a certain Backend user, resolving any avatar provider
|
||||
* that takes care of fetching the image.
|
||||
*
|
||||
* See render() and getImgTag() as main entry points
|
||||
*/
|
||||
class Avatar
|
||||
{
|
||||
/**
|
||||
* Array of sorted and initialized avatar providers
|
||||
*
|
||||
* @var AvatarProviderInterface[]
|
||||
*/
|
||||
protected $avatarProviders = [];
|
||||
|
||||
/**
|
||||
* Renders an avatar based on a Fluid template which contains some base wrapper classes and does
|
||||
* a simple caching functionality, used in Avatar ViewHelper for instance
|
||||
*
|
||||
* @param array $backendUser be_users record
|
||||
* @param int $size width and height of the image
|
||||
* @param bool $showIcon show the record icon
|
||||
* @return string
|
||||
*/
|
||||
public function render(array $backendUser = null, int $size = 32, bool $showIcon = false)
|
||||
{
|
||||
if (!is_array($backendUser)) {
|
||||
$backendUser = $this->getBackendUser()->user;
|
||||
}
|
||||
|
||||
$cacheId = 'avatar_' . md5(
|
||||
$backendUser['uid'] . '/' .
|
||||
(string)$size . '/' .
|
||||
(string)$showIcon
|
||||
);
|
||||
|
||||
$avatar = $this->getCache()->get($cacheId);
|
||||
|
||||
if (!$avatar) {
|
||||
$this->validateSortAndInitiateAvatarProviders();
|
||||
$view = $this->getFluidTemplateObject();
|
||||
|
||||
$view->assignMultiple([
|
||||
'image' => $this->getImgTag($backendUser, $size),
|
||||
'showIcon' => $showIcon,
|
||||
'backendUser' => $backendUser
|
||||
]);
|
||||
$avatar = $view->render();
|
||||
$this->getCache()->set($cacheId, $avatar);
|
||||
}
|
||||
|
||||
return $avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML <img> tag for the avatar
|
||||
*
|
||||
* @param array $backendUser be_users record
|
||||
* @param int $size
|
||||
* @return string
|
||||
*/
|
||||
public function getImgTag(array $backendUser = null, $size = 32)
|
||||
{
|
||||
if (!is_array($backendUser)) {
|
||||
$backendUser = $this->getBackendUser()->user;
|
||||
}
|
||||
|
||||
$avatarImage = false;
|
||||
if ($backendUser !== null) {
|
||||
$avatarImage = $this->getImage($backendUser, $size);
|
||||
}
|
||||
|
||||
if (!$avatarImage) {
|
||||
$avatarImage = GeneralUtility::makeInstance(
|
||||
Image::class,
|
||||
PathUtility::stripPathSitePrefix(GeneralUtility::getFileAbsFileName('EXT:core/Resources/Public/Icons/T3Icons/avatar/avatar-default.svg')),
|
||||
$size,
|
||||
$size
|
||||
);
|
||||
}
|
||||
$imageTag = '<img src="' . htmlspecialchars($avatarImage->getUrl(true)) . '" ' .
|
||||
'width="' . (int)$avatarImage->getWidth() . '" ' .
|
||||
'height="' . (int)$avatarImage->getHeight() . '" />';
|
||||
|
||||
return $imageTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Image from first provider that returns one
|
||||
*
|
||||
* @param array $backendUser be_users record
|
||||
* @param int $size
|
||||
* @return Image|null
|
||||
*/
|
||||
public function getImage(array $backendUser, $size)
|
||||
{
|
||||
foreach ($this->avatarProviders as $provider) {
|
||||
$avatarImage = $provider->getImage($backendUser, $size);
|
||||
if (!empty($avatarImage)) {
|
||||
return $avatarImage;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the registered avatar providers
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function validateSortAndInitiateAvatarProviders()
|
||||
{
|
||||
$providers = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders'] ?? [];
|
||||
if (empty($providers)) {
|
||||
return;
|
||||
}
|
||||
foreach ($providers as $identifier => $configuration) {
|
||||
if (empty($configuration) || !is_array($configuration)) {
|
||||
throw new \RuntimeException(
|
||||
'Missing configuration for avatar provider "' . $identifier . '".',
|
||||
1439317801
|
||||
);
|
||||
}
|
||||
if (!is_string($configuration['provider']) || empty($configuration['provider']) || !class_exists($configuration['provider']) || !is_subclass_of(
|
||||
$configuration['provider'],
|
||||
AvatarProviderInterface::class
|
||||
)) {
|
||||
throw new \RuntimeException(
|
||||
'The avatar provider "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . AvatarProviderInterface::class . '".',
|
||||
1439317802
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$orderedProviders = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($providers);
|
||||
|
||||
// Initializes providers
|
||||
foreach ($orderedProviders as $configuration) {
|
||||
$this->avatarProviders[] = GeneralUtility::makeInstance($configuration['provider']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new standalone view, shorthand function
|
||||
*
|
||||
* @param string $filename Which templateFile should be used.
|
||||
* @return StandaloneView
|
||||
*/
|
||||
protected function getFluidTemplateObject(string $filename = null): StandaloneView
|
||||
{
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setLayoutRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Layouts')]);
|
||||
$view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
|
||||
$view->setTemplateRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates')]);
|
||||
|
||||
if ($filename === null) {
|
||||
$filename = 'Main.html';
|
||||
}
|
||||
|
||||
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/Avatar/' . $filename));
|
||||
|
||||
$view->getRequest()->setControllerExtensionName('Backend');
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FrontendInterface
|
||||
*/
|
||||
protected function getCache()
|
||||
{
|
||||
return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Backend\Avatar;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contract for avatar providers that ensure how an avater should be rendered for a given Backend User
|
||||
*/
|
||||
interface AvatarProviderInterface
|
||||
{
|
||||
/**
|
||||
* Returns an Image object, prepared for output, based on a given be_users record
|
||||
*
|
||||
* @param array $backendUser be_users record
|
||||
* @param int $size
|
||||
* @return Image|null
|
||||
*/
|
||||
public function getImage(array $backendUser, $size);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Backend\Avatar;
|
||||
|
||||
/*
|
||||
* 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\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
|
||||
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Avatar Provider used for rendering avatars based on local files (based on FAL), stored in the be_users.avatar
|
||||
* relation field with sys_file_reference.
|
||||
*/
|
||||
class DefaultAvatarProvider implements AvatarProviderInterface
|
||||
{
|
||||
/**
|
||||
* Return an Image object for rendering the avatar, based on a FAL-based file
|
||||
*
|
||||
* @param array $backendUser be_users record
|
||||
* @param int $size
|
||||
* @return Image|null
|
||||
*/
|
||||
public function getImage(array $backendUser, $size)
|
||||
{
|
||||
$fileUid = $this->getAvatarFileUid($backendUser['uid']);
|
||||
if ($fileUid === 0) {
|
||||
// Early return if there is no valid image file UID
|
||||
return null;
|
||||
}
|
||||
// Get file object
|
||||
try {
|
||||
$file = ResourceFactory::getInstance()->getFileObject($fileUid);
|
||||
$processedImage = $file->process(
|
||||
ProcessedFile::CONTEXT_IMAGECROPSCALEMASK,
|
||||
['width' => $size . 'c', 'height' => $size . 'c']
|
||||
);
|
||||
|
||||
$image = GeneralUtility::makeInstance(
|
||||
Image::class,
|
||||
$processedImage->getPublicUrl(),
|
||||
$processedImage->getProperty('width'),
|
||||
$processedImage->getProperty('height')
|
||||
);
|
||||
} catch (FileDoesNotExistException $e) {
|
||||
// No image found
|
||||
$image = null;
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sys_file UID of the avatar of the given backend user ID
|
||||
*
|
||||
* @param int $backendUserId the UID of the be_users record
|
||||
* @return int the sys_file UID or 0 if none found
|
||||
*/
|
||||
protected function getAvatarFileUid($backendUserId)
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
|
||||
$fileUid = $queryBuilder
|
||||
->select('uid_local')
|
||||
->from('sys_file_reference')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'tablenames',
|
||||
$queryBuilder->createNamedParameter('be_users', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'fieldname',
|
||||
$queryBuilder->createNamedParameter('avatar', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'table_local',
|
||||
$queryBuilder->createNamedParameter('sys_file', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid_foreign',
|
||||
$queryBuilder->createNamedParameter((int)$backendUserId, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn();
|
||||
|
||||
return (int)$fileUid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Backend\Avatar;
|
||||
|
||||
/*
|
||||
* 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\Core\Environment;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
|
||||
/**
|
||||
* Acts as a pseudo model for holding all information of an avatar image
|
||||
* Holds url + dimensions of avatar image
|
||||
*/
|
||||
class Image
|
||||
{
|
||||
/**
|
||||
* Url of avatar image. Needs to be relative to the website root or an absolute URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $width;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $height;
|
||||
|
||||
/**
|
||||
* @param string $url url of image. Needs to be relative to the website root or an absolute URL.
|
||||
* @param int $width width of image
|
||||
* @param int $height height of image
|
||||
*/
|
||||
public function __construct($url, $width, $height)
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->width = (int)$width;
|
||||
$this->height = (int)$height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the URL to the the avatar image
|
||||
*
|
||||
* @param bool $relativeToCurrentScript Determines whether the URL returned should be relative to the current script, in case it is relative at all.
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl($relativeToCurrentScript = false)
|
||||
{
|
||||
$url = $this->url;
|
||||
if ($relativeToCurrentScript && !GeneralUtility::isValidUrl($url)) {
|
||||
$url = PathUtility::getAbsoluteWebPath(Environment::getPublicPath() . '/' . $url);
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,792 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Backend\Shortcut;
|
||||
|
||||
/*
|
||||
* 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\Module\ModuleLoader;
|
||||
use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException;
|
||||
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\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Repository for backend shortcuts
|
||||
*
|
||||
* @internal This class is a specific Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ShortcutRepository
|
||||
{
|
||||
/**
|
||||
* @var int Number of super global (All) group
|
||||
*/
|
||||
protected const SUPERGLOBAL_GROUP = -100;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $shortcuts;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $shortcutGroups;
|
||||
|
||||
/**
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* @var ModuleLoader
|
||||
*/
|
||||
protected $moduleLoader;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$this->moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
|
||||
$this->moduleLoader->load($GLOBALS['TBE_MODULES']);
|
||||
|
||||
$this->shortcutGroups = $this->initShortcutGroups();
|
||||
$this->shortcuts = $this->initShortcuts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a shortcut by its uid
|
||||
*
|
||||
* @param int $shortcutId Shortcut id to get the complete shortcut for
|
||||
* @return mixed An array containing the shortcut's data on success or FALSE on failure
|
||||
*/
|
||||
public function getShortcutById(int $shortcutId)
|
||||
{
|
||||
foreach ($this->shortcuts as $shortcut) {
|
||||
if ($shortcut['raw']['uid'] === $shortcutId) {
|
||||
return $shortcut;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets shortcuts for a specific group
|
||||
*
|
||||
* @param int $groupId Group Id
|
||||
* @return array Array of shortcuts that matched the group
|
||||
*/
|
||||
public function getShortcutsByGroup(int $groupId): array
|
||||
{
|
||||
$shortcuts = [];
|
||||
|
||||
foreach ($this->shortcuts as $shortcut) {
|
||||
if ($shortcut['group'] === $groupId) {
|
||||
$shortcuts[] = $shortcut;
|
||||
}
|
||||
}
|
||||
|
||||
return $shortcuts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shortcut groups the current user has access to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getShortcutGroups(): array
|
||||
{
|
||||
$shortcutGroups = $this->shortcutGroups;
|
||||
|
||||
if (!$this->getBackendUser()->isAdmin()) {
|
||||
foreach ($shortcutGroups as $groupId => $groupName) {
|
||||
if ((int)$groupId < 0) {
|
||||
unset($shortcutGroups[$groupId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $shortcutGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* runs through the available shortcuts an collects their groups
|
||||
*
|
||||
* @return array Array of groups which have shortcuts
|
||||
*/
|
||||
public function getGroupsFromShortcuts(): array
|
||||
{
|
||||
$groups = [];
|
||||
|
||||
foreach ($this->shortcuts as $shortcut) {
|
||||
$groups[$shortcut['group']] = $this->shortcutGroups[$shortcut['group']];
|
||||
}
|
||||
|
||||
return array_unique($groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if there already is a shortcut entry for a given TYPO3 URL
|
||||
*
|
||||
* @param string $url
|
||||
* @return bool
|
||||
*/
|
||||
public function shortcutExists(string $url): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_be_shortcuts');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
|
||||
$uid = $queryBuilder->select('uid')
|
||||
->from('sys_be_shortcuts')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'userid',
|
||||
$queryBuilder->createNamedParameter($this->getBackendUser()->user['uid'], \PDO::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'url',
|
||||
$queryBuilder->createNamedParameter($url, \PDO::PARAM_STR)
|
||||
)
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn();
|
||||
|
||||
return (bool)$uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shortcut
|
||||
*
|
||||
* @param string $url URL of the new shortcut
|
||||
* @param string $module module identifier of the new shortcut
|
||||
* @param string $parentModule parent module identifier of the new shortcut
|
||||
* @param string $title title of the new shortcut
|
||||
* @return bool
|
||||
* @throws \RuntimeException if the given URL is invalid
|
||||
*/
|
||||
public function addShortcut(string $url, string $module, string $parentModule = '', string $title = ''): bool
|
||||
{
|
||||
if (empty($url) || empty($module)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$queryParts = parse_url($url);
|
||||
$queryParameters = [];
|
||||
parse_str($queryParts['query'] ?? '', $queryParameters);
|
||||
|
||||
if (!empty($queryParameters['scheme'])) {
|
||||
throw new \RuntimeException('Shortcut URLs must be relative', 1518785877);
|
||||
}
|
||||
|
||||
$languageService = $this->getLanguageService();
|
||||
$title = $title ?: 'Shortcut';
|
||||
$titlePrefix = '';
|
||||
$type = 'other';
|
||||
$table = '';
|
||||
$recordId = 0;
|
||||
$pageId = 0;
|
||||
|
||||
if (is_array($queryParameters['edit'])) {
|
||||
$table = key($queryParameters['edit']);
|
||||
$recordId = (int)key($queryParameters['edit'][$table]);
|
||||
$pageId = (int)BackendUtility::getRecord($table, $recordId)['pid'];
|
||||
$languageFile = 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf';
|
||||
$action = $queryParameters['edit'][$table][$recordId];
|
||||
|
||||
switch ($action) {
|
||||
case 'edit':
|
||||
$type = 'edit';
|
||||
$titlePrefix = $languageService->sL($languageFile . ':shortcut_edit');
|
||||
break;
|
||||
case 'new':
|
||||
$type = 'new';
|
||||
$titlePrefix = $languageService->sL($languageFile . ':shortcut_create');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if given id is a combined identifier
|
||||
if (!empty($queryParameters['id']) && preg_match('/^[\d]+:/', $queryParameters['id'])) {
|
||||
try {
|
||||
$resourceFactory = ResourceFactory::getInstance();
|
||||
$resource = $resourceFactory->getObjectFromCombinedIdentifier($queryParameters['id']);
|
||||
$title = trim(sprintf(
|
||||
'%s (%s)',
|
||||
$titlePrefix,
|
||||
$resource->getName()
|
||||
));
|
||||
} catch (ResourceDoesNotExistException $e) {
|
||||
}
|
||||
} else {
|
||||
// Lookup the title of this page and use it as default description
|
||||
$pageId = $pageId ?: $recordId ?: $this->extractPageIdFromShortcutUrl($url);
|
||||
$page = $pageId ? BackendUtility::getRecord('pages', $pageId) : null;
|
||||
|
||||
if (!empty($page)) {
|
||||
// Set the name to the title of the page
|
||||
if ($type === 'other') {
|
||||
$title = sprintf(
|
||||
'%s (%s)',
|
||||
$title,
|
||||
$page['title']
|
||||
);
|
||||
} else {
|
||||
$title = sprintf(
|
||||
'%s %s (%s)',
|
||||
$titlePrefix,
|
||||
$languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title']),
|
||||
$page['title']
|
||||
);
|
||||
}
|
||||
} elseif (!empty($table)) {
|
||||
$title = trim(sprintf(
|
||||
'%s %s',
|
||||
$titlePrefix,
|
||||
$languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title'])
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if ($title === 'Shortcut') {
|
||||
$moduleLabels = $this->moduleLoader->getLabelsForModule($module);
|
||||
|
||||
if (!empty($moduleLabels['shortdescription'])) {
|
||||
$title = $this->getLanguageService()->sL($moduleLabels['shortdescription']);
|
||||
}
|
||||
}
|
||||
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_be_shortcuts');
|
||||
$affectedRows = $queryBuilder
|
||||
->insert('sys_be_shortcuts')
|
||||
->values([
|
||||
'userid' => $this->getBackendUser()->user['uid'],
|
||||
'module_name' => $module . '|' . $parentModule,
|
||||
'url' => $url,
|
||||
'description' => $title,
|
||||
'sorting' => $GLOBALS['EXEC_TIME'],
|
||||
])
|
||||
->execute();
|
||||
|
||||
return $affectedRows === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a shortcut
|
||||
*
|
||||
* @param int $id identifier of a shortcut
|
||||
* @param string $title new title of the shortcut
|
||||
* @param int $groupId new group identifier of the shortcut
|
||||
* @return bool
|
||||
*/
|
||||
public function updateShortcut(int $id, string $title, int $groupId): bool
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_be_shortcuts');
|
||||
$queryBuilder->update('sys_be_shortcuts')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->set('description', $title)
|
||||
->set('sc_group', $groupId);
|
||||
|
||||
if (!$backendUser->isAdmin()) {
|
||||
// Users can only modify their own shortcuts
|
||||
$queryBuilder->andWhere(
|
||||
$queryBuilder->expr()->eq(
|
||||
'userid',
|
||||
$queryBuilder->createNamedParameter($backendUser->user['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
);
|
||||
|
||||
if ($groupId < 0) {
|
||||
$queryBuilder->set('sc_group', 0);
|
||||
}
|
||||
}
|
||||
|
||||
$affectedRows = $queryBuilder->execute();
|
||||
|
||||
return $affectedRows === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a shortcut
|
||||
*
|
||||
* @param int $id identifier of a shortcut
|
||||
* @return bool
|
||||
*/
|
||||
public function removeShortcut(int $id): bool
|
||||
{
|
||||
$shortcut = $this->getShortcutById($id);
|
||||
$success = false;
|
||||
|
||||
if ($shortcut['raw']['userid'] == $this->getBackendUser()->user['uid']) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_be_shortcuts');
|
||||
$affectedRows = $queryBuilder->delete('sys_be_shortcuts')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
|
||||
if ($affectedRows === 1) {
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the available shortcut groups from default groups, user TSConfig, and global groups
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function initShortcutGroups(): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$backendUser = $this->getBackendUser();
|
||||
// By default, 5 groups are set
|
||||
$shortcutGroups = [
|
||||
1 => '1',
|
||||
2 => '1',
|
||||
3 => '1',
|
||||
4 => '1',
|
||||
5 => '1',
|
||||
];
|
||||
|
||||
// Groups from TSConfig
|
||||
$bookmarkGroups = $backendUser->getTSConfig()['options.']['bookmarkGroups.'] ?? [];
|
||||
|
||||
if (is_array($bookmarkGroups)) {
|
||||
foreach ($bookmarkGroups as $groupId => $label) {
|
||||
if (!empty($label)) {
|
||||
$shortcutGroups[$groupId] = (string)$label;
|
||||
} elseif ($backendUser->isAdmin()) {
|
||||
unset($shortcutGroups[$groupId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate global groups, all global groups have negative IDs.
|
||||
if (!empty($shortcutGroups)) {
|
||||
foreach ($shortcutGroups as $groupId => $groupLabel) {
|
||||
$shortcutGroups[$groupId * -1] = $groupLabel;
|
||||
}
|
||||
}
|
||||
|
||||
// Group -100 is kind of superglobal and can't be changed.
|
||||
$shortcutGroups[self::SUPERGLOBAL_GROUP] = '1';
|
||||
|
||||
// Add labels
|
||||
$languageFile = 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf';
|
||||
|
||||
foreach ($shortcutGroups as $groupId => $groupLabel) {
|
||||
$groupId = (int)$groupId;
|
||||
$label = $groupLabel;
|
||||
|
||||
if ($groupLabel === '1') {
|
||||
$label = $languageService->sL($languageFile . ':bookmark_group_' . abs($groupId));
|
||||
|
||||
if (empty($label)) {
|
||||
// Fallback label
|
||||
$label = $languageService->sL($languageFile . ':bookmark_group') . ' ' . abs($groupId);
|
||||
}
|
||||
}
|
||||
|
||||
if ($groupId < 0) {
|
||||
// Global group
|
||||
$label = $languageService->sL($languageFile . ':bookmark_global') . ': ' . (!empty($label) ? $label : abs($groupId));
|
||||
|
||||
if ($groupId === self::SUPERGLOBAL_GROUP) {
|
||||
$label = $languageService->sL($languageFile . ':bookmark_global') . ': ' . $languageService->sL($languageFile . ':bookmark_all');
|
||||
}
|
||||
}
|
||||
|
||||
$shortcutGroups[$groupId] = htmlspecialchars($label);
|
||||
}
|
||||
|
||||
return $shortcutGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the shortcuts for the current user
|
||||
*
|
||||
* @return array Array of shortcuts
|
||||
*/
|
||||
protected function initShortcuts(): array
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
// Traverse shortcuts
|
||||
$lastGroup = 0;
|
||||
$shortcuts = [];
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_be_shortcuts');
|
||||
$result = $queryBuilder->select('*')
|
||||
->from('sys_be_shortcuts')
|
||||
->where(
|
||||
$queryBuilder->expr()->andX(
|
||||
$queryBuilder->expr()->eq(
|
||||
'userid',
|
||||
$queryBuilder->createNamedParameter($backendUser->user['uid'], \PDO::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->gte(
|
||||
'sc_group',
|
||||
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
)
|
||||
->orWhere(
|
||||
$queryBuilder->expr()->in(
|
||||
'sc_group',
|
||||
$queryBuilder->createNamedParameter(
|
||||
array_keys($this->getGlobalShortcutGroups()),
|
||||
Connection::PARAM_INT_ARRAY
|
||||
)
|
||||
)
|
||||
)
|
||||
->orderBy('sc_group')
|
||||
->addOrderBy('sorting')
|
||||
->execute();
|
||||
|
||||
while ($row = $result->fetch()) {
|
||||
$shortcut = ['raw' => $row];
|
||||
|
||||
list($row['module_name'], $row['M_module_name']) = explode('|', $row['module_name']);
|
||||
|
||||
$queryParts = parse_url($row['url']);
|
||||
// Explode GET vars recursively
|
||||
$queryParameters = [];
|
||||
parse_str($queryParts['query'] ?? '', $queryParameters);
|
||||
|
||||
if ($row['module_name'] === 'xMOD_alt_doc.php' && is_array($queryParameters['edit'])) {
|
||||
$shortcut['table'] = key($queryParameters['edit']);
|
||||
$shortcut['recordid'] = key($queryParameters['edit'][$shortcut['table']]);
|
||||
|
||||
if ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] === 'edit') {
|
||||
$shortcut['type'] = 'edit';
|
||||
} elseif ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] === 'new') {
|
||||
$shortcut['type'] = 'new';
|
||||
}
|
||||
|
||||
if (substr((string)$shortcut['recordid'], -1) === ',') {
|
||||
$shortcut['recordid'] = substr((string)$shortcut['recordid'], 0, -1);
|
||||
}
|
||||
} else {
|
||||
$shortcut['type'] = 'other';
|
||||
}
|
||||
|
||||
// Check for module access
|
||||
$moduleName = $row['M_module_name'] ?: $row['module_name'];
|
||||
|
||||
// Check if the user has access to this module
|
||||
if (!is_array($this->moduleLoader->checkMod($moduleName))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pageId = $this->extractPageIdFromShortcutUrl($row['url']);
|
||||
|
||||
if (!$backendUser->isAdmin()) {
|
||||
if (MathUtility::canBeInterpretedAsInteger($pageId)) {
|
||||
// Check for webmount access
|
||||
if ($backendUser->isInWebMount($pageId) === null) {
|
||||
continue;
|
||||
}
|
||||
// Check for record access
|
||||
$pageRow = BackendUtility::getRecord('pages', $pageId);
|
||||
|
||||
if ($pageRow === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$backendUser->doesUserHaveAccess($pageRow, Permission::PAGE_SHOW)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$moduleParts = explode('_', $moduleName);
|
||||
$shortcutGroup = (int)$row['sc_group'];
|
||||
|
||||
if ($shortcutGroup && $lastGroup !== $shortcutGroup && $shortcutGroup !== self::SUPERGLOBAL_GROUP) {
|
||||
$shortcut['groupLabel'] = $this->getShortcutGroupLabel($shortcutGroup);
|
||||
}
|
||||
|
||||
$lastGroup = $shortcutGroup;
|
||||
|
||||
if ($row['description']) {
|
||||
$shortcut['label'] = $row['description'];
|
||||
} else {
|
||||
$shortcut['label'] = GeneralUtility::fixed_lgd_cs(rawurldecode($queryParts['query']), 150);
|
||||
}
|
||||
|
||||
$shortcut['group'] = $shortcutGroup;
|
||||
$shortcut['icon'] = $this->getShortcutIcon($row, $shortcut);
|
||||
$shortcut['iconTitle'] = $this->getShortcutIconTitle($shortcut['label'], $row['module_name'], $row['M_module_name']);
|
||||
$shortcut['action'] = 'jump(' . GeneralUtility::quoteJSvalue($this->getTokenUrl($row['url'])) . ',' . GeneralUtility::quoteJSvalue($moduleName) . ',' . GeneralUtility::quoteJSvalue($moduleParts[0]) . ', ' . (int)$pageId . ');';
|
||||
|
||||
$shortcuts[] = $shortcut;
|
||||
}
|
||||
|
||||
return $shortcuts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of global groups, shortcuts in these groups are available to all users
|
||||
*
|
||||
* @return array Array of global groups
|
||||
*/
|
||||
protected function getGlobalShortcutGroups(): array
|
||||
{
|
||||
$globalGroups = [];
|
||||
|
||||
foreach ($this->shortcutGroups as $groupId => $groupLabel) {
|
||||
if ($groupId < 0) {
|
||||
$globalGroups[$groupId] = $groupLabel;
|
||||
}
|
||||
}
|
||||
|
||||
return $globalGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the label for a shortcut group
|
||||
*
|
||||
* @param int $groupId A shortcut group id
|
||||
* @return string The shortcut group label, can be an empty string if no group was found for the id
|
||||
*/
|
||||
protected function getShortcutGroupLabel(int $groupId): string
|
||||
{
|
||||
return $this->shortcutGroups[$groupId] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the icon for the shortcut
|
||||
*
|
||||
* @param array $row
|
||||
* @param array $shortcut
|
||||
* @return string Shortcut icon as img tag
|
||||
*/
|
||||
protected function getShortcutIcon(array $row, array $shortcut): string
|
||||
{
|
||||
switch ($row['module_name']) {
|
||||
case 'xMOD_alt_doc.php':
|
||||
$table = $shortcut['table'];
|
||||
$recordid = $shortcut['recordid'];
|
||||
$icon = '';
|
||||
|
||||
if ($shortcut['type'] === 'edit') {
|
||||
// Creating the list of fields to include in the SQL query:
|
||||
$selectFields[] = 'uid';
|
||||
$selectFields[] = 'pid';
|
||||
|
||||
if ($table === 'pages') {
|
||||
$selectFields[] = 'module';
|
||||
$selectFields[] = 'extendToSubpages';
|
||||
$selectFields[] = 'doktype';
|
||||
}
|
||||
|
||||
if (is_array($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
|
||||
$selectFields = array_merge($selectFields, $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']);
|
||||
}
|
||||
|
||||
if ($GLOBALS['TCA'][$table]['ctrl']['typeicon_column']) {
|
||||
$selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
|
||||
}
|
||||
|
||||
if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
|
||||
$selectFields[] = 't3ver_state';
|
||||
}
|
||||
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable($table);
|
||||
$queryBuilder->select(...array_unique(array_values($selectFields)))
|
||||
->from($table)
|
||||
->where(
|
||||
$queryBuilder->expr()->in(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($recordid, \PDO::PARAM_INT)
|
||||
)
|
||||
);
|
||||
|
||||
$row = $queryBuilder->execute()->fetch();
|
||||
|
||||
$icon = $this->iconFactory->getIconForRecord($table, (array)$row, Icon::SIZE_SMALL)->render();
|
||||
} elseif ($shortcut['type'] === 'new') {
|
||||
$icon = $this->iconFactory->getIconForRecord($table, [], Icon::SIZE_SMALL)->render();
|
||||
}
|
||||
break;
|
||||
case 'file_edit':
|
||||
$icon = $this->iconFactory->getIcon('mimetypes-text-html', Icon::SIZE_SMALL)->render();
|
||||
break;
|
||||
case 'wizard_rte':
|
||||
$icon = $this->iconFactory->getIcon('mimetypes-word', Icon::SIZE_SMALL)->render();
|
||||
break;
|
||||
default:
|
||||
$iconIdentifier = '';
|
||||
$moduleName = $row['module_name'];
|
||||
|
||||
if (strpos($moduleName, '_') !== false) {
|
||||
list($mainModule, $subModule) = explode('_', $moduleName, 2);
|
||||
$iconIdentifier = $this->moduleLoader->modules[$mainModule]['sub'][$subModule]['iconIdentifier'];
|
||||
} elseif (!empty($moduleName)) {
|
||||
$iconIdentifier = $this->moduleLoader->modules[$moduleName]['iconIdentifier'];
|
||||
}
|
||||
|
||||
if (!$iconIdentifier) {
|
||||
$iconIdentifier = 'empty-empty';
|
||||
}
|
||||
|
||||
$icon = $this->iconFactory->getIcon($iconIdentifier, Icon::SIZE_SMALL)->render();
|
||||
}
|
||||
|
||||
return $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns title for the shortcut icon
|
||||
*
|
||||
* @param string $shortcutLabel Shortcut label
|
||||
* @param string $moduleName Backend module name (key)
|
||||
* @param string $parentModuleName Parent module label
|
||||
* @return string Title for the shortcut icon
|
||||
*/
|
||||
protected function getShortcutIconTitle(string $shortcutLabel, string $moduleName, string $parentModuleName = ''): string
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
if (strpos($moduleName, 'xMOD_') === 0) {
|
||||
$title = substr($moduleName, 5);
|
||||
} else {
|
||||
list($mainModule, $subModule) = explode('_', $moduleName);
|
||||
$mainModuleLabels = $this->moduleLoader->getLabelsForModule($mainModule);
|
||||
$title = $languageService->sL($mainModuleLabels['title']);
|
||||
|
||||
if (!empty($subModule)) {
|
||||
$subModuleLabels = $this->moduleLoader->getLabelsForModule($moduleName);
|
||||
$title .= '>' . $languageService->sL($subModuleLabels['title']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($parentModuleName) {
|
||||
$title .= ' (' . $parentModuleName . ')';
|
||||
}
|
||||
|
||||
$title .= ': ' . $shortcutLabel;
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ID of the page in the URL if found.
|
||||
*
|
||||
* @param string $url The URL of the current shortcut link
|
||||
* @return int If a page ID was found, it is returned. Otherwise: 0
|
||||
*/
|
||||
protected function extractPageIdFromShortcutUrl(string $url): int
|
||||
{
|
||||
return (int)preg_replace('/.*[\\?&]id=([^&]+).*/', '$1', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the correct token, if the url is an index.php script
|
||||
* @todo: this needs love
|
||||
*
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
protected function getTokenUrl(string $url): string
|
||||
{
|
||||
$parsedUrl = parse_url($url);
|
||||
$parameters = [];
|
||||
parse_str($parsedUrl['query'] ?? '', $parameters);
|
||||
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
// parse the returnUrl and replace the module token of it
|
||||
if (!empty($parameters['returnUrl'])) {
|
||||
$parsedReturnUrl = parse_url($parameters['returnUrl']);
|
||||
$returnUrlParameters = [];
|
||||
parse_str($parsedReturnUrl['query'] ?? '', $returnUrlParameters);
|
||||
|
||||
if (strpos($parsedReturnUrl['path'] ?? '', 'index.php') !== false && !empty($returnUrlParameters['route'])) {
|
||||
$module = $returnUrlParameters['route'];
|
||||
$parameters['returnUrl'] = (string)$uriBuilder->buildUriFromRoutePath($module, $returnUrlParameters);
|
||||
$url = $parsedUrl['path'] . '?' . http_build_query($parameters, '', '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($parameters['M']) && empty($parameters['route'])) {
|
||||
$parameters['route'] = $parameters['M'];
|
||||
unset($parameters['M']);
|
||||
}
|
||||
|
||||
if (strpos($parsedUrl['path'], 'index.php') !== false && isset($parameters['route'])) {
|
||||
$routePath = $parameters['route'];
|
||||
/** @var \TYPO3\CMS\Backend\Routing\Router $router */
|
||||
$router = GeneralUtility::makeInstance(Router::class);
|
||||
|
||||
try {
|
||||
$route = $router->match($routePath);
|
||||
|
||||
if ($route) {
|
||||
$routeIdentifier = $route->getOption('_identifier');
|
||||
unset($parameters['route']);
|
||||
$url = (string)$uriBuilder->buildUriFromRoute($routeIdentifier, $parameters);
|
||||
}
|
||||
} catch (ResourceNotFoundException $e) {
|
||||
$url = '';
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Backend\ToolbarItems;
|
||||
|
||||
/*
|
||||
* 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\Toolbar\ClearCacheActionsHookInterface;
|
||||
use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Render cache clearing toolbar item
|
||||
* Adds a dropdown if there are more than one item to clear (usually for admins to render the flush all caches)
|
||||
*
|
||||
* The dropdown items can be extended via a hook named "cacheActions".
|
||||
*/
|
||||
class ClearCacheToolbarItem implements ToolbarItemInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $cacheActions = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $optionValues = [];
|
||||
|
||||
/**
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Toolbar/ClearCacheMenu');
|
||||
$isAdmin = $this->getBackendUser()->isAdmin();
|
||||
$userTsConfig = $this->getBackendUser()->getTSConfig();
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
|
||||
// Clear all page-related caches
|
||||
if ($isAdmin || $userTsConfig['options.']['clearCache.']['pages'] ?? false) {
|
||||
$this->cacheActions[] = [
|
||||
'id' => 'pages',
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:flushPageCachesTitle',
|
||||
'description' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:flushPageCachesDescription',
|
||||
'href' => (string)$uriBuilder->buildUriFromRoute('tce_db', ['cacheCmd' => 'pages']),
|
||||
'iconIdentifier' => 'actions-system-cache-clear-impact-low'
|
||||
];
|
||||
$this->optionValues[] = 'pages';
|
||||
}
|
||||
|
||||
// Clearing of all caches is only shown if explicitly enabled via TSConfig
|
||||
// or if BE-User is admin and the TSconfig explicitly disables the possibility for admins.
|
||||
// This is useful for big production systems where admins accidentally could slow down the system.
|
||||
if (($userTsConfig['options.']['clearCache.']['all'] ?? false)
|
||||
|| ($isAdmin && (bool)($userTsConfig['options.']['clearCache.']['all'] ?? true))
|
||||
) {
|
||||
$this->cacheActions[] = [
|
||||
'id' => 'all',
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:flushAllCachesTitle2',
|
||||
'description' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:flushAllCachesDescription2',
|
||||
'href' => (string)$uriBuilder->buildUriFromRoute('tce_db', ['cacheCmd' => 'all']),
|
||||
'iconIdentifier' => 'actions-system-cache-clear-impact-high'
|
||||
];
|
||||
$this->optionValues[] = 'all';
|
||||
}
|
||||
|
||||
// Hook for manipulating cacheActions
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['additionalBackendItems']['cacheActions'] ?? [] as $cacheAction) {
|
||||
$hookObject = GeneralUtility::makeInstance($cacheAction);
|
||||
if (!$hookObject instanceof ClearCacheActionsHookInterface) {
|
||||
throw new \UnexpectedValueException($cacheAction . ' must implement interface ' . ClearCacheActionsHookInterface::class, 1228262000);
|
||||
}
|
||||
$hookObject->manipulateCacheActions($this->cacheActions, $this->optionValues);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user has access to this toolbar item
|
||||
*
|
||||
* @return bool TRUE if user has access, FALSE if not
|
||||
*/
|
||||
public function checkAccess()
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
if ($backendUser->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
foreach ($this->optionValues as $value) {
|
||||
if ($backendUser->getTSConfig()['options.']['clearCache.'][$value] ?? false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render clear cache icon, based on the option if there is more than one icon or just one.
|
||||
*
|
||||
* @return string Icon HTML
|
||||
*/
|
||||
public function getItem()
|
||||
{
|
||||
if ($this->hasDropDown()) {
|
||||
return $this->getFluidTemplateObject('ClearCacheToolbarItem.html')->render();
|
||||
}
|
||||
$view = $this->getFluidTemplateObject('ClearCacheToolbarItemSingle.html');
|
||||
$cacheAction = end($this->cacheActions);
|
||||
$view->assignMultiple([
|
||||
'link' => $cacheAction['href'],
|
||||
'title' => $cacheAction['title'],
|
||||
'iconIdentifier' => $cacheAction['iconIdentifier'],
|
||||
]);
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render drop down
|
||||
*
|
||||
* @return string Drop down HTML
|
||||
*/
|
||||
public function getDropDown()
|
||||
{
|
||||
$view = $this->getFluidTemplateObject('ClearCacheToolbarItemDropDown.html');
|
||||
$view->assign('cacheActions', $this->cacheActions);
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* No additional attributes needed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalAttributes()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This item has a drop down if there is more than one cache action available for the current Backend user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDropDown()
|
||||
{
|
||||
return count($this->cacheActions) > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position relative to others
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return 25;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PageRenderer
|
||||
*/
|
||||
protected function getPageRenderer()
|
||||
{
|
||||
return GeneralUtility::makeInstance(PageRenderer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new standalone view, shorthand function
|
||||
*
|
||||
* @param string $filename Which templateFile should be used.
|
||||
* @return StandaloneView
|
||||
*/
|
||||
protected function getFluidTemplateObject(string $filename): StandaloneView
|
||||
{
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
|
||||
$view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials/ToolbarItems']);
|
||||
$view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ToolbarItems']);
|
||||
|
||||
$view->setTemplate($filename);
|
||||
|
||||
$view->getRequest()->setControllerExtensionName('Backend');
|
||||
return $view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Backend\ToolbarItems;
|
||||
|
||||
/*
|
||||
* 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\Domain\Model\Module\BackendModule;
|
||||
use TYPO3\CMS\Backend\Domain\Repository\Module\BackendModuleRepository;
|
||||
use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Help toolbar item
|
||||
*/
|
||||
class HelpToolbarItem implements ToolbarItemInterface
|
||||
{
|
||||
/**
|
||||
* @var BackendModule
|
||||
*/
|
||||
protected $helpModuleMenu;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$backendModuleRepository = GeneralUtility::makeInstance(BackendModuleRepository::class);
|
||||
$helpModuleMenu = $backendModuleRepository->findByModuleName('help');
|
||||
if ($helpModuleMenu && $helpModuleMenu->getChildren()->count() > 0) {
|
||||
$this->helpModuleMenu = $helpModuleMenu;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Users see this if a module is available
|
||||
*
|
||||
* @return bool TRUE
|
||||
*/
|
||||
public function checkAccess()
|
||||
{
|
||||
return (bool)$this->helpModuleMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render help icon
|
||||
*
|
||||
* @return string toolbar item for the help icon
|
||||
*/
|
||||
public function getItem()
|
||||
{
|
||||
return $this->getFluidTemplateObject('HelpToolbarItem.html')->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render drop down
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDropDown()
|
||||
{
|
||||
$view = $this->getFluidTemplateObject('HelpToolbarItemDropDown.html');
|
||||
$view->assign('modules', $this->helpModuleMenu->getChildren());
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* No additional attributes needed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalAttributes()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This item has a drop down
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDropDown()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position relative to others
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return 70;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new standalone view, shorthand function
|
||||
*
|
||||
* @param string $filename Which templateFile should be used.
|
||||
* @return StandaloneView
|
||||
*/
|
||||
protected function getFluidTemplateObject(string $filename): StandaloneView
|
||||
{
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
|
||||
$view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials/ToolbarItems']);
|
||||
$view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ToolbarItems']);
|
||||
$view->setTemplate($filename);
|
||||
|
||||
$view->getRequest()->setControllerExtensionName('Backend');
|
||||
return $view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Backend\ToolbarItems;
|
||||
|
||||
/*
|
||||
* 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\Domain\Repository\Module\BackendModuleRepository;
|
||||
use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Adds backend live search to the toolbar by adding JavaScript and adding an input search field
|
||||
*/
|
||||
class LiveSearchToolbarItem implements ToolbarItemInterface
|
||||
{
|
||||
/**
|
||||
* Loads the needed JavaScript file, ands includes it to the page renderer
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/LiveSearch');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user has access to this toolbar item,
|
||||
* only allowed when the list module is available.
|
||||
* Live search is heavily dependent on the list module and only available when that module is.
|
||||
*
|
||||
* @return bool TRUE if user has access, FALSE if not
|
||||
*/
|
||||
public function checkAccess()
|
||||
{
|
||||
$backendModuleRepository = GeneralUtility::makeInstance(BackendModuleRepository::class);
|
||||
$listModule = $backendModuleRepository->findByModuleName('web_list');
|
||||
return $listModule !== null && $listModule !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render search field
|
||||
*
|
||||
* @return string Live search form HTML
|
||||
*/
|
||||
public function getItem()
|
||||
{
|
||||
return $this->getFluidTemplateObject('LiveSearchToolbarItem.html')->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* This item needs to additional attributes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalAttributes()
|
||||
{
|
||||
return ['class' => 'toolbar-item-search t3js-toolbar-item-search'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This item has no drop down
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDropDown()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* No drop down here
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDropDown()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Position relative to others, live search should be very right
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return 90;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current PageRenderer
|
||||
*
|
||||
* @return PageRenderer
|
||||
*/
|
||||
protected function getPageRenderer()
|
||||
{
|
||||
return GeneralUtility::makeInstance(PageRenderer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new standalone view, shorthand function
|
||||
*
|
||||
* @param string $filename Which templateFile should be used.
|
||||
* @return StandaloneView
|
||||
*/
|
||||
protected function getFluidTemplateObject(string $filename): StandaloneView
|
||||
{
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
|
||||
$view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials/ToolbarItems']);
|
||||
$view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ToolbarItems']);
|
||||
|
||||
$view->setTemplate($filename);
|
||||
|
||||
$view->getRequest()->setControllerExtensionName('Backend');
|
||||
return $view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Backend\ToolbarItems;
|
||||
|
||||
/*
|
||||
* 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\Backend\Shortcut\ShortcutRepository;
|
||||
use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Class to render the shortcut menu
|
||||
*
|
||||
* @internal This class is a specific Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ShortcutToolbarItem implements ToolbarItemInterface
|
||||
{
|
||||
/**
|
||||
* @var ShortcutRepository
|
||||
*/
|
||||
protected $shortcutRepository;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->shortcutRepository = GeneralUtility::makeInstance(ShortcutRepository::class);
|
||||
|
||||
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
|
||||
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Toolbar/ShortcutMenu');
|
||||
$languageService = $this->getLanguageService();
|
||||
$languageFile = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf';
|
||||
$pageRenderer->addInlineLanguageLabelArray([
|
||||
'bookmark.delete' => $languageService->sL($languageFile . ':toolbarItems.bookmarksDelete'),
|
||||
'bookmark.confirmDelete' => $languageService->sL($languageFile . ':toolbarItems.confirmBookmarksDelete'),
|
||||
'bookmark.create' => $languageService->sL($languageFile . ':toolbarItems.createBookmark'),
|
||||
'bookmark.savedTitle' => $languageService->sL($languageFile . ':toolbarItems.bookmarkSavedTitle'),
|
||||
'bookmark.savedMessage' => $languageService->sL($languageFile . ':toolbarItems.bookmarkSavedMessage'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user has access to this toolbar item
|
||||
*
|
||||
* @return bool TRUE if user has access, FALSE if not
|
||||
*/
|
||||
public function checkAccess()
|
||||
{
|
||||
return (bool)($this->getBackendUser()->getTSConfig()['options.']['enableBookmarks'] ?? false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render shortcut icon
|
||||
*
|
||||
* @return string HTML
|
||||
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getItem()
|
||||
{
|
||||
return $this->getFluidTemplateObject('Item.html')->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* This item has a drop down
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDropDown()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render drop down content
|
||||
*
|
||||
* @return string HTML
|
||||
*/
|
||||
public function getDropDown()
|
||||
{
|
||||
$shortcutMenu = [];
|
||||
$groups = $this->shortcutRepository->getGroupsFromShortcuts();
|
||||
arsort($groups, SORT_NUMERIC);
|
||||
|
||||
foreach ($groups as $groupId => $groupLabel) {
|
||||
$shortcutMenu[] = [
|
||||
'id' => (int)$groupId,
|
||||
'title' => $groupLabel,
|
||||
'shortcuts' => $this->shortcutRepository->getShortcutsByGroup($groupId),
|
||||
];
|
||||
}
|
||||
|
||||
$dropDownView = $this->getFluidTemplateObject('DropDown.html');
|
||||
$dropDownView->assign('shortcutMenu', $shortcutMenu);
|
||||
|
||||
return $dropDownView->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* This toolbar item needs no additional attributes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalAttributes()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Position relative to others, live search should be very right
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a new standalone view, shorthand function
|
||||
*
|
||||
* @param string $templateFilename
|
||||
* @return StandaloneView
|
||||
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException
|
||||
* @throws \InvalidArgumentException
|
||||
* @internal param string $templateFile
|
||||
*/
|
||||
protected function getFluidTemplateObject(string $templateFilename): StandaloneView
|
||||
{
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
|
||||
$view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']);
|
||||
$view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ShortcutToolbarItem']);
|
||||
$view->setTemplate($templateFilename);
|
||||
$view->getRequest()->setControllerExtensionName('Backend');
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Core\Localization\LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Backend\ToolbarItems;
|
||||
|
||||
/*
|
||||
* 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\Toolbar\Enumeration\InformationStatus;
|
||||
use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Routing\RouteNotFoundException;
|
||||
use TYPO3\CMS\Core\Utility\CommandUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Render system info toolbar item
|
||||
*/
|
||||
class SystemInformationToolbarItem implements ToolbarItemInterface
|
||||
{
|
||||
/**
|
||||
* Number displayed as badge on the dropdown trigger
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $totalCount = 0;
|
||||
|
||||
/**
|
||||
* Holds the highest severity
|
||||
*
|
||||
* @var InformationStatus
|
||||
*/
|
||||
protected $highestSeverity;
|
||||
|
||||
/**
|
||||
* The CSS class for the badge
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $severityBadgeClass = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $systemInformation = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $systemMessages = [];
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $signalSlotDispatcher;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $maximumCountInBadge = 99;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Toolbar/SystemInformationMenu');
|
||||
$this->highestSeverity = InformationStatus::cast(InformationStatus::STATUS_INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a system message.
|
||||
* This is a callback method for signal receivers.
|
||||
*
|
||||
* @param string $text The text to be displayed
|
||||
* @param string $status The status of this system message
|
||||
* @param int $count Will be added to the total count
|
||||
* @param string $module The associated module
|
||||
* @param string $params Query string with additional parameters
|
||||
*/
|
||||
public function addSystemMessage($text, $status = InformationStatus::STATUS_OK, $count = 0, $module = '', $params = '')
|
||||
{
|
||||
$this->totalCount += (int)$count;
|
||||
|
||||
/** @var InformationStatus $messageSeverity */
|
||||
$messageSeverity = InformationStatus::cast($status);
|
||||
// define the severity for the badge
|
||||
if ($messageSeverity->isGreaterThan($this->highestSeverity)) {
|
||||
$this->highestSeverity = $messageSeverity;
|
||||
}
|
||||
|
||||
$this->systemMessages[] = [
|
||||
'module' => $module,
|
||||
'params' => $params,
|
||||
'count' => (int)$count,
|
||||
'status' => $messageSeverity,
|
||||
'text' => $text
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a system information.
|
||||
* This is a callback method for signal receivers.
|
||||
*
|
||||
* @param string $title The title of this system information
|
||||
* @param string $value The associated value
|
||||
* @param string $iconIdentifier The icon identifier
|
||||
* @param string $status The status of this system information
|
||||
*/
|
||||
public function addSystemInformation($title, $value, $iconIdentifier, $status = InformationStatus::STATUS_NOTICE)
|
||||
{
|
||||
$this->systemInformation[] = [
|
||||
'title' => $title,
|
||||
'value' => $value,
|
||||
'iconIdentifier' => $iconIdentifier,
|
||||
'status' => $status
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user has access to this toolbar item
|
||||
*
|
||||
* @return bool TRUE if user has access, FALSE if not
|
||||
*/
|
||||
public function checkAccess()
|
||||
{
|
||||
return $this->getBackendUserAuthentication()->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render system information dropdown
|
||||
*
|
||||
* @return string Icon HTML
|
||||
*/
|
||||
public function getItem()
|
||||
{
|
||||
return $this->getFluidTemplateObject('SystemInformationToolbarItem.html')->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render drop down
|
||||
*
|
||||
* @return string Drop down HTML
|
||||
*/
|
||||
public function getDropDown()
|
||||
{
|
||||
if (!$this->checkAccess()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->collectInformation();
|
||||
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$view = $this->getFluidTemplateObject('SystemInformationDropDown.html');
|
||||
|
||||
try {
|
||||
$environmentToolUrl = (string)$uriBuilder->buildUriFromRoute('tools_toolsenvironment');
|
||||
} catch (RouteNotFoundException $e) {
|
||||
$environmentToolUrl = '';
|
||||
}
|
||||
|
||||
$view->assignMultiple([
|
||||
'environmentToolUrl' => $environmentToolUrl,
|
||||
'messages' => $this->systemMessages,
|
||||
'count' => $this->totalCount > $this->maximumCountInBadge ? $this->maximumCountInBadge . '+' : $this->totalCount,
|
||||
'severityBadgeClass' => $this->severityBadgeClass,
|
||||
'systemInformation' => $this->systemInformation
|
||||
]);
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* No additional attributes needed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalAttributes()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This item has a drop down
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDropDown()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position relative to others
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return 75;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the information for the menu
|
||||
*/
|
||||
protected function collectInformation()
|
||||
{
|
||||
$this->getTypo3Version();
|
||||
$this->getWebServer();
|
||||
$this->getPhpVersion();
|
||||
$this->getDatabase();
|
||||
$this->getApplicationContext();
|
||||
$this->getComposerMode();
|
||||
$this->getGitRevision();
|
||||
$this->getOperatingSystem();
|
||||
|
||||
$this->emitGetSystemInformation();
|
||||
$this->emitLoadMessages();
|
||||
|
||||
$this->severityBadgeClass = !$this->highestSeverity->equals(InformationStatus::STATUS_NOTICE) ? 'badge-' . (string)$this->highestSeverity : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TYPO3 version
|
||||
*/
|
||||
protected function getTypo3Version()
|
||||
{
|
||||
$this->systemInformation[] = [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.sysinfo.typo3-version',
|
||||
'value' => VersionNumberUtility::getCurrentTypo3Version(),
|
||||
'iconIdentifier' => 'information-typo3-version'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the webserver software
|
||||
*/
|
||||
protected function getWebServer()
|
||||
{
|
||||
$this->systemInformation[] = [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.sysinfo.webserver',
|
||||
'value' => $_SERVER['SERVER_SOFTWARE'],
|
||||
'iconIdentifier' => 'information-webserver'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PHP version
|
||||
*/
|
||||
protected function getPhpVersion()
|
||||
{
|
||||
$this->systemInformation[] = [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.sysinfo.phpversion',
|
||||
'value' => PHP_VERSION,
|
||||
'iconIdentifier' => 'information-php-version'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database info
|
||||
*/
|
||||
protected function getDatabase()
|
||||
{
|
||||
foreach (GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionNames() as $connectionName) {
|
||||
$this->systemInformation[] = [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.sysinfo.database',
|
||||
'titleAddition' => $connectionName,
|
||||
'value' => GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionByName($connectionName)
|
||||
->getServerVersion(),
|
||||
'iconIdentifier' => 'information-database'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application context
|
||||
*/
|
||||
protected function getApplicationContext()
|
||||
{
|
||||
$applicationContext = GeneralUtility::getApplicationContext();
|
||||
$this->systemInformation[] = [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.sysinfo.applicationcontext',
|
||||
'value' => (string)$applicationContext,
|
||||
'status' => $applicationContext->isProduction() ? InformationStatus::STATUS_OK : InformationStatus::STATUS_WARNING,
|
||||
'iconIdentifier' => 'information-application-context'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the information if the Composer mode is enabled or disabled to the displayed system information
|
||||
*/
|
||||
protected function getComposerMode()
|
||||
{
|
||||
if (!Environment::isComposerMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->systemInformation[] = [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.sysinfo.composerMode',
|
||||
'value' => $GLOBALS['LANG']->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enabled'),
|
||||
'iconIdentifier' => 'information-composer-mode'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current GIT revision and branch
|
||||
*/
|
||||
protected function getGitRevision()
|
||||
{
|
||||
if (!StringUtility::endsWith(TYPO3_version, '-dev') || SystemEnvironmentBuilder::isFunctionDisabled('exec')) {
|
||||
return;
|
||||
}
|
||||
// check if git exists
|
||||
CommandUtility::exec('git --version', $_, $returnCode);
|
||||
if ((int)$returnCode !== 0) {
|
||||
// git is not available
|
||||
return;
|
||||
}
|
||||
|
||||
$revision = trim(CommandUtility::exec('git rev-parse --short HEAD'));
|
||||
$branch = trim(CommandUtility::exec('git rev-parse --abbrev-ref HEAD'));
|
||||
if (!empty($revision) && !empty($branch)) {
|
||||
$this->systemInformation[] = [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.sysinfo.gitrevision',
|
||||
'value' => sprintf('%s [%s]', $revision, $branch),
|
||||
'iconIdentifier' => 'information-git'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the system kernel and version
|
||||
*/
|
||||
protected function getOperatingSystem()
|
||||
{
|
||||
switch (PHP_OS_FAMILY) {
|
||||
case 'Linux':
|
||||
$icon = 'linux';
|
||||
break;
|
||||
case 'Darwin':
|
||||
$icon = 'apple';
|
||||
break;
|
||||
case 'Windows':
|
||||
$icon = 'windows';
|
||||
break;
|
||||
default:
|
||||
$icon = 'unknown';
|
||||
}
|
||||
$this->systemInformation[] = [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.sysinfo.operatingsystem',
|
||||
'value' => PHP_OS . ' ' . php_uname('r'),
|
||||
'iconIdentifier' => 'information-os-' . $icon
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the "getSystemInformation" signal
|
||||
*/
|
||||
protected function emitGetSystemInformation()
|
||||
{
|
||||
$this->getSignalSlotDispatcher()->dispatch(__CLASS__, 'getSystemInformation', [$this]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the "loadMessages" signal
|
||||
*/
|
||||
protected function emitLoadMessages()
|
||||
{
|
||||
$this->getSignalSlotDispatcher()->dispatch(__CLASS__, 'loadMessages', [$this]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new standalone view, shorthand function
|
||||
*
|
||||
* @param string $filename Which templateFile should be used.
|
||||
* @return StandaloneView
|
||||
*/
|
||||
protected function getFluidTemplateObject(string $filename): StandaloneView
|
||||
{
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
|
||||
$view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials/ToolbarItems']);
|
||||
$view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ToolbarItems']);
|
||||
|
||||
$view->setTemplate($filename);
|
||||
|
||||
$view->getRequest()->setControllerExtensionName('Backend');
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current PageRenderer
|
||||
*
|
||||
* @return PageRenderer
|
||||
*/
|
||||
protected function getPageRenderer()
|
||||
{
|
||||
return GeneralUtility::makeInstance(PageRenderer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SignalSlot dispatcher
|
||||
*
|
||||
* @return Dispatcher
|
||||
*/
|
||||
protected function getSignalSlotDispatcher()
|
||||
{
|
||||
if (!isset($this->signalSlotDispatcher)) {
|
||||
$this->signalSlotDispatcher = GeneralUtility::makeInstance(ObjectManager::class)->get(Dispatcher::class);
|
||||
}
|
||||
return $this->signalSlotDispatcher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Backend\ToolbarItems;
|
||||
|
||||
/*
|
||||
* 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\Domain\Repository\Module\BackendModuleRepository;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* User toolbar item
|
||||
*
|
||||
* @internal This class is a specific Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class UserToolbarItem implements ToolbarItemInterface
|
||||
{
|
||||
/**
|
||||
* Item is always enabled
|
||||
*
|
||||
* @return bool TRUE
|
||||
*/
|
||||
public function checkAccess()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render username and an icon
|
||||
*
|
||||
* @return string HTML
|
||||
*/
|
||||
public function getItem()
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$view = $this->getFluidTemplateObject('UserToolbarItem.html');
|
||||
$view->assignMultiple([
|
||||
'currentUser' => $backendUser->user,
|
||||
'switchUserMode' => $backendUser->user['ses_backuserid'],
|
||||
]);
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render drop down
|
||||
*
|
||||
* @return string HTML
|
||||
*/
|
||||
public function getDropDown()
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
|
||||
/** @var BackendModuleRepository $backendModuleRepository */
|
||||
$backendModuleRepository = GeneralUtility::makeInstance(BackendModuleRepository::class);
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
|
||||
$mostRecentUsers = [];
|
||||
if (ExtensionManagementUtility::isLoaded('beuser')
|
||||
&& $backendUser->isAdmin()
|
||||
&& (int)$backendUser->user['ses_backuserid'] === 0
|
||||
&& isset($backendUser->uc['recentSwitchedToUsers'])
|
||||
&& is_array($backendUser->uc['recentSwitchedToUsers'])
|
||||
) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
|
||||
$result = $queryBuilder
|
||||
->select('uid', 'username', 'realName')
|
||||
->from('be_users')
|
||||
->where(
|
||||
$queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($backendUser->uc['recentSwitchedToUsers'], Connection::PARAM_INT_ARRAY))
|
||||
)->execute();
|
||||
|
||||
// Flip the array to have a "sorted" list of items
|
||||
$mostRecentUsers = array_flip($backendUser->uc['recentSwitchedToUsers']);
|
||||
|
||||
while ($row = $result->fetch()) {
|
||||
$row['switchUserLink'] = (string)$uriBuilder->buildUriFromRoute(
|
||||
'system_BeuserTxBeuser',
|
||||
[
|
||||
'SwitchUser' => $row['uid']
|
||||
]
|
||||
);
|
||||
|
||||
$mostRecentUsers[$row['uid']] = $row;
|
||||
}
|
||||
|
||||
// Remove any item that is not an array (means, the stored uid is not available anymore)
|
||||
$mostRecentUsers = array_filter($mostRecentUsers, function ($record) {
|
||||
return is_array($record);
|
||||
});
|
||||
|
||||
$availableUsers = array_keys($mostRecentUsers);
|
||||
if (!empty(array_diff($backendUser->uc['recentSwitchedToUsers'], $availableUsers))) {
|
||||
$backendUser->uc['recentSwitchedToUsers'] = $availableUsers;
|
||||
$backendUser->writeUC();
|
||||
}
|
||||
}
|
||||
|
||||
$view = $this->getFluidTemplateObject('UserToolbarItemDropDown.html');
|
||||
$view->assignMultiple([
|
||||
'modules' => $backendModuleRepository->findByModuleName('user')->getChildren(),
|
||||
'logoutUrl' => (string)$uriBuilder->buildUriFromRoute('logout'),
|
||||
'switchUserMode' => $this->getBackendUser()->user['ses_backuserid'],
|
||||
'recentUsers' => $mostRecentUsers,
|
||||
]);
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an additional class if user is in "switch user" mode
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalAttributes()
|
||||
{
|
||||
$result = [
|
||||
'class' => 'toolbar-item-user'
|
||||
];
|
||||
if ($this->getBackendUser()->user['ses_backuserid']) {
|
||||
$result['class'] .= ' su-user';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This item has a drop down
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDropDown()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position relative to others
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return 80;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new standalone view, shorthand function
|
||||
*
|
||||
* @param string $filename Which templateFile should be used.
|
||||
*
|
||||
* @return StandaloneView
|
||||
*/
|
||||
protected function getFluidTemplateObject(string $filename): StandaloneView
|
||||
{
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
|
||||
$view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials/ToolbarItems']);
|
||||
$view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ToolbarItems']);
|
||||
|
||||
$view->setTemplate($filename);
|
||||
|
||||
$view->getRequest()->setControllerExtensionName('Backend');
|
||||
return $view;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user