Commit 7c20e766 authored by Benni Mack's avatar Benni Mack
Browse files

[TASK] Deprecate various Context-based methods in TSFE

Various methods can now use the Context API, making the
dependency to TSFE less relevant for future changes.

Resolves: #97531
Releases: main
Change-Id: I3daaddd9684e36a5bf58343cd1fdc43d29713a3a
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73910


Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent c9fecd62
.. include:: /Includes.rst.txt
=================================================================================
Deprecation: #97531 - Context-related methods within TypoScriptFrontendController
=================================================================================
See :issue:`97531`
Description
===========
One of the main classes within TYPO3 Frontend —
:php:`TypoScriptFrontendController` a.k.a. :php:`$GLOBALS['TSFE']` —
had various short-hand functionality to access the Context API.
This is mainly for historical reasons, before the Context API
was introduced in TYPO3 v9.
For this reason, the related methods have been marked as deprecated:
* :php:`initUserGroups()`
* :php:`isUserOrGroupSet()`
* :php:`isBackendUserLoggedIn()`
* :php:`doWorkspacePreview()`
* :php:`whichWorkspace()`
Impact
======
Calling the methods directly will trigger a PHP deprecation warning.
Affected Installations
======================
TYPO3 installations with custom extensions using one of the methods.
The extension scanner will report any usage as weak match.
Migration
=========
Migrate towards the Context API instead:
.. code-block:: php
// Is this request within a Workspace currently
$context->getPropertyFromAspect('workspace', 'isOffline', false);
// Is a frontend user logged in
$context->getPropertyFromAspect('frontend.user', 'isLoggedIn', false);
.. index:: Frontend, FullyScanned, ext:frontend
...@@ -179,13 +179,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -179,13 +179,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface
*/ */
public $fe_user; public $fe_user;
/**
* Value that contains the simulated usergroup if any
* @var int
* @internal only to be used in AdminPanel, and within TYPO3 Core
*/
public $simUserGroup = 0;
/** /**
* "CONFIG" object from TypoScript. Array generated based on the TypoScript * "CONFIG" object from TypoScript. Array generated based on the TypoScript
* configuration of the current page. Saved with the cached pages. * configuration of the current page. Saved with the cached pages.
...@@ -522,9 +515,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -522,9 +515,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
/** /**
* Initializes the front-end user groups. * Initializes the front-end user groups.
* Sets frontend.user aspect based on front-end user status. * Sets frontend.user aspect based on front-end user status.
* @deprecated will be removed in TYPO3 v13.0. Use the Context API directly.
*/ */
public function initUserGroups() public function initUserGroups()
{ {
trigger_error('TSFE->initUserGroups() will be removed in TYPO3 v13.0. Use the Context API directly.', E_USER_DEPRECATED);
$this->context->setAspect('frontend.user', $this->fe_user->createUserAspect()); $this->context->setAspect('frontend.user', $this->fe_user->createUserAspect());
} }
...@@ -532,9 +527,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -532,9 +527,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
* Checking if a user is logged in or a group constellation different from "0,-1" * Checking if a user is logged in or a group constellation different from "0,-1"
* *
* @return bool TRUE if either a login user is found (array fe_user->user) OR if the gr_list is set to something else than '0,-1' (could be done even without a user being logged in!) * @return bool TRUE if either a login user is found (array fe_user->user) OR if the gr_list is set to something else than '0,-1' (could be done even without a user being logged in!)
* @deprecated will be removed in TYPO3 v13.0. Use the Context API directly.
*/ */
public function isUserOrGroupSet() public function isUserOrGroupSet()
{ {
trigger_error('TSFE->isUserOrGroupSet() will be removed in TYPO3 v13.0. Use the Context API directly.', E_USER_DEPRECATED);
/** @var UserAspect $userAspect */ /** @var UserAspect $userAspect */
$userAspect = $this->context->getAspect('frontend.user'); $userAspect = $this->context->getAspect('frontend.user');
return $userAspect->isUserOrGroupSet(); return $userAspect->isUserOrGroupSet();
...@@ -544,9 +541,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -544,9 +541,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
* Checks if a backend user is logged in * Checks if a backend user is logged in
* *
* @return bool whether a backend user is logged in * @return bool whether a backend user is logged in
* @deprecated will be removed in TYPO3 v13.0. Use the Context API directly.
*/ */
public function isBackendUserLoggedIn() public function isBackendUserLoggedIn()
{ {
trigger_error('TSFE->isBackendUserLoggedIn() will be removed in TYPO3 v13.0. Use the Context API directly.', E_USER_DEPRECATED);
return (bool)$this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false); return (bool)$this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false);
} }
...@@ -569,11 +568,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -569,11 +568,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface
* - sys_page * - sys_page
* - sys_page->where_groupAccess * - sys_page->where_groupAccess
* - sys_page->where_hid_del * - sys_page->where_hid_del
* - Context: FrontendUser Aspect
* - register['SYS_LASTCHANGED'] * - register['SYS_LASTCHANGED']
* - pageNotFound * - pageNotFound
* *
* Via getPageAndRootlineWithDomain() * Via getPageAndRootline()
* *
* - rootLine * - rootLine
* - page * - page
...@@ -585,17 +583,15 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -585,17 +583,15 @@ class TypoScriptFrontendController implements LoggerAwareInterface
* *
* @todo: * @todo:
* *
* On the first impression the method does to much. * On the first impression the method does too much.
* The reasons are manifold. * The reasons are manifold.
* *
* 1.) The user group setup could be done once on a higher level. * 1.) The workflow of the resolution could be elaborated to be less
*
* 2.) The workflow of the resolution could be elaborated to be less
* tangled. Maybe the check of the page id to be below the domain via the * tangled. Maybe the check of the page id to be below the domain via the
* root line doesn't need to be done each time, but for the final result * root line doesn't need to be done each time, but for the final result
* only. * only.
* *
* 3.) The root line does not need to be directly addressed by this class. * 2.) The root line does not need to be directly addressed by this class.
* A root line is always related to one page. The rootline could be handled * A root line is always related to one page. The rootline could be handled
* indirectly by page objects. Page objects still don't exist. * indirectly by page objects. Page objects still don't exist.
* *
...@@ -610,11 +606,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -610,11 +606,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface
} }
$timeTracker = $this->getTimeTracker(); $timeTracker = $this->getTimeTracker();
// Now, get the id, validate access etc:
// Set the valid usergroups for FE
$this->initUserGroups();
// Initialize the PageRepository has to be done after the frontend usergroups are initialized / resolved, as
// frontend group aspect is modified before
$this->sys_page = GeneralUtility::makeInstance(PageRepository::class, $this->context); $this->sys_page = GeneralUtility::makeInstance(PageRepository::class, $this->context);
$timeTracker->push('determineId rootLine/'); $timeTracker->push('determineId rootLine/');
try { try {
...@@ -755,7 +746,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -755,7 +746,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
$requestedPageIsHidden = false; $requestedPageIsHidden = false;
try { try {
$hiddenField = $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled'] ?? ''; $hiddenField = $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled'] ?? '';
$includeHiddenPages = $this->context->getPropertyFromAspect('visibility', 'includeHiddenPages') || $this->isBackendUserLoggedIn(); $includeHiddenPages = $this->context->getPropertyFromAspect('visibility', 'includeHiddenPages') || $this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false);
if (!empty($hiddenField) && !$includeHiddenPages) { if (!empty($hiddenField) && !$includeHiddenPages) {
// Page is "hidden" => 404 (deliberately done in default language, as this cascades to language overlays) // Page is "hidden" => 404 (deliberately done in default language, as this cascades to language overlays)
$rawPageRecord = $this->sys_page->getPage_noCheck($this->id); $rawPageRecord = $this->sys_page->getPage_noCheck($this->id);
...@@ -830,7 +821,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -830,7 +821,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
} }
// Is the ID a link to another page?? // Is the ID a link to another page??
if ($this->page['doktype'] == PageRepository::DOKTYPE_SHORTCUT) { if ($this->page['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
// We need to clear MP if the page is a shortcut. Reason is if the short cut goes to another page, then we LEAVE the rootline which the MP expects. // We need to clear MP if the page is a shortcut. Reason is if the shortcut goes to another page, then we LEAVE the rootline which the MP expects.
$this->MP = ''; $this->MP = '';
// saving the page so that we can check later - when we know // saving the page so that we can check later - when we know
// about languages - whether we took the correct shortcut or // about languages - whether we took the correct shortcut or
...@@ -967,7 +958,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -967,7 +958,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
if ((int)$this->rootLine[$a]['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION) { if ((int)$this->rootLine[$a]['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION) {
// If there is a backend user logged in, check if they have read access to the page: // If there is a backend user logged in, check if they have read access to the page:
if ($this->isBackendUserLoggedIn()) { if ($this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false)) {
// If there was no page selected, the user apparently did not have read access to the // If there was no page selected, the user apparently did not have read access to the
// current page (not position in rootline) and we set the remove-flag... // current page (not position in rootline) and we set the remove-flag...
if (!$this->getBackendUser()->doesUserHaveAccess($this->page, Permission::PAGE_SHOW)) { if (!$this->getBackendUser()->doesUserHaveAccess($this->page, Permission::PAGE_SHOW)) {
...@@ -1750,7 +1741,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -1750,7 +1741,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
protected function setSysLastChanged() protected function setSysLastChanged()
{ {
// We only update the info if browsing the live workspace // We only update the info if browsing the live workspace
if ($this->page['SYS_LASTCHANGED'] < (int)$this->register['SYS_LASTCHANGED'] && !$this->doWorkspacePreview()) { $isInWorkspace = $this->context->getPropertyFromAspect('workspace', 'isOffline', false);
if ($isInWorkspace) {
return;
}
if ($this->page['SYS_LASTCHANGED'] < (int)$this->register['SYS_LASTCHANGED']) {
$connection = GeneralUtility::makeInstance(ConnectionPool::class) $connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('pages'); ->getConnectionForTable('pages');
$pageId = $this->page['_PAGES_OVERLAY_UID'] ?? $this->id; $pageId = $this->page['_PAGES_OVERLAY_UID'] ?? $this->id;
...@@ -2236,8 +2231,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -2236,8 +2231,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface
{ {
// Getting status whether we can send cache control headers for proxy caching: // Getting status whether we can send cache control headers for proxy caching:
$doCache = $this->isStaticCacheble(); $doCache = $this->isStaticCacheble();
$isBackendUserLoggedIn = $this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false);
$isInWorkspace = $this->context->getPropertyFromAspect('workspace', 'isOffline', false);
// Finally, when backend users are logged in, do not send cache headers at all (Admin Panel might be displayed for instance). // Finally, when backend users are logged in, do not send cache headers at all (Admin Panel might be displayed for instance).
$isClientCachable = $doCache && !$this->isBackendUserLoggedIn() && !$this->doWorkspacePreview(); $isClientCachable = $doCache && !$isBackendUserLoggedIn && !$isInWorkspace;
if ($isClientCachable) { if ($isClientCachable) {
$headers = [ $headers = [
'Expires' => gmdate('D, d M Y H:i:s T', $this->cacheExpires), 'Expires' => gmdate('D, d M Y H:i:s T', $this->cacheExpires),
...@@ -2252,7 +2249,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -2252,7 +2249,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
'Cache-Control' => 'private, no-store', 'Cache-Control' => 'private, no-store',
]; ];
// Now, if a backend user is logged in, tell him in the Admin Panel log what the caching status would have been: // Now, if a backend user is logged in, tell him in the Admin Panel log what the caching status would have been:
if ($this->isBackendUserLoggedIn()) { if ($isBackendUserLoggedIn) {
if ($doCache) { if ($doCache) {
$this->getTimeTracker()->setTSlogMessage('Cache-headers with max-age "' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']) . '" would have been sent'); $this->getTimeTracker()->setTSlogMessage('Cache-headers with max-age "' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']) . '" would have been sent');
} else { } else {
...@@ -2263,7 +2260,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -2263,7 +2260,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
if ($this->isINTincScript()) { if ($this->isINTincScript()) {
$reasonMsg[] = '*_INT object(s) on page.'; $reasonMsg[] = '*_INT object(s) on page.';
} }
if (is_array($this->fe_user->user)) { if ($this->context->getPropertyFromAspect('frontend.user', 'isLoggedIn', false)) {
$reasonMsg[] = 'Frontend user logged in.'; $reasonMsg[] = 'Frontend user logged in.';
} }
$this->getTimeTracker()->setTSlogMessage('Cache-headers would disable proxy caching! Reason(s): "' . implode(' ', $reasonMsg) . '"', LogLevel::NOTICE); $this->getTimeTracker()->setTSlogMessage('Cache-headers would disable proxy caching! Reason(s): "' . implode(' ', $reasonMsg) . '"', LogLevel::NOTICE);
...@@ -2285,7 +2282,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -2285,7 +2282,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
*/ */
public function isStaticCacheble() public function isStaticCacheble()
{ {
return !$this->no_cache && !$this->isINTincScript() && !$this->isUserOrGroupSet(); return !$this->no_cache && !$this->isINTincScript() && !$this->context->getAspect('frontend.user')->isUserOrGroupSet();
} }
/******************************************** /********************************************
...@@ -2383,9 +2380,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -2383,9 +2380,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
* Returns TRUE if workspace preview is enabled * Returns TRUE if workspace preview is enabled
* *
* @return bool Returns TRUE if workspace preview is enabled * @return bool Returns TRUE if workspace preview is enabled
* @deprecated will be removed in TYPO3 v13.0. Use the Context API directly.
*/ */
public function doWorkspacePreview() public function doWorkspacePreview()
{ {
trigger_error('TSFE->doWorkspacePreview() will be removed in TYPO3 v13.0. Use the Context API directly.', E_USER_DEPRECATED);
return $this->context->getPropertyFromAspect('workspace', 'isOffline', false); return $this->context->getPropertyFromAspect('workspace', 'isOffline', false);
} }
...@@ -2393,9 +2392,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface ...@@ -2393,9 +2392,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
* Returns the uid of the current workspace * Returns the uid of the current workspace
* *
* @return int returns workspace integer for which workspace is being preview. 0 if none (= live workspace). * @return int returns workspace integer for which workspace is being preview. 0 if none (= live workspace).
* @deprecated will be removed in TYPO3 v13.0. Use the Context API directly.
*/ */
public function whichWorkspace(): int public function whichWorkspace(): int
{ {
trigger_error('TSFE->whichWorkspace() will be removed in TYPO3 v13.0. Use the Context API directly.', E_USER_DEPRECATED);
return $this->context->getPropertyFromAspect('workspace', 'id', 0); return $this->context->getPropertyFromAspect('workspace', 'id', 0);
} }
......
...@@ -965,9 +965,11 @@ class RequestHandler implements RequestHandlerInterface ...@@ -965,9 +965,11 @@ class RequestHandler implements RequestHandlerInterface
*/ */
protected function displayPreviewInfoMessage(TypoScriptFrontendController $controller) protected function displayPreviewInfoMessage(TypoScriptFrontendController $controller)
{ {
$isInPreviewMode = $controller->getContext()->hasAspect('frontend.preview') $context = $controller->getContext();
&& $controller->getContext()->getPropertyFromAspect('frontend.preview', 'isPreview'); $isInWorkspace = $context->getPropertyFromAspect('workspace', 'isOffline', false);
if (!$isInPreviewMode || $controller->doWorkspacePreview() || ($controller->config['config']['disablePreviewNotification'] ?? false)) { $isInPreviewMode = $context->hasAspect('frontend.preview')
&& $context->getPropertyFromAspect('frontend.preview', 'isPreview');
if (!$isInPreviewMode || $isInWorkspace || ($controller->config['config']['disablePreviewNotification'] ?? false)) {
return; return;
} }
if ($controller->config['config']['message_preview'] ?? '') { if ($controller->config['config']['message_preview'] ?? '') {
......
...@@ -46,9 +46,10 @@ class ContentLengthResponseHeader implements MiddlewareInterface ...@@ -46,9 +46,10 @@ class ContentLengthResponseHeader implements MiddlewareInterface
{ {
$response = $handler->handle($request); $response = $handler->handle($request);
if ($GLOBALS['TSFE'] instanceof TypoScriptFrontendController) { if ($GLOBALS['TSFE'] instanceof TypoScriptFrontendController) {
$context = $GLOBALS['TSFE']->getContext();
if ( if (
(!isset($GLOBALS['TSFE']->config['config']['enableContentLengthHeader']) || $GLOBALS['TSFE']->config['config']['enableContentLengthHeader']) (!isset($GLOBALS['TSFE']->config['config']['enableContentLengthHeader']) || $GLOBALS['TSFE']->config['config']['enableContentLengthHeader'])
&& !$GLOBALS['TSFE']->isBackendUserLoggedIn() && !$GLOBALS['TSFE']->doWorkspacePreview() && !$context->getPropertyFromAspect('backend.user', 'isLoggedIn', false) && !$context->getPropertyFromAspect('workspace', 'isOffline', false)
) { ) {
$response = $response->withHeader('Content-Length', (string)$response->getBody()->getSize()); $response = $response->withHeader('Content-Length', (string)$response->getBody()->getSize());
} }
......
...@@ -79,8 +79,7 @@ class FrontendUserAuthenticator implements MiddlewareInterface, LoggerAwareInter ...@@ -79,8 +79,7 @@ class FrontendUserAuthenticator implements MiddlewareInterface, LoggerAwareInter
$frontendUser->fetchGroupData($request); $frontendUser->fetchGroupData($request);
// Register the frontend user as aspect and within the request // Register the frontend user as aspect and within the request
$userAspect = $frontendUser->createUserAspect(); $this->context->setAspect('frontend.user', $frontendUser->createUserAspect());
$this->context->setAspect('frontend.user', $userAspect);
$request = $request->withAttribute('frontend.user', $frontendUser); $request = $request->withAttribute('frontend.user', $frontendUser);
if ($this->context->getAspect('frontend.user')->isLoggedIn() && $rateLimiter) { if ($this->context->getAspect('frontend.user')->isLoggedIn() && $rateLimiter) {
......
...@@ -5288,4 +5288,39 @@ return [ ...@@ -5288,4 +5288,39 @@ return [
'Deprecation-96641-LinkRelatedFunctionalityInContentObjectRenderer.rst', 'Deprecation-96641-LinkRelatedFunctionalityInContentObjectRenderer.rst',
], ],
], ],
'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->initUserGroups' => [
'numberOfMandatoryArguments' => 0,
'maximumNumberOfArguments' => 0,
'restFiles' => [
'Deprecation-97531-ContextRelatedMethodsWithinTSFE.rst',
],
],
'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->isUserOrGroupSet' => [
'numberOfMandatoryArguments' => 0,
'maximumNumberOfArguments' => 0,
'restFiles' => [
'Deprecation-97531-ContextRelatedMethodsWithinTSFE.rst',
],
],
'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->isBackendUserLoggedIn' => [
'numberOfMandatoryArguments' => 0,
'maximumNumberOfArguments' => 0,
'restFiles' => [
'Deprecation-97531-ContextRelatedMethodsWithinTSFE.rst',
],
],
'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->doWorkspacePreview' => [
'numberOfMandatoryArguments' => 0,
'maximumNumberOfArguments' => 0,
'restFiles' => [
'Deprecation-97531-ContextRelatedMethodsWithinTSFE.rst',
],
],
'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->whichWorkspace' => [
'numberOfMandatoryArguments' => 0,
'maximumNumberOfArguments' => 0,
'restFiles' => [
'Deprecation-97531-ContextRelatedMethodsWithinTSFE.rst',
],
],
]; ];
...@@ -321,7 +321,7 @@ class WorkspacePreview implements MiddlewareInterface ...@@ -321,7 +321,7 @@ class WorkspacePreview implements MiddlewareInterface
$content = ''; $content = '';
if (!isset($tsfe->config['config']['disablePreviewNotification']) || (int)$tsfe->config['config']['disablePreviewNotification'] !== 1) { if (!isset($tsfe->config['config']['disablePreviewNotification']) || (int)$tsfe->config['config']['disablePreviewNotification'] !== 1) {
// get the title of the current workspace // get the title of the current workspace
$currentWorkspaceId = $tsfe->whichWorkspace(); $currentWorkspaceId = $tsfe->getContext()->getPropertyFromAspect('workspace', 'id', 0);
$currentWorkspaceTitle = $this->getWorkspaceTitle($currentWorkspaceId); $currentWorkspaceTitle = $this->getWorkspaceTitle($currentWorkspaceId);
$currentWorkspaceTitle = htmlspecialchars($currentWorkspaceTitle); $currentWorkspaceTitle = htmlspecialchars($currentWorkspaceTitle);
if ($tsfe->config['config']['message_preview_workspace'] ?? false) { if ($tsfe->config['config']['message_preview_workspace'] ?? false) {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment