first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Aspect;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Page\PageRepository;
|
||||
|
||||
/**
|
||||
* Class FileMetadataTranslationAspect
|
||||
*
|
||||
* We do not have AOP in TYPO3 for now, thus the aspect which
|
||||
* deals with metadata translation is a slot which reacts on a signal
|
||||
* in the Index\MetadataRepository.
|
||||
*
|
||||
* The aspect injects user permissions and mount points into the storage
|
||||
* based on user or group configuration.
|
||||
*
|
||||
* @internal this is a concrete TYPO3 hook implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class FileMetadataOverlayAspect
|
||||
{
|
||||
/**
|
||||
* Do translation and workspace overlay
|
||||
*
|
||||
* @param \ArrayObject $data
|
||||
*/
|
||||
public function languageAndWorkspaceOverlay(\ArrayObject $data)
|
||||
{
|
||||
// Should only be in Frontend, but not in eID context
|
||||
if (!(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_FE) || isset($_REQUEST['eID'])) {
|
||||
return;
|
||||
}
|
||||
$overlaidMetaData = $data->getArrayCopy();
|
||||
$pageRepository = GeneralUtility::makeInstance(PageRepository::class);
|
||||
$pageRepository->versionOL('sys_file_metadata', $overlaidMetaData);
|
||||
$overlaidMetaData = $pageRepository->getLanguageOverlay(
|
||||
'sys_file_metadata',
|
||||
$overlaidMetaData
|
||||
);
|
||||
if ($overlaidMetaData !== null) {
|
||||
$data->exchangeArray($overlaidMetaData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Authentication;
|
||||
|
||||
/*
|
||||
* 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\AbstractUserAuthentication;
|
||||
use TYPO3\CMS\Core\Configuration\Features;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
|
||||
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Extension class for Front End User Authentication.
|
||||
*/
|
||||
class FrontendUserAuthentication extends AbstractUserAuthentication
|
||||
{
|
||||
/**
|
||||
* form field with 0 or 1
|
||||
* 1 = permanent login enabled
|
||||
* 0 = session is valid for a browser session only
|
||||
* @var string
|
||||
*/
|
||||
public $formfield_permanent = 'permalogin';
|
||||
|
||||
/**
|
||||
* Lifetime of anonymous session data in seconds.
|
||||
* @var int
|
||||
*/
|
||||
protected $sessionDataLifetime = 86400;
|
||||
|
||||
/**
|
||||
* Session timeout (on the server)
|
||||
*
|
||||
* If >0: session-timeout in seconds.
|
||||
* If <=0: Instant logout after login.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $sessionTimeout = 6000;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $usergroup_column = 'usergroup';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $usergroup_table = 'fe_groups';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $groupData = [
|
||||
'title' => [],
|
||||
'uid' => [],
|
||||
'pid' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Used to accumulate the TSconfig data of the user
|
||||
* @var array
|
||||
*/
|
||||
public $TSdataArray = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $userTS = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $userTSUpdated = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $sesData_change = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $userData_change = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $is_permanent = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $loginHidden = false;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Disable cookie by default, will be activated if saveSessionData() is called,
|
||||
// a user is logging-in or an existing session is found
|
||||
$this->dontSetCookie = true;
|
||||
|
||||
$this->name = self::getCookieName();
|
||||
$this->get_name = 'ftu';
|
||||
$this->loginType = 'FE';
|
||||
$this->user_table = 'fe_users';
|
||||
$this->username_column = 'username';
|
||||
$this->userident_column = 'password';
|
||||
$this->userid_column = 'uid';
|
||||
$this->lastLogin_column = 'lastlogin';
|
||||
$this->enablecolumns = [
|
||||
'deleted' => 'deleted',
|
||||
'disabled' => 'disable',
|
||||
'starttime' => 'starttime',
|
||||
'endtime' => 'endtime'
|
||||
];
|
||||
$this->formfield_uname = 'user';
|
||||
$this->formfield_uident = 'pass';
|
||||
$this->formfield_status = 'logintype';
|
||||
$this->sendNoCacheHeaders = false;
|
||||
$this->getFallBack = true;
|
||||
$this->getMethodEnabled = true;
|
||||
$this->lockIP = $GLOBALS['TYPO3_CONF_VARS']['FE']['lockIP'];
|
||||
$this->checkPid = $GLOBALS['TYPO3_CONF_VARS']['FE']['checkFeUserPid'];
|
||||
$this->lifetime = (int)$GLOBALS['TYPO3_CONF_VARS']['FE']['lifetime'];
|
||||
$this->sessionTimeout = (int)$GLOBALS['TYPO3_CONF_VARS']['FE']['sessionTimeout'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured cookie name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCookieName()
|
||||
{
|
||||
$configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['FE']['cookieName']);
|
||||
if (empty($configuredCookieName)) {
|
||||
$configuredCookieName = 'fe_typo_user';
|
||||
}
|
||||
return $configuredCookieName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a user session
|
||||
*
|
||||
* @see AbstractUserAuthentication::start()
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
if ($this->sessionTimeout > 0 && $this->sessionTimeout < $this->lifetime) {
|
||||
// If server session timeout is non-zero but less than client session timeout: Copy this value instead.
|
||||
$this->sessionTimeout = $this->lifetime;
|
||||
}
|
||||
$this->sessionDataLifetime = (int)$GLOBALS['TYPO3_CONF_VARS']['FE']['sessionDataLifetime'];
|
||||
if ($this->sessionDataLifetime <= 0) {
|
||||
$this->sessionDataLifetime = 86400;
|
||||
}
|
||||
parent::start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new session record for the current user for insertion into the DB.
|
||||
*
|
||||
* @param array $tempuser
|
||||
* @return array User session record
|
||||
*/
|
||||
public function getNewSessionRecord($tempuser)
|
||||
{
|
||||
$insertFields = parent::getNewSessionRecord($tempuser);
|
||||
$insertFields['ses_permanent'] = $this->is_permanent ? 1 : 0;
|
||||
return $insertFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a session cookie needs to be set (lifetime=0)
|
||||
*
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isSetSessionCookie()
|
||||
{
|
||||
return ($this->newSessionID || $this->forceSetCookie)
|
||||
&& ((int)$this->lifetime === 0 || !isset($this->user['ses_permanent']) || !$this->user['ses_permanent']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a non-session cookie needs to be set (lifetime>0)
|
||||
*
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isRefreshTimeBasedCookie()
|
||||
{
|
||||
return $this->lifetime > 0 && isset($this->user['ses_permanent']) && $this->user['ses_permanent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an info array with Login/Logout data submitted by a form or params
|
||||
*
|
||||
* @return array
|
||||
* @see AbstractUserAuthentication::getLoginFormData()
|
||||
*/
|
||||
public function getLoginFormData()
|
||||
{
|
||||
$loginData = parent::getLoginFormData();
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 0 || $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 1) {
|
||||
if ($this->getMethodEnabled) {
|
||||
$isPermanent = GeneralUtility::_GP($this->formfield_permanent);
|
||||
} else {
|
||||
$isPermanent = GeneralUtility::_POST($this->formfield_permanent);
|
||||
}
|
||||
if (strlen($isPermanent) != 1) {
|
||||
$isPermanent = $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'];
|
||||
} elseif (!$isPermanent) {
|
||||
// To make sure the user gets a session cookie and doesn't keep a possibly existing time based cookie,
|
||||
// we need to force setting the session cookie here
|
||||
$this->forceSetCookie = true;
|
||||
}
|
||||
$isPermanent = (bool)$isPermanent;
|
||||
} elseif ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 2) {
|
||||
$isPermanent = true;
|
||||
} else {
|
||||
$isPermanent = false;
|
||||
}
|
||||
$loginData['permanent'] = $isPermanent;
|
||||
$this->is_permanent = $isPermanent;
|
||||
return $loginData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a user session record and returns its values.
|
||||
* However, as the FE user cookie is normally not set, this has to be done
|
||||
* before the parent class is doing the rest.
|
||||
*
|
||||
* @param array $tempuser User data array
|
||||
* @return array The session data for the newly created session.
|
||||
*/
|
||||
public function createUserSession($tempuser)
|
||||
{
|
||||
// At this point we do not know if we need to set a session or a permanent cookie
|
||||
// So we force the cookie to be set after authentication took place, which will
|
||||
// then call setSessionCookie(), which will set a cookie with correct settings.
|
||||
$this->dontSetCookie = false;
|
||||
return parent::createUserSession($tempuser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will select all fe_groups records that the current fe_user is member of
|
||||
* and which groups are also allowed in the current domain.
|
||||
* It also accumulates the TSconfig for the fe_user/fe_groups in ->TSdataArray
|
||||
*
|
||||
* @return int Returns the number of usergroups for the frontend users (if the internal user record exists and the usergroup field contains a value)
|
||||
*/
|
||||
public function fetchGroupData()
|
||||
{
|
||||
$this->TSdataArray = [];
|
||||
$this->userTS = [];
|
||||
$this->userTSUpdated = false;
|
||||
$this->groupData = [
|
||||
'title' => [],
|
||||
'uid' => [],
|
||||
'pid' => []
|
||||
];
|
||||
// Setting default configuration:
|
||||
$this->TSdataArray[] = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultUserTSconfig'];
|
||||
// Get the info data for auth services
|
||||
$authInfo = $this->getAuthInfoArray();
|
||||
if (is_array($this->user)) {
|
||||
$this->logger->debug('Get usergroups for user', [
|
||||
$this->userid_column => $this->user[$this->userid_column],
|
||||
$this->username_column => $this->user[$this->username_column]
|
||||
]);
|
||||
} else {
|
||||
$this->logger->debug('Get usergroups for "anonymous" user');
|
||||
}
|
||||
$groupDataArr = [];
|
||||
// Use 'auth' service to find the groups for the user
|
||||
$serviceChain = '';
|
||||
$subType = 'getGroups' . $this->loginType;
|
||||
while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
|
||||
$serviceChain .= ',' . $serviceObj->getServiceKey();
|
||||
$serviceObj->initAuth($subType, [], $authInfo, $this);
|
||||
$groupData = $serviceObj->getGroups($this->user, $groupDataArr);
|
||||
if (is_array($groupData) && !empty($groupData)) {
|
||||
// Keys in $groupData should be unique ids of the groups (like "uid") so this function will override groups.
|
||||
$groupDataArr = $groupData + $groupDataArr;
|
||||
}
|
||||
unset($serviceObj);
|
||||
}
|
||||
if ($serviceChain) {
|
||||
$this->logger->debug($subType . ' auth services called: ' . $serviceChain);
|
||||
}
|
||||
if (empty($groupDataArr)) {
|
||||
$this->logger->debug('No usergroups found by services');
|
||||
}
|
||||
if (!empty($groupDataArr)) {
|
||||
$this->logger->debug(count($groupDataArr) . ' usergroup records found by services');
|
||||
}
|
||||
// Use 'auth' service to check the usergroups if they are really valid
|
||||
foreach ($groupDataArr as $groupData) {
|
||||
// By default a group is valid
|
||||
$validGroup = true;
|
||||
$serviceChain = '';
|
||||
$subType = 'authGroups' . $this->loginType;
|
||||
while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
|
||||
$serviceChain .= ',' . $serviceObj->getServiceKey();
|
||||
$serviceObj->initAuth($subType, [], $authInfo, $this);
|
||||
if (!$serviceObj->authGroup($this->user, $groupData)) {
|
||||
$validGroup = false;
|
||||
$this->logger->debug($subType . ' auth service did not auth group', [
|
||||
'uid ' => $groupData['uid'],
|
||||
'title' => $groupData['title']
|
||||
]);
|
||||
break;
|
||||
}
|
||||
unset($serviceObj);
|
||||
}
|
||||
unset($serviceObj);
|
||||
if ($validGroup && (string)$groupData['uid'] !== '') {
|
||||
$this->groupData['title'][$groupData['uid']] = $groupData['title'];
|
||||
$this->groupData['uid'][$groupData['uid']] = $groupData['uid'];
|
||||
$this->groupData['pid'][$groupData['uid']] = $groupData['pid'];
|
||||
$this->groupData['TSconfig'][$groupData['uid']] = $groupData['TSconfig'];
|
||||
}
|
||||
}
|
||||
if (!empty($this->groupData) && !empty($this->groupData['TSconfig'])) {
|
||||
// TSconfig: collect it in the order it was collected
|
||||
foreach ($this->groupData['TSconfig'] as $TSdata) {
|
||||
$this->TSdataArray[] = $TSdata;
|
||||
}
|
||||
$this->TSdataArray[] = $this->user['TSconfig'];
|
||||
// Sort information
|
||||
ksort($this->groupData['title']);
|
||||
ksort($this->groupData['uid']);
|
||||
ksort($this->groupData['pid']);
|
||||
}
|
||||
return !empty($this->groupData['uid']) ? count($this->groupData['uid']) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parsed TSconfig for the fe_user
|
||||
* The TSconfig will be cached in $this->userTS.
|
||||
*
|
||||
* @return array TSconfig array for the fe_user
|
||||
*/
|
||||
public function getUserTSconf()
|
||||
{
|
||||
if (!$this->userTSUpdated) {
|
||||
// Parsing the user TS (or getting from cache)
|
||||
$this->TSdataArray = TypoScriptParser::checkIncludeLines_array($this->TSdataArray);
|
||||
$userTS = implode(LF . '[GLOBAL]' . LF, $this->TSdataArray);
|
||||
$parseObj = GeneralUtility::makeInstance(TypoScriptParser::class);
|
||||
$parseObj->parse($userTS);
|
||||
$this->userTS = $parseObj->setup;
|
||||
$this->userTSUpdated = true;
|
||||
}
|
||||
return $this->userTS;
|
||||
}
|
||||
|
||||
/*****************************************
|
||||
*
|
||||
* Session data management functions
|
||||
*
|
||||
****************************************/
|
||||
/**
|
||||
* Will write UC and session data.
|
||||
* If the flag $this->userData_change has been set, the function ->writeUC is called (which will save persistent user session data)
|
||||
* If the flag $this->sesData_change has been set, the current session record is updated with the content of $this->sessionData
|
||||
*
|
||||
* @see getKey(), setKey()
|
||||
*/
|
||||
public function storeSessionData()
|
||||
{
|
||||
// Saves UC and SesData if changed.
|
||||
if ($this->userData_change) {
|
||||
$this->writeUC();
|
||||
}
|
||||
|
||||
if ($this->sesData_change && $this->id) {
|
||||
if (empty($this->sessionData)) {
|
||||
// Remove session-data
|
||||
$this->removeSessionData();
|
||||
// Remove cookie if not logged in as the session data is removed as well
|
||||
if (empty($this->user['uid']) && !$this->loginHidden && $this->isCookieSet()) {
|
||||
$this->removeCookie($this->name);
|
||||
}
|
||||
} elseif (!$this->isExistingSessionRecord($this->id)) {
|
||||
$sessionRecord = $this->getNewSessionRecord([]);
|
||||
$sessionRecord['ses_anonymous'] = 1;
|
||||
$sessionRecord['ses_data'] = serialize($this->sessionData);
|
||||
$updatedSession = $this->getSessionBackend()->set($this->id, $sessionRecord);
|
||||
$this->user = array_merge($this->user ?? [], $updatedSession);
|
||||
// Now set the cookie (= fix the session)
|
||||
$this->setSessionCookie();
|
||||
} else {
|
||||
// Update session data
|
||||
$insertFields = [
|
||||
'ses_data' => serialize($this->sessionData)
|
||||
];
|
||||
$updatedSession = $this->getSessionBackend()->update($this->id, $insertFields);
|
||||
$this->user = array_merge($this->user ?? [], $updatedSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes data of the current session.
|
||||
*/
|
||||
public function removeSessionData()
|
||||
{
|
||||
if (!empty($this->sessionData)) {
|
||||
$this->sesData_change = true;
|
||||
}
|
||||
$this->sessionData = [];
|
||||
|
||||
if ($this->isExistingSessionRecord($this->id)) {
|
||||
// Remove session record if $this->user is empty is if session is anonymous
|
||||
if ((empty($this->user) && !$this->loginHidden) || $this->user['ses_anonymous']) {
|
||||
$this->getSessionBackend()->remove($this->id);
|
||||
} else {
|
||||
$this->getSessionBackend()->update($this->id, [
|
||||
'ses_data' => ''
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current session record, sets the internal ->user array to null,
|
||||
* Thereby the current user (if any) is effectively logged out!
|
||||
* Additionally the cookie is removed, but only if there is no session data.
|
||||
* If session data exists, only the user information is removed and the session
|
||||
* gets converted into an anonymous session if the feature toggle
|
||||
* "security.frontend.keepSessionDataOnLogout" is set to true (default: false).
|
||||
*/
|
||||
protected function performLogoff()
|
||||
{
|
||||
$sessionData = [];
|
||||
try {
|
||||
// Session might not be loaded at this point, so fetch it
|
||||
$oldSession = $this->getSessionBackend()->get($this->id);
|
||||
$sessionData = unserialize($oldSession['ses_data']);
|
||||
} catch (SessionNotFoundException $e) {
|
||||
// Leave uncaught, will unset cookie later in this method
|
||||
}
|
||||
|
||||
$keepSessionDataOnLogout = GeneralUtility::makeInstance(Features::class)
|
||||
->isFeatureEnabled('security.frontend.keepSessionDataOnLogout');
|
||||
|
||||
if ($keepSessionDataOnLogout && !empty($sessionData)) {
|
||||
// Regenerate session as anonymous
|
||||
$this->regenerateSessionId($oldSession, true);
|
||||
$this->user = null;
|
||||
} else {
|
||||
parent::performLogoff();
|
||||
if ($this->isCookieSet()) {
|
||||
$this->removeCookie($this->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate the session ID and transfer the session to new ID
|
||||
* Call this method whenever a user proceeds to a higher authorization level
|
||||
* e.g. when an anonymous session is now authenticated.
|
||||
* Forces cookie to be set
|
||||
*
|
||||
* @param array $existingSessionRecord If given, this session record will be used instead of fetching again'
|
||||
* @param bool $anonymous If true session will be regenerated as anonymous session
|
||||
*/
|
||||
protected function regenerateSessionId(array $existingSessionRecord = [], bool $anonymous = false)
|
||||
{
|
||||
if (empty($existingSessionRecord)) {
|
||||
$existingSessionRecord = $this->getSessionBackend()->get($this->id);
|
||||
}
|
||||
$existingSessionRecord['ses_anonymous'] = (int)$anonymous;
|
||||
if ($anonymous) {
|
||||
$existingSessionRecord['ses_userid'] = 0;
|
||||
}
|
||||
parent::regenerateSessionId($existingSessionRecord, $anonymous);
|
||||
// We force the cookie to be set later in the authentication process
|
||||
$this->dontSetCookie = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns session data for the fe_user; Either persistent data following the fe_users uid/profile (requires login)
|
||||
* or current-session based (not available when browse is closed, but does not require login)
|
||||
*
|
||||
* @param string $type Session data type; Either "user" (persistent, bound to fe_users profile) or "ses" (temporary, bound to current session cookie)
|
||||
* @param string $key Key from the data array to return; The session data (in either case) is an array ($this->uc / $this->sessionData) and this value determines which key to return the value for.
|
||||
* @return mixed Returns whatever value there was in the array for the key, $key
|
||||
* @see setKey()
|
||||
*/
|
||||
public function getKey($type, $key)
|
||||
{
|
||||
if (!$key) {
|
||||
return null;
|
||||
}
|
||||
$value = null;
|
||||
switch ($type) {
|
||||
case 'user':
|
||||
$value = $this->uc[$key];
|
||||
break;
|
||||
case 'ses':
|
||||
$value = $this->getSessionData($key);
|
||||
break;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves session data, either persistent or bound to current session cookie. Please see getKey() for more details.
|
||||
* When a value is set the flags $this->userData_change or $this->sesData_change will be set so that the final call to ->storeSessionData() will know if a change has occurred and needs to be saved to the database.
|
||||
* Notice: Simply calling this function will not save the data to the database! The actual saving is done in storeSessionData() which is called as some of the last things in \TYPO3\CMS\Frontend\Http\RequestHandler. So if you exit before this point, nothing gets saved of course! And the solution is to call $GLOBALS['TSFE']->storeSessionData(); before you exit.
|
||||
*
|
||||
* @param string $type Session data type; Either "user" (persistent, bound to fe_users profile) or "ses" (temporary, bound to current session cookie)
|
||||
* @param string $key Key from the data array to store incoming data in; The session data (in either case) is an array ($this->uc / $this->sessionData) and this value determines in which key the $data value will be stored.
|
||||
* @param mixed $data The data value to store in $key
|
||||
* @see setKey(), storeSessionData()
|
||||
*/
|
||||
public function setKey($type, $key, $data)
|
||||
{
|
||||
if (!$key) {
|
||||
return;
|
||||
}
|
||||
switch ($type) {
|
||||
case 'user':
|
||||
if ($this->user['uid']) {
|
||||
if ($data === null) {
|
||||
unset($this->uc[$key]);
|
||||
} else {
|
||||
$this->uc[$key] = $data;
|
||||
}
|
||||
$this->userData_change = true;
|
||||
}
|
||||
break;
|
||||
case 'ses':
|
||||
$this->setSessionData($key, $data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set session data by key.
|
||||
* The data will last only for this login session since it is stored in the user session.
|
||||
*
|
||||
* @param string $key A non empty string to store the data under
|
||||
* @param mixed $data Data store store in session
|
||||
*/
|
||||
public function setSessionData($key, $data)
|
||||
{
|
||||
$this->sesData_change = true;
|
||||
if ($data === null) {
|
||||
unset($this->sessionData[$key]);
|
||||
return;
|
||||
}
|
||||
parent::setSessionData($key, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the tokens so that they can be used by a later incarnation of this class.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $data
|
||||
*/
|
||||
public function setAndSaveSessionData($key, $data)
|
||||
{
|
||||
$this->setSessionData($key, $data);
|
||||
$this->storeSessionData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collector, removing old expired sessions.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function gc()
|
||||
{
|
||||
$this->getSessionBackend()->collectGarbage($this->gc_time, $this->sessionDataLifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the current login
|
||||
*
|
||||
* This is used by the fe_login_mode feature for pages.
|
||||
* A current login is unset, but we remember that there has been one.
|
||||
*/
|
||||
public function hideActiveLogin()
|
||||
{
|
||||
$this->user = null;
|
||||
$this->loginHidden = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the field "is_online" every 60 seconds of a logged-in user
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function updateOnlineTimestamp()
|
||||
{
|
||||
if (!is_array($this->user) || !$this->user['uid']
|
||||
|| $this->user['is_online'] >= $GLOBALS['EXEC_TIME'] - 60) {
|
||||
return;
|
||||
}
|
||||
$dbConnection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('fe_users');
|
||||
$dbConnection->update(
|
||||
'fe_users',
|
||||
['is_online' => $GLOBALS['EXEC_TIME']],
|
||||
['uid' => (int)$this->user['uid']]
|
||||
);
|
||||
$this->user['is_online'] = $GLOBALS['EXEC_TIME'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Category\Collection;
|
||||
|
||||
/*
|
||||
* 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\Context\Context;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Page\PageRepository;
|
||||
|
||||
/**
|
||||
* Extend category collection for the frontend, to collect related records
|
||||
* while respecting language, enable fields, etc.
|
||||
*
|
||||
* @internal this is a concrete TYPO3 hook implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class CategoryCollection extends \TYPO3\CMS\Core\Category\Collection\CategoryCollection
|
||||
{
|
||||
/**
|
||||
* Creates a new collection objects and reconstitutes the
|
||||
* given database record to the new object.
|
||||
*
|
||||
* Overrides the parent method to create a *frontend* category collection.
|
||||
*
|
||||
* @param array $collectionRecord Database record
|
||||
* @param bool $fillItems Populates the entries directly on load, might be bad for memory on large collections
|
||||
* @return \TYPO3\CMS\Frontend\Category\Collection\CategoryCollection
|
||||
*/
|
||||
public static function create(array $collectionRecord, $fillItems = false)
|
||||
{
|
||||
/** @var \TYPO3\CMS\Frontend\Category\Collection\CategoryCollection $collection */
|
||||
$collection = GeneralUtility::makeInstance(
|
||||
__CLASS__,
|
||||
$collectionRecord['table_name'],
|
||||
$collectionRecord['field_name']
|
||||
);
|
||||
$collection->fromArray($collectionRecord);
|
||||
if ($fillItems) {
|
||||
$collection->loadContents();
|
||||
}
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the collection with the given id from persistence
|
||||
* For memory reasons, only data for the collection itself is loaded by default.
|
||||
* Entries can be loaded on first access or straightaway using the $fillItems flag.
|
||||
*
|
||||
* Overrides the parent method because of the call to "self::create()" which otherwise calls up
|
||||
* \TYPO3\CMS\Core\Category\Collection\CategoryCollection
|
||||
*
|
||||
* @param int $id Id of database record to be loaded
|
||||
* @param bool $fillItems Populates the entries directly on load, might be bad for memory on large collections
|
||||
* @param string $tableName the table name
|
||||
* @param string $fieldName Name of the categories relation field
|
||||
* @return \TYPO3\CMS\Core\Collection\CollectionInterface
|
||||
*/
|
||||
public static function load($id, $fillItems = false, $tableName = '', $fieldName = '')
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable(static::$storageTableName);
|
||||
$queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
|
||||
|
||||
$collectionRecord = $queryBuilder
|
||||
->select('*')
|
||||
->from(static::$storageTableName)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->execute()
|
||||
->fetch();
|
||||
|
||||
$collectionRecord['table_name'] = $tableName;
|
||||
$collectionRecord['field_name'] = $fieldName;
|
||||
|
||||
return self::create($collectionRecord, $fillItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the collected records in this collection, by
|
||||
* looking up the MM relations of this record to the
|
||||
* table name defined in the local field 'table_name'.
|
||||
*
|
||||
* Overrides its parent method to implement usage of language,
|
||||
* enable fields, etc. Also performs overlays.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getCollectedRecords()
|
||||
{
|
||||
$relatedRecords = [];
|
||||
|
||||
$queryBuilder = $this->getCollectedRecordsQueryBuilder();
|
||||
$queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
|
||||
$context = GeneralUtility::makeInstance(Context::class);
|
||||
$pageRepository = GeneralUtility::makeInstance(PageRepository::class, $context);
|
||||
$languageId = $context->getPropertyFromAspect('language', 'contentId', 0);
|
||||
|
||||
// If language handling is defined for item table, add language condition
|
||||
if (isset($GLOBALS['TCA'][$this->getItemTableName()]['ctrl']['languageField'])) {
|
||||
// Consider default or "all" language
|
||||
$languageField = sprintf(
|
||||
'%s.%s',
|
||||
$this->getItemTableName(),
|
||||
$GLOBALS['TCA'][$this->getItemTableName()]['ctrl']['languageField']
|
||||
);
|
||||
|
||||
$languageConstraint = $queryBuilder->expr()->in(
|
||||
$languageField,
|
||||
$queryBuilder->createNamedParameter([0, -1], Connection::PARAM_INT_ARRAY)
|
||||
);
|
||||
|
||||
// If not in default language, also consider items in current language with no original
|
||||
if ($languageId > 0) {
|
||||
$transOrigPointerField = sprintf(
|
||||
'%s.%s',
|
||||
$this->getItemTableName(),
|
||||
$GLOBALS['TCA'][$this->getItemTableName()]['ctrl']['transOrigPointerField']
|
||||
);
|
||||
|
||||
$languageConstraint = $queryBuilder->expr()->orX(
|
||||
$languageConstraint,
|
||||
$queryBuilder->expr()->andX(
|
||||
$queryBuilder->expr()->eq(
|
||||
$languageField,
|
||||
$queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
$transOrigPointerField,
|
||||
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$queryBuilder->andWhere($languageConstraint);
|
||||
}
|
||||
|
||||
// Get the related records from the database
|
||||
$result = $queryBuilder->execute();
|
||||
|
||||
while ($record = $result->fetch()) {
|
||||
// Overlay the record for workspaces
|
||||
$pageRepository->versionOL(
|
||||
$this->getItemTableName(),
|
||||
$record
|
||||
);
|
||||
|
||||
// Overlay the record for translations
|
||||
if (is_array($record)) {
|
||||
$record = $pageRepository->getLanguageOverlay($this->getItemTableName(), $record);
|
||||
}
|
||||
|
||||
// Record may have been unset during the overlay process
|
||||
if (is_array($record)) {
|
||||
$relatedRecords[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
return $relatedRecords;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Frontend\Compatibility;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Cache\CacheManager;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Exception\Page\RootLineException;
|
||||
use TYPO3\CMS\Core\Http\NormalizedParams;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\RootlineUtility;
|
||||
|
||||
/**
|
||||
* Resolves sys_domain entries when a Request object is given,
|
||||
* or a pageId is given or a rootpage Id is given (= if there is a sys_domain record on that specific page).
|
||||
* Always keeps the sorting in line.
|
||||
*
|
||||
* @internal this functionality is for compatibility reasons and might be removed in TYPO3 v10.0.
|
||||
*/
|
||||
class LegacyDomainResolver implements SingletonInterface
|
||||
{
|
||||
/**
|
||||
* Runtime cache of domains per processed page ids.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $domainDataCache = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheIdentifier = 'legacy-domains';
|
||||
|
||||
/**
|
||||
* @var FrontendInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* all entries in sys_domain grouped by page (pid)
|
||||
* @var array
|
||||
*/
|
||||
protected $groupedDomainsPerPage;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core');
|
||||
$this->populate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds up all domain records from DB and all routes
|
||||
*/
|
||||
protected function populate()
|
||||
{
|
||||
if ($data = $this->cache->get($this->cacheIdentifier)) {
|
||||
// Due to the nature of PhpFrontend, the `<?php` and `#` wraps have to be removed
|
||||
$data = preg_replace('/^<\?php\s*|\s*#$/', '', $data);
|
||||
$this->groupedDomainsPerPage = json_decode($data, true);
|
||||
} else {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_domain');
|
||||
$statement = $queryBuilder
|
||||
->select('*')
|
||||
->from('sys_domain')
|
||||
->orderBy('sorting', 'ASC')
|
||||
->execute();
|
||||
|
||||
while ($row = $statement->fetch()) {
|
||||
$row['domainName'] = rtrim($row['domainName'], '/');
|
||||
$this->groupedDomainsPerPage[(int)$row['pid']][] = $row;
|
||||
}
|
||||
|
||||
$this->cache->set($this->cacheIdentifier, json_encode($this->groupedDomainsPerPage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getGroupedDomainsPerPage(): array
|
||||
{
|
||||
return $this->groupedDomainsPerPage ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a sys_domain record that fits for a given page ID by traversing the rootline up and finding
|
||||
* a suitable page with sys_domain records.
|
||||
* As all sys_domains have been fetched already, the internal grouped list of sys_domains can be used directly.
|
||||
*
|
||||
* Usually used in the Frontend to find out the domain of a page to link to.
|
||||
*
|
||||
* Includes a runtime cache if a frontend request links to the same page multiple times.
|
||||
*
|
||||
* @param int $pageId Target page id
|
||||
* @param ServerRequestInterface|null $currentRequest if given, the domain record is marked with "isCurrentDomain"
|
||||
* @return array|null the sys_domain record if found
|
||||
*/
|
||||
public function matchPageId(int $pageId, ServerRequestInterface $currentRequest = null): ?array
|
||||
{
|
||||
// Using array_key_exists() here, nice $result can be NULL
|
||||
// (happens, if there's no domain records defined)
|
||||
if (array_key_exists($pageId, $this->domainDataCache)) {
|
||||
return $this->domainDataCache[$pageId];
|
||||
}
|
||||
try {
|
||||
$this->domainDataCache[$pageId] = $this->resolveDomainEntry(
|
||||
$pageId,
|
||||
$currentRequest
|
||||
);
|
||||
} catch (RootLineException $e) {
|
||||
$this->domainDataCache[$pageId] = null;
|
||||
}
|
||||
return $this->domainDataCache[$pageId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full sys_domain record, based on a page record, which is assumed the "pid" of the sys_domain record.
|
||||
* Since ordering is taken into account, this is the first sys_domain record on that page Id.
|
||||
*
|
||||
* @param int $pageId
|
||||
* @return array|null
|
||||
*/
|
||||
public function matchRootPageId(int $pageId): ?array
|
||||
{
|
||||
return !empty($this->groupedDomainsPerPage[$pageId]) ? reset($this->groupedDomainsPerPage[$pageId]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $pageId
|
||||
* @param ServerRequestInterface|null $currentRequest
|
||||
* @return array|null
|
||||
*/
|
||||
protected function resolveDomainEntry(int $pageId, ?ServerRequestInterface $currentRequest): ?array
|
||||
{
|
||||
$rootLine = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get();
|
||||
// walk the rootline downwards from the target page
|
||||
// to the root page, until a domain record is found
|
||||
foreach ($rootLine as $pageInRootline) {
|
||||
$pidInRootline = (int)$pageInRootline['uid'];
|
||||
if (empty($this->groupedDomainsPerPage[$pidInRootline])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$domainEntriesOfPage = $this->groupedDomainsPerPage[$pidInRootline];
|
||||
foreach ($domainEntriesOfPage as $domainEntry) {
|
||||
if ($domainEntry['hidden']) {
|
||||
continue;
|
||||
}
|
||||
// When no currentRequest is given, let's take the first non-hidden sys_domain page
|
||||
if ($currentRequest === null) {
|
||||
return $domainEntry;
|
||||
}
|
||||
// Otherwise the check should match against the current domain (and set "isCurrentDomain")
|
||||
// Current domain is "forced", however, otherwise the first one is fine
|
||||
if ($this->domainNameMatchesCurrentRequest($domainEntry['domainName'], $currentRequest)) {
|
||||
$result = $domainEntry;
|
||||
$result['isCurrentDomain'] = true;
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given domain name (potentially including a path segment) matches currently requested host or
|
||||
* the host including the path segment
|
||||
*
|
||||
* @param string $domainName
|
||||
* @param ServerRequestInterface|null $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function domainNameMatchesCurrentRequest($domainName, ServerRequestInterface $request): bool
|
||||
{
|
||||
/** @var NormalizedParams $normalizedParams */
|
||||
$normalizedParams = $request->getAttribute('normalizedParams');
|
||||
if (!($normalizedParams instanceof NormalizedParams)) {
|
||||
return false;
|
||||
}
|
||||
$currentDomain = $normalizedParams->getHttpHost();
|
||||
// remove the script filename from the path segment.
|
||||
$currentPathSegment = trim(preg_replace('|/[^/]*$|', '', $normalizedParams->getScriptName()));
|
||||
return $currentDomain === $domainName || $currentDomain . $currentPathSegment === $domainName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Composer;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 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 Composer\Script\Event;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\InstallerScripts\EntryPoint;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\InstallerScriptsRegistration;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\ScriptDispatcher;
|
||||
|
||||
/**
|
||||
* Hook into Composer build to generate TYPO3 frontend entry script
|
||||
*
|
||||
* @internal only used for TYPO3 Core internally.
|
||||
*/
|
||||
class InstallerScripts implements InstallerScriptsRegistration
|
||||
{
|
||||
/**
|
||||
* @param Event $event
|
||||
* @param ScriptDispatcher $scriptDispatcher
|
||||
*/
|
||||
public static function register(Event $event, ScriptDispatcher $scriptDispatcher)
|
||||
{
|
||||
$scriptDispatcher->addInstallerScript(
|
||||
new EntryPoint(
|
||||
dirname(__DIR__, 2) . '/Resources/Private/Php/frontend.php',
|
||||
'index.php'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
<?php
|
||||
|
||||
namespace TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractConditionMatcher;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Context\UserAspect;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Matching TypoScript conditions for frontend disposal.
|
||||
*
|
||||
* Used with the TypoScript parser. Matches browserinfo
|
||||
* and IP numbers for use with templates.
|
||||
*/
|
||||
class ConditionMatcher extends AbstractConditionMatcher
|
||||
{
|
||||
/**
|
||||
* @var Context
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* @param Context $context optional context to fetch data from
|
||||
*/
|
||||
public function __construct(Context $context = null)
|
||||
{
|
||||
$this->context = $context ?? GeneralUtility::makeInstance(Context::class);
|
||||
$this->rootline = $this->determineRootline();
|
||||
$this->initializeExpressionLanguageResolver();
|
||||
}
|
||||
|
||||
protected function updateExpressionLanguageVariables(): void
|
||||
{
|
||||
$tree = new \stdClass();
|
||||
$tree->level = $this->rootline ? count($this->rootline) - 1 : 0;
|
||||
$tree->rootLine = $this->rootline;
|
||||
$tree->rootLineIds = array_column($this->rootline, 'uid');
|
||||
|
||||
$frontendUserAspect = $this->context->getAspect('frontend.user');
|
||||
$frontend = new \stdClass();
|
||||
$frontend->user = new \stdClass();
|
||||
$frontend->user->isLoggedIn = $frontendUserAspect->get('isLoggedIn');
|
||||
$frontend->user->userId = $frontendUserAspect->get('id');
|
||||
$frontend->user->userGroupList = implode(',', $frontendUserAspect->get('groupIds'));
|
||||
|
||||
$backendUserAspect = $this->context->getAspect('backend.user');
|
||||
$backend = new \stdClass();
|
||||
$backend->user = new \stdClass();
|
||||
$backend->user->isAdmin = $backendUserAspect->get('isAdmin');
|
||||
$backend->user->isLoggedIn = $backendUserAspect->get('isLoggedIn');
|
||||
$backend->user->userId = $backendUserAspect->get('id');
|
||||
$backend->user->userGroupList = implode(',', $backendUserAspect->get('groupIds'));
|
||||
|
||||
$workspaceAspect = $this->context->getAspect('workspace');
|
||||
$workspace = new \stdClass();
|
||||
$workspace->workspaceId = $workspaceAspect->get('id');
|
||||
$workspace->isLive = $workspaceAspect->get('isLive');
|
||||
$workspace->isOffline = $workspaceAspect->get('isOffline');
|
||||
|
||||
$this->expressionLanguageResolverVariables = [
|
||||
'tree' => $tree,
|
||||
'frontend' => $frontend,
|
||||
'backend' => $backend,
|
||||
'workspace' => $workspace,
|
||||
'page' => $this->getPage(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates a TypoScript condition given as input,
|
||||
* eg. "[browser=net][...(other conditions)...]"
|
||||
*
|
||||
* @param string $string The condition to match against its criteria.
|
||||
* @return bool Whether the condition matched
|
||||
* @see \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::parse()
|
||||
* @throws \TYPO3\CMS\Core\Configuration\TypoScript\Exception\InvalidTypoScriptConditionException
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function evaluateCondition($string)
|
||||
{
|
||||
if ($this->strictSyntaxEnabled()) {
|
||||
trigger_error('The old condition syntax will be removed in TYPO3 v10.0, use the new expression language. Used condition: [' . $string . '].', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
list($key, $value) = GeneralUtility::trimExplode('=', $string, false, 2);
|
||||
$result = $this->evaluateConditionCommon($key, $value);
|
||||
if (is_bool($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
switch ($key) {
|
||||
case 'usergroup':
|
||||
$groupList = $this->getGroupList();
|
||||
// '0,-1' is the default usergroups when not logged in!
|
||||
if ($groupList !== '0,-1') {
|
||||
$values = GeneralUtility::trimExplode(',', $value, true);
|
||||
foreach ($values as $test) {
|
||||
if ($test === '*' || GeneralUtility::inList($groupList, $test)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'treeLevel':
|
||||
$values = GeneralUtility::trimExplode(',', $value, true);
|
||||
$treeLevel = count($this->rootline) - 1;
|
||||
foreach ($values as $test) {
|
||||
if ($test == $treeLevel) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'PIDupinRootline':
|
||||
case 'PIDinRootline':
|
||||
$values = GeneralUtility::trimExplode(',', $value, true);
|
||||
if ($key === 'PIDinRootline' || !in_array($this->pageId, $values)) {
|
||||
foreach ($values as $test) {
|
||||
foreach ($this->rootline as $rlDat) {
|
||||
if ($rlDat['uid'] == $test) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'site':
|
||||
$site = $this->getCurrentSite();
|
||||
if ($site instanceof Site) {
|
||||
$values = GeneralUtility::trimExplode(',', $value, true);
|
||||
foreach ($values as $test) {
|
||||
$point = strcspn($test, '=');
|
||||
$testValue = substr($test, $point + 1);
|
||||
$testValue = trim($testValue);
|
||||
$theVarName = trim(substr($test, 0, $point));
|
||||
$methodName = 'get' . ucfirst($theVarName);
|
||||
if (method_exists($site, $methodName)) {
|
||||
$sitePropertyValue = call_user_func([$site, $methodName]);
|
||||
// loose check on purpose in order to check for integer values
|
||||
if ($testValue == $sitePropertyValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'siteLanguage':
|
||||
$siteLanguage = $this->getCurrentSiteLanguage();
|
||||
if ($siteLanguage instanceof SiteLanguage) {
|
||||
$values = GeneralUtility::trimExplode(',', $value, true);
|
||||
foreach ($values as $test) {
|
||||
$point = strcspn($test, '=');
|
||||
$testValue = substr($test, $point + 1);
|
||||
$testValue = trim($testValue);
|
||||
$theVarName = trim(substr($test, 0, $point));
|
||||
$methodName = 'get' . ucfirst($theVarName);
|
||||
if (method_exists($siteLanguage, $methodName)) {
|
||||
$languagePropertyValue = call_user_func([$siteLanguage, $methodName]);
|
||||
// loose check on purpose in order to check for integer values
|
||||
if ($testValue == $languagePropertyValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$conditionResult = $this->evaluateCustomDefinedCondition($string);
|
||||
if ($conditionResult !== null) {
|
||||
return $conditionResult;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns GP / ENV / TSFE / session vars
|
||||
*
|
||||
* @example GP:L
|
||||
* @example TSFE:fe_user|sesData|foo|bar
|
||||
* @example TSFE:id
|
||||
* @example ENV:HTTP_HOST
|
||||
*
|
||||
* @param string $var Identifier
|
||||
* @return mixed|null The value of the variable pointed to or NULL if variable did not exist
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function getVariable($var)
|
||||
{
|
||||
$vars = explode(':', $var, 2);
|
||||
$val = $this->getVariableCommon($vars);
|
||||
if ($val === null) {
|
||||
$splitAgain = explode('|', $vars[1], 2);
|
||||
$k = trim($splitAgain[0]);
|
||||
if ($k) {
|
||||
switch ((string)trim($vars[0])) {
|
||||
case 'TSFE':
|
||||
if (strpos($vars[1], 'fe_user|sesData|') === 0) {
|
||||
trigger_error(
|
||||
'Condition on TSFE|fe_user|sesData is deprecated and will be removed in TYPO3 v10.0.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$val = $this->getSessionVariable(substr($vars[1], 16));
|
||||
} else {
|
||||
$val = $this->getGlobal('TSFE|' . $vars[1]);
|
||||
}
|
||||
break;
|
||||
case 'session':
|
||||
$val = $this->getSessionVariable($vars[1]);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return variable from current frontend user session
|
||||
*
|
||||
* @param string $var Session key
|
||||
* @return mixed|null The value of the variable pointed to or NULL if variable did not exist
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function getSessionVariable(string $var)
|
||||
{
|
||||
$retVal = null;
|
||||
$keyParts = explode('|', $var);
|
||||
$sessionKey = array_shift($keyParts);
|
||||
$tsfe = $this->getTypoScriptFrontendController();
|
||||
if ($tsfe && is_object($tsfe->fe_user)) {
|
||||
$retVal = $tsfe->fe_user->getSessionData($sessionKey);
|
||||
foreach ($keyParts as $keyPart) {
|
||||
if (is_object($retVal)) {
|
||||
$retVal = $retVal->{$keyPart};
|
||||
} elseif (is_array($retVal)) {
|
||||
$retVal = $retVal[$keyPart];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the usergroup list of the current user.
|
||||
*
|
||||
* @return string The usergroup list of the current user
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function getGroupList(): string
|
||||
{
|
||||
/** @var UserAspect $userAspect */
|
||||
$userAspect = $this->context->getAspect('frontend.user');
|
||||
return implode(',', $userAspect->getGroupIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the current page Id.
|
||||
*
|
||||
* @return int The current page Id
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function determinePageId()
|
||||
{
|
||||
return (int)($this->getTypoScriptFrontendController()->id ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the properties for the current page.
|
||||
*
|
||||
* @return array The properties for the current page.
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function getPage()
|
||||
{
|
||||
return is_array($this->getTypoScriptFrontendController()->page)
|
||||
? $this->getTypoScriptFrontendController()->page
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the rootline for the current page.
|
||||
*
|
||||
* @return array The rootline for the current page.
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function determineRootline()
|
||||
{
|
||||
return (array)$this->getTypoScriptFrontendController()->tmpl->rootLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the current user.
|
||||
*
|
||||
* @return int The id of the current user
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function getUserId(): int
|
||||
{
|
||||
$userAspect = $this->context->getAspect('frontend.user');
|
||||
return $userAspect->get('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a user is logged in.
|
||||
*
|
||||
* @return bool Determines if a user is logged in
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function isUserLoggedIn(): bool
|
||||
{
|
||||
/** @var UserAspect $userAspect */
|
||||
$userAspect = $this->context->getAspect('frontend.user');
|
||||
return $userAspect->isLoggedIn();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function getTypoScriptFrontendController()
|
||||
{
|
||||
return $GLOBALS['TSFE'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently configured "site language" if a site is configured (= resolved) in the current request.
|
||||
*
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function getCurrentSiteLanguage(): ?SiteLanguage
|
||||
{
|
||||
if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface
|
||||
&& $GLOBALS['TYPO3_REQUEST']->getAttribute('language') instanceof SiteLanguage) {
|
||||
return $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently configured site if a site is configured (= resolved) in the current request.
|
||||
*
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
protected function getCurrentSite(): ?Site
|
||||
{
|
||||
if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface
|
||||
&& $GLOBALS['TYPO3_REQUEST']->getAttribute('site') instanceof Site) {
|
||||
return $GLOBALS['TYPO3_REQUEST']->getAttribute('site');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Contains an abstract class for all tslib content class implementations.
|
||||
*/
|
||||
abstract class AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* @var ContentObjectRenderer
|
||||
*/
|
||||
protected $cObj;
|
||||
|
||||
/**
|
||||
* @var PageRenderer
|
||||
*/
|
||||
protected $pageRenderer;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* @param ContentObjectRenderer $cObj
|
||||
*/
|
||||
public function __construct(ContentObjectRenderer $cObj)
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content object.
|
||||
*
|
||||
* @param array $conf
|
||||
* @return string
|
||||
*/
|
||||
abstract public function render($conf = []);
|
||||
|
||||
/**
|
||||
* Getter for current ContentObjectRenderer
|
||||
*
|
||||
* @return ContentObjectRenderer
|
||||
*/
|
||||
public function getContentObjectRenderer()
|
||||
{
|
||||
return $this->cObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PageRenderer
|
||||
*/
|
||||
protected function getPageRenderer()
|
||||
{
|
||||
if ($this->pageRenderer === null) {
|
||||
$this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
|
||||
}
|
||||
|
||||
return $this->pageRenderer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains CASE class object.
|
||||
*/
|
||||
class CaseContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, CASE
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
if (!empty($conf['if.']) && !$this->cObj->checkIf($conf['if.'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$setCurrent = isset($conf['setCurrent.'])
|
||||
? $this->cObj->stdWrap($conf['setCurrent'] ?? '', $conf['setCurrent.'])
|
||||
: ($conf['setCurrent'] ?? null);
|
||||
if ($setCurrent) {
|
||||
$this->cObj->data[$this->cObj->currentValKey] = $setCurrent;
|
||||
}
|
||||
$key = isset($conf['key.']) ? $this->cObj->stdWrap($conf['key'], $conf['key.']) : $conf['key'];
|
||||
$key = isset($conf[$key]) && (string)$conf[$key] !== '' ? $key : 'default';
|
||||
// If no "default" property is available, then an empty string is returned
|
||||
if ($key === 'default' && !isset($conf['default'])) {
|
||||
$theValue = '';
|
||||
} else {
|
||||
$theValue = $this->cObj->cObjGetSingle($conf[$key], $conf[$key . '.'] ?? [], $key);
|
||||
}
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']);
|
||||
}
|
||||
return $theValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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\TimeTracker\TimeTracker;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* Contains CONTENT class object.
|
||||
*/
|
||||
class ContentContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, CONTENT
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
if (!empty($conf['if.']) && !$this->cObj->checkIf($conf['if.'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$frontendController = $this->getFrontendController();
|
||||
$theValue = '';
|
||||
$originalRec = $frontendController->currentRecord;
|
||||
// If the currentRecord is set, we register, that this record has invoked this function.
|
||||
// It's should not be allowed to do this again then!!
|
||||
if ($originalRec) {
|
||||
++$frontendController->recordRegister[$originalRec];
|
||||
}
|
||||
$conf['table'] = isset($conf['table.']) ? trim($this->cObj->stdWrap($conf['table'], $conf['table.'])) : trim($conf['table']);
|
||||
$conf['select.'] = !empty($conf['select.']) ? $conf['select.'] : [];
|
||||
$renderObjName = $conf['renderObj'] ?: '<' . $conf['table'];
|
||||
$renderObjKey = $conf['renderObj'] ? 'renderObj' : '';
|
||||
$renderObjConf = $conf['renderObj.'];
|
||||
$slide = isset($conf['slide.']) ? (int)$this->cObj->stdWrap($conf['slide'], $conf['slide.']) : (int)$conf['slide'];
|
||||
if (!$slide) {
|
||||
$slide = 0;
|
||||
}
|
||||
$slideCollect = isset($conf['slide.']['collect.']) ? (int)$this->cObj->stdWrap($conf['slide.']['collect'], $conf['slide.']['collect.']) : (int)$conf['slide.']['collect'];
|
||||
if (!$slideCollect) {
|
||||
$slideCollect = 0;
|
||||
}
|
||||
$slideCollectReverse = isset($conf['slide.']['collectReverse.']) ? (int)$this->cObj->stdWrap($conf['slide.']['collectReverse'], $conf['slide.']['collectReverse.']) : (int)$conf['slide.']['collectReverse'];
|
||||
$slideCollectReverse = (bool)$slideCollectReverse;
|
||||
$slideCollectFuzzy = isset($conf['slide.']['collectFuzzy.'])
|
||||
? (bool)$this->cObj->stdWrap($conf['slide.']['collectFuzzy'], $conf['slide.']['collectFuzzy.'])
|
||||
: (bool)$conf['slide.']['collectFuzzy'];
|
||||
if (!$slideCollect) {
|
||||
$slideCollectFuzzy = true;
|
||||
}
|
||||
$again = false;
|
||||
$tmpValue = '';
|
||||
|
||||
do {
|
||||
$records = $this->cObj->getRecords($conf['table'], $conf['select.']);
|
||||
$cobjValue = '';
|
||||
if (!empty($records)) {
|
||||
$this->cObj->currentRecordTotal = count($records);
|
||||
$this->getTimeTracker()->setTSlogMessage('NUMROWS: ' . count($records));
|
||||
|
||||
/** @var ContentObjectRenderer $cObj */
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$cObj->setParent($this->cObj->data, $this->cObj->currentRecord);
|
||||
$this->cObj->currentRecordNumber = 0;
|
||||
|
||||
foreach ($records as $row) {
|
||||
// Call hook for possible manipulation of database row for cObj->data
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content_content.php']['modifyDBRow'] ?? [] as $className) {
|
||||
$_procObj = GeneralUtility::makeInstance($className);
|
||||
$_procObj->modifyDBRow($row, $conf['table']);
|
||||
}
|
||||
if (!$frontendController->recordRegister[$conf['table'] . ':' . $row['uid']]) {
|
||||
$this->cObj->currentRecordNumber++;
|
||||
$cObj->parentRecordNumber = $this->cObj->currentRecordNumber;
|
||||
$frontendController->currentRecord = $conf['table'] . ':' . $row['uid'];
|
||||
$this->cObj->lastChanged($row['tstamp']);
|
||||
$cObj->start($row, $conf['table']);
|
||||
$tmpValue = $cObj->cObjGetSingle($renderObjName, $renderObjConf, $renderObjKey);
|
||||
$cobjValue .= $tmpValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($slideCollectReverse) {
|
||||
$theValue = $cobjValue . $theValue;
|
||||
} else {
|
||||
$theValue .= $cobjValue;
|
||||
}
|
||||
if ($slideCollect > 0) {
|
||||
$slideCollect--;
|
||||
}
|
||||
if ($slide) {
|
||||
if ($slide > 0) {
|
||||
$slide--;
|
||||
}
|
||||
$conf['select.']['pidInList'] = $this->cObj->getSlidePids($conf['select.']['pidInList'], $conf['select.']['pidInList.']);
|
||||
if (isset($conf['select.']['pidInList.'])) {
|
||||
unset($conf['select.']['pidInList.']);
|
||||
}
|
||||
$again = (string)$conf['select.']['pidInList'] !== '';
|
||||
}
|
||||
} while ($again && $slide && ((string)$tmpValue === '' && $slideCollectFuzzy || $slideCollect));
|
||||
|
||||
$wrap = isset($conf['wrap.']) ? $this->cObj->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
|
||||
if ($wrap) {
|
||||
$theValue = $this->cObj->wrap($theValue, $wrap);
|
||||
}
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']);
|
||||
}
|
||||
// Restore
|
||||
$frontendController->currentRecord = $originalRec;
|
||||
if ($originalRec) {
|
||||
--$frontendController->recordRegister[$originalRec];
|
||||
}
|
||||
return $theValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the frontend controller
|
||||
*
|
||||
* @return TypoScriptFrontendController
|
||||
*/
|
||||
protected function getFrontendController()
|
||||
{
|
||||
return $GLOBALS['TSFE'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Time Tracker
|
||||
*
|
||||
* @return TimeTracker
|
||||
*/
|
||||
protected function getTimeTracker()
|
||||
{
|
||||
return GeneralUtility::makeInstance(TimeTracker::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* A class that contains methods that can be used to use the dataProcessing functionality
|
||||
*/
|
||||
class ContentDataProcessor
|
||||
{
|
||||
/**
|
||||
* Check for the availability of processors, defined in TypoScript, and use them for data processing
|
||||
*
|
||||
* @param ContentObjectRenderer $cObject
|
||||
* @param array $configuration Configuration array
|
||||
* @param array $variables the variables to be processed
|
||||
* @return array the processed data and variables as key/value store
|
||||
* @throws \UnexpectedValueException If a processor class does not exist
|
||||
*/
|
||||
public function process(ContentObjectRenderer $cObject, array $configuration, array $variables)
|
||||
{
|
||||
if (
|
||||
!empty($configuration['dataProcessing.'])
|
||||
&& is_array($configuration['dataProcessing.'])
|
||||
) {
|
||||
$processors = $configuration['dataProcessing.'];
|
||||
$processorKeys = ArrayUtility::filterAndSortByNumericKeys($processors);
|
||||
|
||||
foreach ($processorKeys as $key) {
|
||||
$className = $processors[$key];
|
||||
if (!class_exists($className)) {
|
||||
throw new \UnexpectedValueException('Processor class name "' . $className . '" does not exist!', 1427455378);
|
||||
}
|
||||
|
||||
if (!in_array(DataProcessorInterface::class, class_implements($className), true)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Processor with class name "' . $className . '" ' .
|
||||
'must implement interface "' . DataProcessorInterface::class . '"',
|
||||
1427455377
|
||||
);
|
||||
}
|
||||
|
||||
$processorConfiguration = $processors[$key . '.'] ?? [];
|
||||
|
||||
$variables = GeneralUtility::makeInstance($className)->process(
|
||||
$cObject,
|
||||
$configuration,
|
||||
$processorConfiguration,
|
||||
$variables
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $variables;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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\TimeTracker\TimeTracker;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Contains COA class object.
|
||||
*/
|
||||
class ContentObjectArrayContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, COBJ_ARRAY / COA
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
if (empty($conf)) {
|
||||
$this->getTimeTracker()->setTSlogMessage('No elements in this content object array (COBJ_ARRAY, COA).', 2);
|
||||
return '';
|
||||
}
|
||||
if (!empty($conf['if.']) && !$this->cObj->checkIf($conf['if.'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = $this->cObj->cObjGet($conf);
|
||||
$wrap = isset($conf['wrap.']) ? $this->cObj->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
|
||||
if ($wrap) {
|
||||
$content = $this->cObj->wrap($content, $wrap);
|
||||
}
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TimeTracker
|
||||
*/
|
||||
protected function getTimeTracker()
|
||||
{
|
||||
return GeneralUtility::makeInstance(TimeTracker::class);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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\TimeTracker\TimeTracker;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* Contains COA_INT class object.
|
||||
*/
|
||||
class ContentObjectArrayInternalContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, COA_INT
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
if (!is_array($conf)) {
|
||||
$this->getTimeTracker()->setTSlogMessage('No elements in this content object array (COA_INT).', 2);
|
||||
return '';
|
||||
}
|
||||
$substKey = 'INT_SCRIPT.' . $this->getTypoScriptFrontendController()->uniqueHash();
|
||||
$content = '<!--' . $substKey . '-->';
|
||||
$this->getTypoScriptFrontendController()->config['INTincScript'][$substKey] = [
|
||||
'conf' => $conf,
|
||||
'cObj' => serialize($this->cObj),
|
||||
'type' => 'COA'
|
||||
];
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TimeTracker
|
||||
*/
|
||||
protected function getTimeTracker()
|
||||
{
|
||||
return GeneralUtility::makeInstance(TimeTracker::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TypoScriptFrontendController
|
||||
*/
|
||||
protected function getTypoScriptFrontendController()
|
||||
{
|
||||
return $GLOBALS['TSFE'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* interface for classes which hook into \TYPO3\CMS\Frontend\ContentObject and do additional getData processing
|
||||
*/
|
||||
interface ContentObjectGetDataHookInterface
|
||||
{
|
||||
/**
|
||||
* Extends the getData()-Method of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer to process more/other commands
|
||||
*
|
||||
* @param string $getDataString Full content of getData-request e.g. "TSFE:id // field:title // field:uid
|
||||
* @param array $fields Current field-array
|
||||
* @param string $sectionValue Currently examined section value of the getData request e.g. "field:title
|
||||
* @param string $returnValue Current returnValue that was processed so far by getData
|
||||
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject Parent content object
|
||||
* @return string Get data result
|
||||
*/
|
||||
public function getDataExtension($getDataString, array $fields, $sectionValue, $returnValue, ContentObjectRenderer &$parentObject);
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes which hook into \TYPO3\CMS\Frontend\ContentObject and do additional getImgResource processing
|
||||
*/
|
||||
interface ContentObjectGetImageResourceHookInterface
|
||||
{
|
||||
/**
|
||||
* Hook for post-processing image resources
|
||||
*
|
||||
* @param string $file Original image file
|
||||
* @param array $configuration TypoScript getImgResource properties
|
||||
* @param array $imageResource Information of the created/converted image resource
|
||||
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parent Parent content object
|
||||
* @return array Modified image resource information
|
||||
*/
|
||||
public function getImgResourcePostProcess($file, array $configuration, array $imageResource, ContentObjectRenderer $parent);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
|
||||
/**
|
||||
* Interface for hooks to fetch the public URL of files
|
||||
*/
|
||||
interface ContentObjectGetPublicUrlForFileHookInterface
|
||||
{
|
||||
/**
|
||||
* Post-processes a public URL.
|
||||
*
|
||||
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parent The current content object (context)
|
||||
* @param array $configuration TypoScript configuration
|
||||
* @param File $file The file object to be used
|
||||
* @param string $pubicUrl Reference to the public URL
|
||||
*/
|
||||
public function postProcess(ContentObjectRenderer $parent, array $configuration, File $file, &$pubicUrl);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes which hook into \TYPO3\CMS\Frontend\ContentObject and do additional cObjGetSingle processing
|
||||
*/
|
||||
interface ContentObjectGetSingleHookInterface
|
||||
{
|
||||
/**
|
||||
* Renders content objects, that are not defined in the core
|
||||
*
|
||||
* @param string $contentObjectName The content object name, eg. "TEXT" or "USER" or "IMAGE
|
||||
* @param array $configuration Array with TypoScript properties for the content object
|
||||
* @param string $TypoScriptKey Label used for the internal debug tracking
|
||||
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject Parent content object
|
||||
* @return string cObject output
|
||||
*/
|
||||
public function getSingleContentObject($contentObjectName, array $configuration, $TypoScriptKey, ContentObjectRenderer &$parentObject);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes which hook into getSourceCollection for additional processing
|
||||
*/
|
||||
interface ContentObjectOneSourceCollectionHookInterface
|
||||
{
|
||||
/**
|
||||
* Renders One Source Collection
|
||||
*
|
||||
* @param array $sourceRenderConfiguration Array with TypoScript Properties for the imgResource
|
||||
* @param array $sourceConfiguration
|
||||
* @param string $oneSourceCollection already prerendered SourceCollection
|
||||
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject Parent content object
|
||||
* @internal param array $configuration Array with the Source Configuration
|
||||
* @return string HTML Content for oneSourceCollection
|
||||
*/
|
||||
public function getOneSourceCollection(array $sourceRenderConfiguration, array $sourceConfiguration, $oneSourceCollection, ContentObjectRenderer &$parentObject);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes which hook into \TYPO3\CMS\Frontend\ContentObject and do additional
|
||||
* initialization processing.
|
||||
*/
|
||||
interface ContentObjectPostInitHookInterface
|
||||
{
|
||||
/**
|
||||
* Hook for post processing the initialization of ContentObjectRenderer
|
||||
*
|
||||
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject Parent content object
|
||||
*/
|
||||
public function postProcessContentObjectInitialization(ContentObjectRenderer &$parentObject);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* interface for classes which hook into \TYPO3\CMS\Frontend\ContentObject and do additional stdWrap processing
|
||||
*/
|
||||
interface ContentObjectStdWrapHookInterface
|
||||
{
|
||||
/**
|
||||
* Hook for modifying $content before core's stdWrap does anything
|
||||
*
|
||||
* @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
|
||||
* @param array $configuration TypoScript stdWrap properties
|
||||
* @param ContentObjectRenderer $parentObject Parent content object
|
||||
* @return string Further processed $content
|
||||
*/
|
||||
public function stdWrapPreProcess($content, array $configuration, ContentObjectRenderer &$parentObject);
|
||||
|
||||
/**
|
||||
* Hook for modifying $content after core's stdWrap has processed setContentToCurrent, setCurrent, lang, data, field, current, cObject, numRows, filelist and/or preUserFunc
|
||||
*
|
||||
* @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
|
||||
* @param array $configuration TypoScript stdWrap properties
|
||||
* @param ContentObjectRenderer $parentObject Parent content object
|
||||
* @return string Further processed $content
|
||||
*/
|
||||
public function stdWrapOverride($content, array $configuration, ContentObjectRenderer &$parentObject);
|
||||
|
||||
/**
|
||||
* Hook for modifying $content after core's stdWrap has processed override, preIfEmptyListNum, ifEmpty, ifBlank, listNum, trim and/or more (nested) stdWraps
|
||||
*
|
||||
* @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
|
||||
* @param array $configuration TypoScript "stdWrap properties".
|
||||
* @param ContentObjectRenderer $parentObject Parent content object
|
||||
* @return string Further processed $content
|
||||
*/
|
||||
public function stdWrapProcess($content, array $configuration, ContentObjectRenderer &$parentObject);
|
||||
|
||||
/**
|
||||
* Hook for modifying $content after core's stdWrap has processed anything but debug
|
||||
*
|
||||
* @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
|
||||
* @param array $configuration TypoScript stdWrap properties
|
||||
* @param ContentObjectRenderer $parentObject Parent content object
|
||||
* @return string Further processed $content
|
||||
*/
|
||||
public function stdWrapPostProcess($content, array $configuration, ContentObjectRenderer &$parentObject);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for data processor classes processing data from
|
||||
* ContentObjectRenderer, used e.g. with the FLUIDTEMPLATE content object
|
||||
*/
|
||||
interface DataProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Process content object data
|
||||
*
|
||||
* @param ContentObjectRenderer $cObj The data of the content element or page
|
||||
* @param array $contentObjectConfiguration The configuration of Content Object
|
||||
* @param array $processorConfiguration The configuration of this processor
|
||||
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
|
||||
* @return array the processed data as key/value store
|
||||
*/
|
||||
public function process(
|
||||
ContentObjectRenderer $cObj,
|
||||
array $contentObjectConfiguration,
|
||||
array $processorConfiguration,
|
||||
array $processedData
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains EDITPANEL class object.
|
||||
*/
|
||||
class EditPanelContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, EDITPANEL
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
$theValue = '';
|
||||
if ($GLOBALS['TSFE']->isBackendUserLoggedIn()) {
|
||||
$theValue = $this->cObj->editPanel($theValue, $conf);
|
||||
}
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']);
|
||||
}
|
||||
return $theValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Exception;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class Exception
|
||||
* @internal this is a concrete TYPO3 implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class ContentRenderingException extends \TYPO3\CMS\Core\Error\Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Exception;
|
||||
|
||||
/*
|
||||
* 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\Frontend\ContentObject\AbstractContentObject;
|
||||
|
||||
/**
|
||||
* Interface ExceptionHandlerInterface
|
||||
*/
|
||||
interface ExceptionHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Handles exceptions thrown during rendering of content objects
|
||||
* The handler can decide whether to re-throw the exception or
|
||||
* return a nice error message for production context.
|
||||
*
|
||||
* @param \Exception $exception
|
||||
* @param AbstractContentObject $contentObject
|
||||
* @param array $contentObjectConfiguration
|
||||
* @return string
|
||||
*/
|
||||
public function handle(\Exception $exception, AbstractContentObject $contentObject = null, $contentObjectConfiguration = []);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use TYPO3\CMS\Core\Crypto\Random;
|
||||
use TYPO3\CMS\Core\Http\ImmediateResponseException;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject;
|
||||
|
||||
/**
|
||||
* Exception handler class for content object rendering
|
||||
* @internal this is a concrete TYPO3 implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class ProductionExceptionHandler implements ExceptionHandlerInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration = [];
|
||||
|
||||
/**
|
||||
* @param array $configuration
|
||||
*/
|
||||
public function __construct(array $configuration = [])
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles exceptions thrown during rendering of content objects
|
||||
* The handler can decide whether to re-throw the exception or
|
||||
* return a nice error message for production context.
|
||||
*
|
||||
* @param \Exception $exception
|
||||
* @param AbstractContentObject $contentObject
|
||||
* @param array $contentObjectConfiguration
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(\Exception $exception, AbstractContentObject $contentObject = null, $contentObjectConfiguration = [])
|
||||
{
|
||||
// ImmediateResponseException should work similar to exit / die and must therefore not be handled by this ExceptionHandler.
|
||||
if ($exception instanceof ImmediateResponseException) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if (!empty($this->configuration['ignoreCodes.'])) {
|
||||
if (in_array($exception->getCode(), array_map('intval', $this->configuration['ignoreCodes.']), true)) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
$errorMessage = $this->configuration['errorMessage'] ?? 'Oops, an error occurred! Code: %s';
|
||||
$code = date('YmdHis', $_SERVER['REQUEST_TIME']) . GeneralUtility::makeInstance(Random::class)->generateRandomHexString(8);
|
||||
|
||||
$this->logException($exception, $errorMessage, $code);
|
||||
|
||||
return sprintf($errorMessage, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Exception $exception
|
||||
* @param string $errorMessage
|
||||
* @param string $code
|
||||
*/
|
||||
protected function logException(\Exception $exception, $errorMessage, $code)
|
||||
{
|
||||
$this->logger->alert(sprintf($errorMessage, $code), ['exception' => $exception]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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\Type\File\ImageInfo;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
|
||||
|
||||
/**
|
||||
* Contains FILE class object.
|
||||
* @deprecated FILE cObject will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
class FileContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, FILE
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
$this->getTypoScriptFrontendController()->logDeprecatedTyposcript('cObject FILE', 'Use IMAGE to show images instead.');
|
||||
$theValue = '';
|
||||
$file = isset($conf['file.']) ? $this->cObj->stdWrap($conf['file'], $conf['file.']) : $conf['file'];
|
||||
try {
|
||||
$file = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($file);
|
||||
if (file_exists($file)) {
|
||||
$fileInfo = GeneralUtility::split_fileref($file);
|
||||
$extension = $fileInfo['fileext'];
|
||||
if ($extension === 'jpg' || $extension === 'jpeg' || $extension === 'gif' || $extension === 'png') {
|
||||
$imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $file);
|
||||
$altParameters = trim($this->cObj->getAltParam($conf, false));
|
||||
$theValue = '<img src="'
|
||||
. htmlspecialchars($this->getTypoScriptFrontendController()->absRefPrefix . $file)
|
||||
. '" width="' . (int)$imageInfo->getWidth() . '" height="' . (int)$imageInfo->getHeight()
|
||||
. '"' . $this->cObj->getBorderAttr(' border="0"') . ' ' . $altParameters . ' />';
|
||||
} elseif (filesize($file) < 1024 * 1024) {
|
||||
$theValue = file_get_contents($file);
|
||||
}
|
||||
}
|
||||
} catch (\TYPO3\CMS\Core\Resource\Exception $e) {
|
||||
// do nothing
|
||||
}
|
||||
$linkWrap = isset($conf['linkWrap.']) ? $this->cObj->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
|
||||
if ($linkWrap) {
|
||||
$theValue = $this->cObj->linkWrap($theValue, $linkWrap);
|
||||
}
|
||||
$wrap = isset($conf['wrap.']) ? $this->cObj->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
|
||||
if ($wrap) {
|
||||
$theValue = $this->cObj->wrap($theValue, $wrap);
|
||||
}
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']);
|
||||
}
|
||||
return $theValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TypoScriptFrontendController
|
||||
*/
|
||||
protected function getTypoScriptFrontendController()
|
||||
{
|
||||
return $GLOBALS['TSFE'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
|
||||
/**
|
||||
* Interface for classes which hook into \TYPO3\CMS\Frontend\ContentObject and do additional getImgResource processing
|
||||
*/
|
||||
interface FileLinkHookInterface
|
||||
{
|
||||
/**
|
||||
* Finds alternative previewImage for given File.
|
||||
*
|
||||
* @param File $file
|
||||
* @return File
|
||||
* @abstract
|
||||
*/
|
||||
public function getPreviewImage(File $file);
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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\TypoScript\TypoScriptService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Frontend\Resource\FileCollector;
|
||||
|
||||
/**
|
||||
* Contains FILES content object
|
||||
*/
|
||||
class FilesContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject FILES
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
if (!empty($conf['if.']) && !$this->cObj->checkIf($conf['if.'])) {
|
||||
return '';
|
||||
}
|
||||
// Store the original "currentFile" within a variable so it can be re-applied later-on
|
||||
$originalFileInContentObject = $this->cObj->getCurrentFile();
|
||||
|
||||
$fileCollector = $this->findAndSortFiles($conf);
|
||||
$fileObjects = $fileCollector->getFiles();
|
||||
$availableFileObjectCount = count($fileObjects);
|
||||
|
||||
// optionSplit applied to conf to allow different settings per file
|
||||
$splitConf = GeneralUtility::makeInstance(TypoScriptService::class)
|
||||
->explodeConfigurationForOptionSplit($conf, $availableFileObjectCount);
|
||||
|
||||
$start = 0;
|
||||
if (!empty($conf['begin'])) {
|
||||
$start = (int)$conf['begin'];
|
||||
}
|
||||
if (!empty($conf['begin.'])) {
|
||||
$start = (int)$this->cObj->stdWrap($start, $conf['begin.']);
|
||||
}
|
||||
$start = MathUtility::forceIntegerInRange($start, 0, $availableFileObjectCount);
|
||||
|
||||
$limit = $availableFileObjectCount;
|
||||
if (!empty($conf['maxItems'])) {
|
||||
$limit = (int)$conf['maxItems'];
|
||||
}
|
||||
if (!empty($conf['maxItems.'])) {
|
||||
$limit = (int)$this->cObj->stdWrap($limit, $conf['maxItems.']);
|
||||
}
|
||||
|
||||
$end = MathUtility::forceIntegerInRange($start + $limit, $start, $availableFileObjectCount);
|
||||
|
||||
if (isset($GLOBALS['TSFE'])) {
|
||||
$GLOBALS['TSFE']->register['FILES_COUNT'] = min($limit, $availableFileObjectCount);
|
||||
}
|
||||
$fileObjectCounter = 0;
|
||||
$keys = array_keys($fileObjects);
|
||||
|
||||
$content = '';
|
||||
for ($i = $start; $i < $end; $i++) {
|
||||
$key = $keys[$i];
|
||||
$fileObject = $fileObjects[$key];
|
||||
|
||||
if (isset($GLOBALS['TSFE'])) {
|
||||
$GLOBALS['TSFE']->register['FILE_NUM_CURRENT'] = $fileObjectCounter;
|
||||
}
|
||||
$this->cObj->setCurrentFile($fileObject);
|
||||
$content .= $this->cObj->cObjGetSingle($splitConf[$key]['renderObj'], $splitConf[$key]['renderObj.'], 'renderObj');
|
||||
$fileObjectCounter++;
|
||||
}
|
||||
|
||||
// Reset current file within cObj to the original file after rendering output of FILES
|
||||
// so e.g. stdWrap is not working on the last current file applied, thus avoiding side-effects
|
||||
$this->cObj->setCurrentFile($originalFileInContentObject);
|
||||
|
||||
return $this->cObj->stdWrap($content, $conf['stdWrap.'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check for references, collections, folders and
|
||||
* accumulates into one etc.
|
||||
*
|
||||
* @param array $conf
|
||||
* @return FileCollector
|
||||
*/
|
||||
protected function findAndSortFiles(array $conf)
|
||||
{
|
||||
$fileCollector = $this->getFileCollector();
|
||||
|
||||
// Getting the files
|
||||
if ((isset($conf['references']) && $conf['references']) || (isset($conf['references.']) && $conf['references.'])) {
|
||||
/*
|
||||
The TypoScript could look like this:
|
||||
# all items related to the page.media field:
|
||||
references {
|
||||
table = pages
|
||||
uid.data = page:uid
|
||||
fieldName = media
|
||||
}
|
||||
# or: sys_file_references with uid 27:
|
||||
references = 27
|
||||
*/
|
||||
$referencesUidList = $this->cObj->stdWrapValue('references', $conf);
|
||||
$referencesUids = GeneralUtility::intExplode(',', $referencesUidList, true);
|
||||
$fileCollector->addFileReferences($referencesUids);
|
||||
|
||||
if (!empty($conf['references.'])) {
|
||||
$this->addFileReferences($conf, (array)$this->cObj->data, $fileCollector);
|
||||
}
|
||||
}
|
||||
|
||||
if ((isset($conf['files']) && $conf['files']) || (isset($conf['files.']) && $conf['files.'])) {
|
||||
/*
|
||||
The TypoScript could look like this:
|
||||
# with sys_file UIDs:
|
||||
files = 12,14,15# using stdWrap:
|
||||
files.field = some_field
|
||||
*/
|
||||
$fileUids = GeneralUtility::intExplode(',', $this->cObj->stdWrapValue('files', $conf), true);
|
||||
$fileCollector->addFiles($fileUids);
|
||||
}
|
||||
|
||||
if ((isset($conf['collections']) && $conf['collections']) || (isset($conf['collections.']) && $conf['collections.'])) {
|
||||
$collectionUids = GeneralUtility::intExplode(',', $this->cObj->stdWrapValue('collections', $conf), true);
|
||||
$fileCollector->addFilesFromFileCollections($collectionUids);
|
||||
}
|
||||
|
||||
if ((isset($conf['folders']) && $conf['folders']) || (isset($conf['folders.']) && $conf['folders.'])) {
|
||||
$folderIdentifiers = GeneralUtility::trimExplode(',', $this->cObj->stdWrapValue('folders', $conf));
|
||||
$fileCollector->addFilesFromFolders($folderIdentifiers, !empty($conf['folders.']['recursive']));
|
||||
}
|
||||
|
||||
// Enable sorting for multiple fileObjects
|
||||
$sortingProperty = '';
|
||||
if ((isset($conf['sorting']) && $conf['sorting']) || (isset($conf['sorting.']) && $conf['sorting.'])) {
|
||||
$sortingProperty = $this->cObj->stdWrapValue('sorting', $conf);
|
||||
}
|
||||
if ($sortingProperty !== '') {
|
||||
$sortingDirection = $conf['sorting.']['direction'] ?? '';
|
||||
if (isset($conf['sorting.']['direction.'])) {
|
||||
$sortingDirection = $this->cObj->stdWrap($sortingDirection, $conf['sorting.']['direction.']);
|
||||
}
|
||||
$fileCollector->sort($sortingProperty, $sortingDirection);
|
||||
}
|
||||
|
||||
return $fileCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles and resolves file references.
|
||||
*
|
||||
* @param array $configuration TypoScript configuration
|
||||
* @param array $element The parent element referencing to files
|
||||
* @param FileCollector $fileCollector
|
||||
*/
|
||||
protected function addFileReferences(array $configuration, array $element, FileCollector $fileCollector)
|
||||
{
|
||||
|
||||
// It's important that this always stays "fieldName" and not be renamed to "field" as it would otherwise collide with the stdWrap key of that name
|
||||
$referencesFieldName = $this->cObj->stdWrapValue('fieldName', $configuration['references.']);
|
||||
|
||||
// If no reference fieldName is set, there's nothing to do
|
||||
if (empty($referencesFieldName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentId = !empty($element['uid']) ? $element['uid'] : 0;
|
||||
$tableName = $this->cObj->getCurrentTable();
|
||||
|
||||
// Fetch the references of the default element
|
||||
$referencesForeignTable = $this->cObj->stdWrapValue('table', $configuration['references.'], $tableName);
|
||||
$referencesForeignUid = $this->cObj->stdWrapValue('uid', $configuration['references.'], $currentId);
|
||||
|
||||
$pageRepository = $this->getPageRepository();
|
||||
// Fetch element if definition has been modified via TypoScript
|
||||
if ($referencesForeignTable !== $tableName || $referencesForeignUid !== $currentId) {
|
||||
$element = $pageRepository->getRawRecord($referencesForeignTable, $referencesForeignUid);
|
||||
|
||||
$pageRepository->versionOL($referencesForeignTable, $element, true);
|
||||
if (is_array($element)) {
|
||||
$element = $pageRepository->getLanguageOverlay($referencesForeignTable, $element);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($element)) {
|
||||
$fileCollector->addFilesFromRelation($referencesForeignTable, $referencesFieldName, $element);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Frontend\Page\PageRepository
|
||||
*/
|
||||
protected function getPageRepository()
|
||||
{
|
||||
return $GLOBALS['TSFE']->sys_page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FileCollector
|
||||
*/
|
||||
protected function getFileCollector()
|
||||
{
|
||||
return GeneralUtility::makeInstance(FileCollector::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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\TypoScript\TypoScriptService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
|
||||
use TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
|
||||
|
||||
/**
|
||||
* Contains FLUIDTEMPLATE class object
|
||||
*/
|
||||
class FluidTemplateContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* @var StandaloneView
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @var ContentDataProcessor
|
||||
*/
|
||||
protected $contentDataProcessor;
|
||||
|
||||
/**
|
||||
* @param ContentObjectRenderer $cObj
|
||||
*/
|
||||
public function __construct(ContentObjectRenderer $cObj)
|
||||
{
|
||||
parent::__construct($cObj);
|
||||
$this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContentDataProcessor $contentDataProcessor
|
||||
*/
|
||||
public function setContentDataProcessor($contentDataProcessor)
|
||||
{
|
||||
$this->contentDataProcessor = $contentDataProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendering the cObject, FLUIDTEMPLATE
|
||||
*
|
||||
* Configuration properties:
|
||||
* - file string+stdWrap The FLUID template file
|
||||
* - layoutRootPaths array of filepath+stdWrap Root paths to layouts (fallback)
|
||||
* - partialRootPaths array of filepath+stdWrap Root paths to partials (fallback)
|
||||
* - variable array of cObjects, the keys are the variable names in fluid
|
||||
* - dataProcessing array of data processors which are classes to manipulate $data
|
||||
* - extbase.pluginName
|
||||
* - extbase.controllerExtensionName
|
||||
* - extbase.controllerName
|
||||
* - extbase.controllerActionName
|
||||
*
|
||||
* Example:
|
||||
* 10 = FLUIDTEMPLATE
|
||||
* 10.templateName = MyTemplate
|
||||
* 10.templateRootPaths.10 = EXT:site_configuration/Resources/Private/Templates/
|
||||
* 10.partialRootPaths.10 = EXT:site_configuration/Resources/Private/Patials/
|
||||
* 10.layoutRootPaths.10 = EXT:site_configuration/Resources/Private/Layouts/
|
||||
* 10.variables {
|
||||
* mylabel = TEXT
|
||||
* mylabel.value = Label from TypoScript coming
|
||||
* }
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string The HTML output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
$parentView = $this->view;
|
||||
$this->initializeStandaloneViewInstance();
|
||||
|
||||
if (!is_array($conf)) {
|
||||
$conf = [];
|
||||
}
|
||||
|
||||
$this->setFormat($conf);
|
||||
$this->setTemplate($conf);
|
||||
$this->setLayoutRootPath($conf);
|
||||
$this->setPartialRootPath($conf);
|
||||
$this->setExtbaseVariables($conf);
|
||||
$this->assignSettings($conf);
|
||||
$variables = $this->getContentObjectVariables($conf);
|
||||
$variables = $this->contentDataProcessor->process($this->cObj, $conf, $variables);
|
||||
|
||||
$this->view->assignMultiple($variables);
|
||||
|
||||
$this->renderFluidTemplateAssetsIntoPageRenderer();
|
||||
$content = $this->renderFluidView();
|
||||
$content = $this->applyStandardWrapToRenderedContent($content, $conf);
|
||||
|
||||
$this->view = $parentView;
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to render HeaderAssets and FooterAssets sections from the
|
||||
* Fluid template, then adds each (if not empty) to either header or
|
||||
* footer, as appropriate, using PageRenderer.
|
||||
*/
|
||||
protected function renderFluidTemplateAssetsIntoPageRenderer()
|
||||
{
|
||||
$pageRenderer = $this->getPageRenderer();
|
||||
$headerAssets = $this->view->renderSection('HeaderAssets', ['contentObject' => $this], true);
|
||||
$footerAssets = $this->view->renderSection('FooterAssets', ['contentObject' => $this], true);
|
||||
if (!empty(trim($headerAssets))) {
|
||||
$pageRenderer->addHeaderData($headerAssets);
|
||||
}
|
||||
if (!empty(trim($footerAssets))) {
|
||||
$pageRenderer->addFooterData($footerAssets);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating standalone view instance must not be done in construct() as
|
||||
* it can lead to a nasty cache issue since content object instances
|
||||
* are not always re-created by the content object rendered for every
|
||||
* usage, but can be re-used. Thus, we need a fresh instance of
|
||||
* StandaloneView every time render() is called.
|
||||
*/
|
||||
protected function initializeStandaloneViewInstance()
|
||||
{
|
||||
$this->view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set template
|
||||
*
|
||||
* @param array $conf With possibly set file resource
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function setTemplate(array $conf)
|
||||
{
|
||||
// Fetch the Fluid template by templateName
|
||||
if (
|
||||
(!empty($conf['templateName']) || !empty($conf['templateName.']))
|
||||
&& !empty($conf['templateRootPaths.']) && is_array($conf['templateRootPaths.'])
|
||||
) {
|
||||
$templateRootPaths = $this->applyStandardWrapToFluidPaths($conf['templateRootPaths.']);
|
||||
$this->view->setTemplateRootPaths($templateRootPaths);
|
||||
$templateName = isset($conf['templateName.'])
|
||||
? $this->cObj->stdWrap($conf['templateName'] ?? '', $conf['templateName.'])
|
||||
: $conf['templateName'];
|
||||
$this->view->setTemplate($templateName);
|
||||
} elseif (!empty($conf['template']) && !empty($conf['template.'])) {
|
||||
// Fetch the Fluid template by template cObject
|
||||
$templateSource = $this->cObj->cObjGetSingle($conf['template'], $conf['template.'], 'template');
|
||||
if ($templateSource === '') {
|
||||
throw new ContentRenderingException(
|
||||
'Could not find template source for ' . $conf['template'],
|
||||
1437420865
|
||||
);
|
||||
}
|
||||
$this->view->setTemplateSource($templateSource);
|
||||
} else {
|
||||
// Fetch the Fluid template by file stdWrap
|
||||
$file = isset($conf['file.'])
|
||||
? $this->cObj->stdWrap($conf['file'] ?? '', $conf['file.'])
|
||||
: ($conf['file'] ?? '');
|
||||
// Get the absolute file name
|
||||
$templatePathAndFilename = GeneralUtility::getFileAbsFileName($file);
|
||||
$this->view->setTemplatePathAndFilename($templatePathAndFilename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set layout root path if given in configuration
|
||||
*
|
||||
* @param array $conf Configuration array
|
||||
*/
|
||||
protected function setLayoutRootPath(array $conf)
|
||||
{
|
||||
// Override the default layout path via typoscript
|
||||
$layoutPaths = [];
|
||||
if (isset($conf['layoutRootPath']) || isset($conf['layoutRootPath.'])) {
|
||||
$layoutRootPath = isset($conf['layoutRootPath.'])
|
||||
? $this->cObj->stdWrap($conf['layoutRootPath'], $conf['layoutRootPath.'])
|
||||
: $conf['layoutRootPath'];
|
||||
$layoutPaths[] = GeneralUtility::getFileAbsFileName($layoutRootPath);
|
||||
}
|
||||
if (isset($conf['layoutRootPaths.'])) {
|
||||
$layoutPaths = array_replace($layoutPaths, $this->applyStandardWrapToFluidPaths($conf['layoutRootPaths.']));
|
||||
}
|
||||
if (!empty($layoutPaths)) {
|
||||
$this->view->setLayoutRootPaths($layoutPaths);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set partial root path if given in configuration
|
||||
*
|
||||
* @param array $conf Configuration array
|
||||
*/
|
||||
protected function setPartialRootPath(array $conf)
|
||||
{
|
||||
$partialPaths = [];
|
||||
if (isset($conf['partialRootPath']) || isset($conf['partialRootPath.'])) {
|
||||
$partialRootPath = isset($conf['partialRootPath.'])
|
||||
? $this->cObj->stdWrap($conf['partialRootPath'], $conf['partialRootPath.'])
|
||||
: $conf['partialRootPath'];
|
||||
$partialPaths[] = GeneralUtility::getFileAbsFileName($partialRootPath);
|
||||
}
|
||||
if (isset($conf['partialRootPaths.'])) {
|
||||
$partialPaths = array_replace($partialPaths, $this->applyStandardWrapToFluidPaths($conf['partialRootPaths.']));
|
||||
}
|
||||
if (!empty($partialPaths)) {
|
||||
$this->view->setPartialRootPaths($partialPaths);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set different format if given in configuration
|
||||
*
|
||||
* @param array $conf Configuration array
|
||||
*/
|
||||
protected function setFormat(array $conf)
|
||||
{
|
||||
$format = isset($conf['format.'])
|
||||
? $this->cObj->stdWrap($conf['format'] ?? '', $conf['format.'])
|
||||
: ($conf['format'] ?? '');
|
||||
if ($format) {
|
||||
$this->view->setFormat($format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set some extbase variables if given
|
||||
*
|
||||
* @param array $conf Configuration array
|
||||
*/
|
||||
protected function setExtbaseVariables(array $conf)
|
||||
{
|
||||
/** @var \TYPO3\CMS\Extbase\Mvc\Request $request */
|
||||
$requestPluginName = isset($conf['extbase.']['pluginName.'])
|
||||
? $this->cObj->stdWrap($conf['extbase.']['pluginName'] ?? '', $conf['extbase.']['pluginName.'])
|
||||
: ($conf['extbase.']['pluginName'] ?? '');
|
||||
if ($requestPluginName) {
|
||||
$this->view->getRequest()->setPluginName($requestPluginName);
|
||||
}
|
||||
$requestControllerExtensionName = isset($conf['extbase.']['controllerExtensionName.'])
|
||||
? $this->cObj->stdWrap($conf['extbase.']['controllerExtensionName'] ?? '', $conf['extbase.']['controllerExtensionName.'])
|
||||
: ($conf['extbase.']['controllerExtensionName'] ?? '');
|
||||
if ($requestControllerExtensionName) {
|
||||
$this->view->getRequest()->setControllerExtensionName($requestControllerExtensionName);
|
||||
}
|
||||
$requestControllerName = isset($conf['extbase.']['controllerName.'])
|
||||
? $this->cObj->stdWrap($conf['extbase.']['controllerName'] ?? '', $conf['extbase.']['controllerName.'])
|
||||
: ($conf['extbase.']['controllerName'] ?? '');
|
||||
if ($requestControllerName) {
|
||||
$this->view->getRequest()->setControllerName($requestControllerName);
|
||||
}
|
||||
$requestControllerActionName = isset($conf['extbase.']['controllerActionName.'])
|
||||
? $this->cObj->stdWrap($conf['extbase.']['controllerActionName'] ?? '', $conf['extbase.']['controllerActionName.'])
|
||||
: ($conf['extbase.']['controllerActionName'] ?? '');
|
||||
if ($requestControllerActionName) {
|
||||
$this->view->getRequest()->setControllerActionName($requestControllerActionName);
|
||||
}
|
||||
|
||||
if (
|
||||
$requestPluginName
|
||||
&& $requestControllerExtensionName
|
||||
&& $requestControllerName
|
||||
&& $requestControllerActionName
|
||||
) {
|
||||
$configurationManager = GeneralUtility::makeInstance(ObjectManager::class)->get(ConfigurationManager::class);
|
||||
$configurationManager->setConfiguration([
|
||||
'extensionName' => $requestControllerExtensionName,
|
||||
'pluginName' => $requestPluginName,
|
||||
]);
|
||||
|
||||
if (!isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$requestControllerExtensionName]['plugins'][$requestPluginName]['controllers'])) {
|
||||
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$requestControllerExtensionName]['plugins'][$requestPluginName]['controllers'] = [
|
||||
$requestControllerName => [
|
||||
'actions' => [
|
||||
$requestControllerActionName,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$requestBuilder = GeneralUtility::makeInstance(ObjectManager::class)->get(RequestBuilder::class);
|
||||
$this->view->getRenderingContext()->getControllerContext()->setRequest($requestBuilder->build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile rendered content objects in variables array ready to assign to the view
|
||||
*
|
||||
* @param array $conf Configuration array
|
||||
* @return array the variables to be assigned
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function getContentObjectVariables(array $conf)
|
||||
{
|
||||
$variables = [];
|
||||
$reservedVariables = ['data', 'current'];
|
||||
// Accumulate the variables to be process and loop them through cObjGetSingle
|
||||
$variablesToProcess = (array)($conf['variables.'] ?? []);
|
||||
foreach ($variablesToProcess as $variableName => $cObjType) {
|
||||
if (is_array($cObjType)) {
|
||||
continue;
|
||||
}
|
||||
if (!in_array($variableName, $reservedVariables)) {
|
||||
$variables[$variableName] = $this->cObj->cObjGetSingle($cObjType, $variablesToProcess[$variableName . '.'], 'variables.' . $variableName);
|
||||
} else {
|
||||
throw new \InvalidArgumentException(
|
||||
'Cannot use reserved name "' . $variableName . '" as variable name in FLUIDTEMPLATE.',
|
||||
1288095720
|
||||
);
|
||||
}
|
||||
}
|
||||
$variables['data'] = $this->cObj->data;
|
||||
$variables['current'] = $this->cObj->data[$this->cObj->currentValKey ?? null] ?? null;
|
||||
return $variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set any TypoScript settings to the view. This is similar to a
|
||||
* default MVC action controller in extbase.
|
||||
*
|
||||
* @param array $conf Configuration
|
||||
*/
|
||||
protected function assignSettings(array $conf)
|
||||
{
|
||||
if (isset($conf['settings.'])) {
|
||||
/** @var TypoScriptService $typoScriptService */
|
||||
$typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
|
||||
$settings = $typoScriptService->convertTypoScriptArrayToPlainArray($conf['settings.']);
|
||||
$this->view->assign('settings', $settings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render fluid standalone view
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function renderFluidView()
|
||||
{
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply standard wrap to content
|
||||
*
|
||||
* @param string $content Rendered HTML content
|
||||
* @param array $conf Configuration array
|
||||
* @return string Standard wrapped content
|
||||
*/
|
||||
protected function applyStandardWrapToRenderedContent($content, array $conf)
|
||||
{
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies stdWrap on Fluid path definitions
|
||||
*
|
||||
* @param array $paths
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function applyStandardWrapToFluidPaths(array $paths)
|
||||
{
|
||||
$finalPaths = [];
|
||||
foreach ($paths as $key => $path) {
|
||||
if (StringUtility::endsWith($key, '.')) {
|
||||
if (isset($paths[substr($key, 0, -1)])) {
|
||||
continue;
|
||||
}
|
||||
$path = $this->cObj->stdWrap('', $path);
|
||||
} elseif (isset($paths[$key . '.'])) {
|
||||
$path = $this->cObj->stdWrap($path, $paths[$key . '.']);
|
||||
}
|
||||
$finalPaths[$key] = GeneralUtility::getFileAbsFileName($path);
|
||||
}
|
||||
return $finalPaths;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Contains HMENU class object.
|
||||
*/
|
||||
class HierarchicalMenuContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, HMENU
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
if (!empty($conf['if.']) && !$this->cObj->checkIf($conf['if.'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$theValue = '';
|
||||
$menuType = $conf[1];
|
||||
try {
|
||||
/** @var Menu\MenuContentObjectFactory $menuObjectFactory */
|
||||
$menuObjectFactory = GeneralUtility::makeInstance(Menu\MenuContentObjectFactory::class);
|
||||
$menu = $menuObjectFactory->getMenuObjectByType($menuType);
|
||||
$GLOBALS['TSFE']->register['count_HMENU']++;
|
||||
$GLOBALS['TSFE']->register['count_HMENU_MENUOBJ'] = 0;
|
||||
$GLOBALS['TSFE']->register['count_MENUOBJ'] = 0;
|
||||
$GLOBALS['TSFE']->applicationData['GMENU_LAYERS']['WMid'] = [];
|
||||
$GLOBALS['TSFE']->applicationData['GMENU_LAYERS']['WMparentId'] = [];
|
||||
$menu->parent_cObj = $this->cObj;
|
||||
$menu->start($GLOBALS['TSFE']->tmpl, $GLOBALS['TSFE']->sys_page, '', $conf, 1);
|
||||
$menu->makeMenu();
|
||||
$theValue .= $menu->writeMenu();
|
||||
} catch (Menu\Exception\NoSuchMenuTypeException $e) {
|
||||
}
|
||||
$wrap = isset($conf['wrap.']) ? $this->cObj->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
|
||||
if ($wrap) {
|
||||
$theValue = $this->cObj->wrap($theValue, $wrap);
|
||||
}
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']);
|
||||
}
|
||||
return $theValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains IMAGE class object.
|
||||
*/
|
||||
class ImageContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, IMAGE
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
if (!empty($conf['if.']) && !$this->cObj->checkIf($conf['if.'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$theValue = $this->cObj->cImage($conf['file'], $conf);
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']);
|
||||
}
|
||||
return $theValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains IMG_RESOURCE class object.
|
||||
*/
|
||||
class ImageResourceContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, IMG_RESOURCE
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
*
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
$GLOBALS['TSFE']->lastImgResourceInfo = $this->cObj->getImgResource($conf['file'], $conf['file.']);
|
||||
if ($GLOBALS['TSFE']->lastImgResourceInfo) {
|
||||
$imageResource = $GLOBALS['TSFE']->lastImgResourceInfo[3];
|
||||
$theValue = isset($conf['stdWrap.']) ? $this->cObj->stdWrap($imageResource, $conf['stdWrap.']) : $imageResource;
|
||||
} else {
|
||||
$theValue = '';
|
||||
}
|
||||
|
||||
return $theValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains LOAD_REGISTER class object.
|
||||
*/
|
||||
class LoadRegisterContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, LOAD_REGISTER
|
||||
* NOTICE: This cObject does NOT return any content since it just sets internal data based on the TypoScript properties.
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Empty string (the cObject only sets internal data!)
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
$GLOBALS['TSFE']->registerStack[] = $GLOBALS['TSFE']->register;
|
||||
if (is_array($conf)) {
|
||||
$isExecuted = [];
|
||||
foreach ($conf as $theKey => $theValue) {
|
||||
$register = rtrim($theKey, '.');
|
||||
if (!isset($isExecuted[$register]) || !$isExecuted[$register]) {
|
||||
$registerProperties = $register . '.';
|
||||
if (isset($conf[$register]) && isset($conf[$registerProperties])) {
|
||||
$theValue = $this->cObj->stdWrap($conf[$register], $conf[$registerProperties]);
|
||||
} elseif (isset($conf[$registerProperties])) {
|
||||
$theValue = $this->cObj->stdWrap('', $conf[$registerProperties]);
|
||||
}
|
||||
$GLOBALS['TSFE']->register[$register] = $theValue;
|
||||
$isExecuted[$register] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* interface for classes which hook into AbstractMenuContentObject
|
||||
*/
|
||||
interface AbstractMenuFilterPagesHookInterface
|
||||
{
|
||||
/**
|
||||
* Checks if a page is OK to include in the final menu item array.
|
||||
*
|
||||
* @param array $data Array of menu items
|
||||
* @param array $banUidArray Array of page uids which are to be excluded
|
||||
* @param bool $spacer If set, then the page is a spacer.
|
||||
* @param \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject $obj The menu object
|
||||
* @return bool Returns TRUE if the page can be safely included.
|
||||
*/
|
||||
public function processFilter(array &$data, array $banUidArray, $spacer, AbstractMenuContentObject $obj);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu;
|
||||
|
||||
/*
|
||||
* 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\Collection\AbstractRecordCollection;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Category\Collection\CategoryCollection;
|
||||
|
||||
/**
|
||||
* Utility class for menus based on category collections of pages.
|
||||
*
|
||||
* Returns all the relevant pages for rendering with a menu content object.
|
||||
* @internal this is only used for internal purposes and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class CategoryMenuUtility
|
||||
{
|
||||
/**
|
||||
* @var string Name of the field used for sorting the pages
|
||||
*/
|
||||
protected static $sortingField;
|
||||
|
||||
/**
|
||||
* Collects all pages for the selected categories, sorted according to configuration.
|
||||
*
|
||||
* @param string $selectedCategories Comma-separated list of system categories primary keys
|
||||
* @param array $configuration TypoScript configuration for the "special." keyword
|
||||
* @param \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject $parentObject Back-reference to the calling object
|
||||
* @return array List of selected pages
|
||||
*/
|
||||
public function collectPages($selectedCategories, $configuration, $parentObject)
|
||||
{
|
||||
$selectedPages = [];
|
||||
$categoriesPerPage = [];
|
||||
// Determine the name of the relation field
|
||||
$relationField = '';
|
||||
if (isset($configuration['relation.'])) {
|
||||
$relationField = $parentObject->parent_cObj->stdWrap(
|
||||
$configuration['relation'],
|
||||
$configuration['relation.']
|
||||
);
|
||||
} elseif (isset($configuration['relation'])) {
|
||||
$relationField = $configuration['relation'];
|
||||
}
|
||||
// Get the pages for each selected category
|
||||
$selectedCategories = GeneralUtility::intExplode(',', $selectedCategories, true);
|
||||
foreach ($selectedCategories as $aCategory) {
|
||||
$collection = CategoryCollection::load(
|
||||
$aCategory,
|
||||
true,
|
||||
'pages',
|
||||
$relationField
|
||||
);
|
||||
$categoryUid = 0;
|
||||
if ($collection instanceof AbstractRecordCollection) {
|
||||
$categoryUid = $collection->getUid();
|
||||
}
|
||||
// Loop on the results, overlay each page record found
|
||||
foreach ($collection as $pageItem) {
|
||||
$parentObject->getSysPage()->versionOL('pages', $pageItem, true);
|
||||
if (is_array($pageItem)) {
|
||||
$selectedPages[$pageItem['uid']] = $parentObject->getSysPage()->getPageOverlay($pageItem);
|
||||
// Keep a list of the categories each page belongs to
|
||||
if (!isset($categoriesPerPage[$pageItem['uid']])) {
|
||||
$categoriesPerPage[$pageItem['uid']] = [];
|
||||
}
|
||||
$categoriesPerPage[$pageItem['uid']][] = $categoryUid;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Loop on the selected pages to add the categories they belong to, as comma-separated list of category uid's)
|
||||
// (this makes them available for rendering, if needed)
|
||||
foreach ($selectedPages as $uid => $pageRecord) {
|
||||
$selectedPages[$uid]['_categories'] = implode(',', $categoriesPerPage[$uid]);
|
||||
}
|
||||
|
||||
// Sort the pages according to the sorting property
|
||||
self::$sortingField = isset($configuration['sorting.']) ? $parentObject->getParentContentObject()->stdWrap($configuration['sorting'], $configuration['sorting.']) : $configuration['sorting'];
|
||||
$order = isset($configuration['order.']) ? $parentObject->getParentContentObject()->stdWrap($configuration['order'], $configuration['order.']) : $configuration['order'];
|
||||
$selectedPages = $this->sortPages($selectedPages, $order);
|
||||
|
||||
return $selectedPages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the selected pages
|
||||
*
|
||||
* If the sorting field is not defined or does not corresponding to an existing field
|
||||
* of the "pages" tables, the list of pages will remain unchanged.
|
||||
*
|
||||
* @param array $pages List of selected pages
|
||||
* @param string $order Order for sorting (should "asc" or "desc")
|
||||
* @return array Sorted list of pages
|
||||
*/
|
||||
protected function sortPages($pages, $order)
|
||||
{
|
||||
// Perform the sorting only if a criterion was actually defined
|
||||
if (!empty(self::$sortingField)) {
|
||||
// Check that the sorting field exists (checking the first record is enough)
|
||||
$firstPage = current($pages);
|
||||
if (isset($firstPage[self::$sortingField])) {
|
||||
// Make sure the order property is either "asc" or "desc" (default is "asc")
|
||||
if (!empty($order)) {
|
||||
$order = strtolower($order);
|
||||
if ($order !== 'desc') {
|
||||
$order = 'asc';
|
||||
}
|
||||
}
|
||||
uasort(
|
||||
$pages,
|
||||
[
|
||||
self::class,
|
||||
'sortPagesUtility'
|
||||
]
|
||||
);
|
||||
// If the sort order is descending, reverse the sorted array
|
||||
if ($order === 'desc') {
|
||||
$pages = array_reverse($pages, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static utility for sorting pages according to the selected criterion
|
||||
*
|
||||
* @param array $pageA Record for first page to be compared
|
||||
* @param array $pageB Record for second page to be compared
|
||||
* @return array -1 if first argument is smaller than second argument, 1 if first is greater than second and 0 if both are equal
|
||||
*/
|
||||
public static function sortPagesUtility($pageA, $pageB)
|
||||
{
|
||||
return strnatcasecmp($pageA[self::$sortingField], $pageB[self::$sortingField]);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu\Exception;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* No such menu type exception
|
||||
*/
|
||||
class NoSuchMenuTypeException extends \TYPO3\CMS\Frontend\Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,569 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu;
|
||||
|
||||
/*
|
||||
* 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\Type\File\ImageInfo;
|
||||
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
|
||||
|
||||
/**
|
||||
* Extension class creating graphic based menus (PNG or GIF files)
|
||||
*
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
class GraphicalMenuContentObject extends AbstractMenuContentObject
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
trigger_error('GMENU and GraphicalMenuContentObject will be removed in TYPO3 v10.0, you should build accessible websites with TMENU/Text, and optional images on top, which can be achieved with TypoScript.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls procesItemStates() so that the common configuration for the menu items are resolved into individual configuration per item.
|
||||
* Calls makeGifs() for all "normal" items and if configured for, also the "rollover" items.
|
||||
*
|
||||
* @see AbstractMenuContentObject::procesItemStates(), makeGifs()
|
||||
*/
|
||||
public function generate()
|
||||
{
|
||||
$splitCount = count($this->menuArr);
|
||||
if ($splitCount) {
|
||||
list($NOconf, $ROconf) = $this->procesItemStates($splitCount);
|
||||
//store initial count value
|
||||
$tsfe = $this->getTypoScriptFrontendController();
|
||||
$temp_HMENU_MENUOBJ = $tsfe->register['count_HMENU_MENUOBJ'];
|
||||
$temp_MENUOBJ = $tsfe->register['count_MENUOBJ'];
|
||||
// Now we generate the giffiles:
|
||||
$this->makeGifs($NOconf, 'NO');
|
||||
// store count from NO obj
|
||||
$tempcnt_HMENU_MENUOBJ = $tsfe->register['count_HMENU_MENUOBJ'];
|
||||
$tempcnt_MENUOBJ = $tsfe->register['count_MENUOBJ'];
|
||||
if ($this->mconf['debugItemConf']) {
|
||||
echo '<h3>$NOconf:</h3>';
|
||||
debug($NOconf);
|
||||
}
|
||||
// RollOver
|
||||
if ($ROconf) {
|
||||
// Start recount for rollover with initial values
|
||||
$tsfe->register['count_HMENU_MENUOBJ'] = $temp_HMENU_MENUOBJ;
|
||||
$tsfe->register['count_MENUOBJ'] = $temp_MENUOBJ;
|
||||
$this->makeGifs($ROconf, 'RO');
|
||||
if ($this->mconf['debugItemConf']) {
|
||||
echo '<h3>$ROconf:</h3>';
|
||||
debug($ROconf);
|
||||
}
|
||||
}
|
||||
// Use count from NO obj
|
||||
$tsfe->register['count_HMENU_MENUOBJ'] = $tempcnt_HMENU_MENUOBJ;
|
||||
$tsfe->register['count_MENUOBJ'] = $tempcnt_MENUOBJ;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will traverse input array with configuration per-item and create corresponding GIF files for the menu.
|
||||
* The data of the files are stored in $this->result
|
||||
*
|
||||
* @param array $conf Array with configuration for each item.
|
||||
* @param string $resKey Type of images: normal ("NO") or rollover ("RO"). Valid values are "NO" and "RO
|
||||
* @internal
|
||||
* @see generate()
|
||||
*/
|
||||
public function makeGifs($conf, $resKey)
|
||||
{
|
||||
$isGD = $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'];
|
||||
if (!is_array($conf)) {
|
||||
$conf = [];
|
||||
}
|
||||
$totalWH = [];
|
||||
$items = count($conf);
|
||||
$minDim = 0;
|
||||
$maxDim = 0;
|
||||
$Hcounter = 0;
|
||||
$Wcounter = 0;
|
||||
$Hobjs = [];
|
||||
$Wobjs = [];
|
||||
if ($isGD) {
|
||||
// Generate the gif-files. the $menuArr is filled with some values like output_w, output_h, output_file
|
||||
$Hobjs = $this->mconf['applyTotalH'];
|
||||
if ($Hobjs) {
|
||||
$Hobjs = GeneralUtility::intExplode(',', $Hobjs);
|
||||
}
|
||||
$Wobjs = $this->mconf['applyTotalW'];
|
||||
if ($Wobjs) {
|
||||
$Wobjs = GeneralUtility::intExplode(',', $Wobjs);
|
||||
}
|
||||
$minDim = $this->mconf['min'];
|
||||
if ($minDim) {
|
||||
$minDim = $this->calcIntExplode($minDim . ',');
|
||||
}
|
||||
$maxDim = $this->mconf['max'];
|
||||
if ($maxDim) {
|
||||
$maxDim = $this->calcIntExplode($maxDim . ',');
|
||||
}
|
||||
if ($minDim) {
|
||||
$conf[$items] = $conf[$items - 1];
|
||||
$this->menuArr[$items] = [];
|
||||
$items = count($conf);
|
||||
}
|
||||
// TOTAL width
|
||||
if ($this->mconf['useLargestItemX'] || $this->mconf['useLargestItemY'] || $this->mconf['distributeX'] || $this->mconf['distributeY']) {
|
||||
$totalWH = $this->findLargestDims($conf, $items, $Hobjs, $Wobjs, $minDim, $maxDim);
|
||||
}
|
||||
}
|
||||
$c = 0;
|
||||
$maxFlag = 0;
|
||||
$distributeAccu = ['H' => 0, 'W' => 0];
|
||||
foreach ($conf as $key => $val) {
|
||||
$this->getTypoScriptFrontendController()->register['count_HMENU_MENUOBJ']++;
|
||||
$this->getTypoScriptFrontendController()->register['count_MENUOBJ']++;
|
||||
if ($items === $c + 1 && $minDim) {
|
||||
$Lobjs = $this->mconf['removeObjectsOfDummy'];
|
||||
if ($Lobjs) {
|
||||
$Lobjs = GeneralUtility::intExplode(',', $Lobjs);
|
||||
foreach ($Lobjs as $remItem) {
|
||||
unset($val[$remItem]);
|
||||
unset($val[$remItem . '.']);
|
||||
}
|
||||
}
|
||||
$flag = 0;
|
||||
$tempXY = explode(',', $val['XY']);
|
||||
if ($Wcounter < $minDim[0]) {
|
||||
$tempXY[0] = $minDim[0] - $Wcounter;
|
||||
$flag = 1;
|
||||
}
|
||||
if ($Hcounter < $minDim[1]) {
|
||||
$tempXY[1] = $minDim[1] - $Hcounter;
|
||||
$flag = 1;
|
||||
}
|
||||
$val['XY'] = implode(',', $tempXY);
|
||||
if (!$flag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$c++;
|
||||
$gifCreator = null;
|
||||
if ($isGD) {
|
||||
// Pre-working the item
|
||||
$gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
|
||||
$gifCreator->start($val, $this->menuArr[$key]);
|
||||
// If useLargestItemH/W is specified
|
||||
if (!empty($totalWH) && ($this->mconf['useLargestItemX'] || $this->mconf['useLargestItemY'])) {
|
||||
$tempXY = explode(',', $gifCreator->setup['XY']);
|
||||
if ($this->mconf['useLargestItemX']) {
|
||||
$tempXY[0] = max($totalWH['W']);
|
||||
}
|
||||
if ($this->mconf['useLargestItemY']) {
|
||||
$tempXY[1] = max($totalWH['H']);
|
||||
}
|
||||
// Regenerate the new values...
|
||||
$val['XY'] = implode(',', $tempXY);
|
||||
$gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
|
||||
$gifCreator->start($val, $this->menuArr[$key]);
|
||||
}
|
||||
// If distributeH/W is specified
|
||||
if (!empty($totalWH) && ($this->mconf['distributeX'] || $this->mconf['distributeY'])) {
|
||||
$tempXY = explode(',', $gifCreator->setup['XY']);
|
||||
if ($this->mconf['distributeX']) {
|
||||
$diff = $this->mconf['distributeX'] - $totalWH['W_total'] - $distributeAccu['W'];
|
||||
$compensate = round($diff / ($items - $c + 1));
|
||||
$distributeAccu['W'] += $compensate;
|
||||
$tempXY[0] = $totalWH['W'][$key] + $compensate;
|
||||
}
|
||||
if ($this->mconf['distributeY']) {
|
||||
$diff = $this->mconf['distributeY'] - $totalWH['H_total'] - $distributeAccu['H'];
|
||||
$compensate = round($diff / ($items - $c + 1));
|
||||
$distributeAccu['H'] += $compensate;
|
||||
$tempXY[1] = $totalWH['H'][$key] + $compensate;
|
||||
}
|
||||
// Regenerate the new values...
|
||||
$val['XY'] = implode(',', $tempXY);
|
||||
$gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
|
||||
$gifCreator->start($val, $this->menuArr[$key]);
|
||||
}
|
||||
// If max dimensions are specified
|
||||
if ($maxDim) {
|
||||
$tempXY = explode(',', $val['XY']);
|
||||
if ($maxDim[0] && $Wcounter + $gifCreator->XY[0] >= $maxDim[0]) {
|
||||
$tempXY[0] = $maxDim[0] - $Wcounter;
|
||||
$maxFlag = 1;
|
||||
}
|
||||
if ($maxDim[1] && $Hcounter + $gifCreator->XY[1] >= $maxDim[1]) {
|
||||
$tempXY[1] = $maxDim[1] - $Hcounter;
|
||||
$maxFlag = 1;
|
||||
}
|
||||
if ($maxFlag) {
|
||||
$val['XY'] = implode(',', $tempXY);
|
||||
$gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
|
||||
$gifCreator->start($val, $this->menuArr[$key]);
|
||||
}
|
||||
}
|
||||
// displace
|
||||
if ($Hobjs) {
|
||||
foreach ($Hobjs as $index) {
|
||||
if ($gifCreator->setup[$index] && $gifCreator->setup[$index . '.']) {
|
||||
$oldOffset = explode(',', $gifCreator->setup[$index . '.']['offset']);
|
||||
$gifCreator->setup[$index . '.']['offset'] = implode(',', $gifCreator->applyOffset($oldOffset, [0, -$Hcounter]));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($Wobjs) {
|
||||
foreach ($Wobjs as $index) {
|
||||
if ($gifCreator->setup[$index] && $gifCreator->setup[$index . '.']) {
|
||||
$oldOffset = explode(',', $gifCreator->setup[$index . '.']['offset']);
|
||||
$gifCreator->setup[$index . '.']['offset'] = implode(',', $gifCreator->applyOffset($oldOffset, [-$Wcounter, 0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finding alternative GIF names if any (by altImgResource)
|
||||
$gifFileName = '';
|
||||
if ($conf[$key]['altImgResource'] || is_array($conf[$key]['altImgResource.'])) {
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$cObj->start($this->menuArr[$key], 'pages');
|
||||
$altImgInfo = $cObj->getImgResource($conf[$key]['altImgResource'], $conf[$key]['altImgResource.']);
|
||||
$gifFileName = $altImgInfo[3];
|
||||
}
|
||||
// If an alternative name was NOT given, find the GIFBUILDER name.
|
||||
if (!$gifFileName && $isGD) {
|
||||
GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/menu/');
|
||||
$gifFileName = $gifCreator->fileName('assets/menu/');
|
||||
}
|
||||
$this->result[$resKey][$key] = $conf[$key];
|
||||
// Generation of image file:
|
||||
// File exists
|
||||
if (file_exists($gifFileName)) {
|
||||
$imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $gifFileName);
|
||||
$this->result[$resKey][$key]['output_w'] = (int)$imageInfo->getWidth();
|
||||
$this->result[$resKey][$key]['output_h'] = (int)$imageInfo->getHeight();
|
||||
$this->result[$resKey][$key]['output_file'] = $gifFileName;
|
||||
} elseif ($isGD) {
|
||||
// file is generated
|
||||
$gifCreator->make();
|
||||
$this->result[$resKey][$key]['output_w'] = $gifCreator->w;
|
||||
$this->result[$resKey][$key]['output_h'] = $gifCreator->h;
|
||||
$this->result[$resKey][$key]['output_file'] = $gifFileName;
|
||||
$gifCreator->output($this->result[$resKey][$key]['output_file']);
|
||||
$gifCreator->destroy();
|
||||
}
|
||||
// counter is increased
|
||||
$Hcounter += $this->result[$resKey][$key]['output_h'];
|
||||
// counter is increased
|
||||
$Wcounter += $this->result[$resKey][$key]['output_w'];
|
||||
if ($maxFlag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function searching for the largest width and height of the menu items to be generated.
|
||||
* Uses some of the same code as makeGifs and even instantiates some gifbuilder objects BUT does not render the images - only reading out which width they would have.
|
||||
* Remember to upgrade the code in here if the makeGifs function is updated.
|
||||
*
|
||||
* @param array $conf Same configuration array as passed to makeGifs()
|
||||
* @param int $items The number of menu items
|
||||
* @param array $Hobjs Array with "applyTotalH" numbers (unused)
|
||||
* @param array $Wobjs Array with "applyTotalW" numbers (unused)
|
||||
* @param array $minDim Array with "min" x/y
|
||||
* @param array $maxDim Array with "max" x/y
|
||||
* @return array Array with keys "H" and "W" which are in themselves arrays with the heights and widths of menu items inside. This can be used to find the max/min size of the menu items.
|
||||
* @internal
|
||||
* @see makeGifs()
|
||||
*/
|
||||
public function findLargestDims($conf, $items, $Hobjs, $Wobjs, $minDim, $maxDim)
|
||||
{
|
||||
$items = (int)$items;
|
||||
$totalWH = [
|
||||
'W' => [],
|
||||
'H' => [],
|
||||
'W_total' => 0,
|
||||
'H_total' => 0
|
||||
];
|
||||
$Hcounter = 0;
|
||||
$Wcounter = 0;
|
||||
$c = 0;
|
||||
$maxFlag = 0;
|
||||
foreach ($conf as $key => $val) {
|
||||
// SAME CODE AS makeGifs()! BEGIN
|
||||
if ($items === $c + 1 && $minDim) {
|
||||
$Lobjs = $this->mconf['removeObjectsOfDummy'];
|
||||
if ($Lobjs) {
|
||||
$Lobjs = GeneralUtility::intExplode(',', $Lobjs);
|
||||
foreach ($Lobjs as $remItem) {
|
||||
unset($val[$remItem]);
|
||||
unset($val[$remItem . '.']);
|
||||
}
|
||||
}
|
||||
$flag = 0;
|
||||
$tempXY = explode(',', $val['XY']);
|
||||
if ($Wcounter < $minDim[0]) {
|
||||
$tempXY[0] = $minDim[0] - $Wcounter;
|
||||
$flag = 1;
|
||||
}
|
||||
if ($Hcounter < $minDim[1]) {
|
||||
$tempXY[1] = $minDim[1] - $Hcounter;
|
||||
$flag = 1;
|
||||
}
|
||||
$val['XY'] = implode(',', $tempXY);
|
||||
if (!$flag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$c++;
|
||||
$gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
|
||||
$gifCreator->start($val, $this->menuArr[$key]);
|
||||
if ($maxDim) {
|
||||
$tempXY = explode(',', $val['XY']);
|
||||
if ($maxDim[0] && $Wcounter + $gifCreator->XY[0] >= $maxDim[0]) {
|
||||
$tempXY[0] = $maxDim[0] - $Wcounter;
|
||||
$maxFlag = 1;
|
||||
}
|
||||
if ($maxDim[1] && $Hcounter + $gifCreator->XY[1] >= $maxDim[1]) {
|
||||
$tempXY[1] = $maxDim[1] - $Hcounter;
|
||||
$maxFlag = 1;
|
||||
}
|
||||
if ($maxFlag) {
|
||||
$val['XY'] = implode(',', $tempXY);
|
||||
$gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
|
||||
$gifCreator->start($val, $this->menuArr[$key]);
|
||||
}
|
||||
}
|
||||
// SAME CODE AS makeGifs()! END
|
||||
// Setting the width/height
|
||||
$totalWH['W'][$key] = $gifCreator->XY[0];
|
||||
$totalWH['H'][$key] = $gifCreator->XY[1];
|
||||
$totalWH['W_total'] += $gifCreator->XY[0];
|
||||
$totalWH['H_total'] += $gifCreator->XY[1];
|
||||
// counter is increased
|
||||
$Hcounter += $gifCreator->XY[1];
|
||||
// counter is increased
|
||||
$Wcounter += $gifCreator->XY[0];
|
||||
if ($maxFlag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $totalWH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the ->result['NO'] array of menu items configuration (made by ->generate()) and renders the HTML of each item (the images themselves was made with makeGifs() before this. See ->generate())
|
||||
* During the execution of this function many internal methods prefixed "extProc_" from this class is called and many of these are for now dummy functions. But they can be used for processing as they are used by the GMENU_LAYERS
|
||||
*
|
||||
* @return string The HTML for the menu (returns result through $this->extProc_finish(); )
|
||||
*/
|
||||
public function writeMenu()
|
||||
{
|
||||
if (!is_array($this->menuArr) || empty($this->result) || !is_array($this->result['NO'])) {
|
||||
return '';
|
||||
}
|
||||
$this->WMresult = '';
|
||||
$this->INPfixMD5 = substr(md5(microtime() . $this->GMENU_fixKey), 0, 4);
|
||||
$this->WMmenuItems = count($this->result['NO']);
|
||||
$typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
|
||||
$this->WMsubmenuObjSuffixes = $typoScriptService->explodeConfigurationForOptionSplit(['sOSuffix' => $this->mconf['submenuObjSuffixes']], $this->WMmenuItems);
|
||||
$this->extProc_init();
|
||||
$tsfe = $this->getTypoScriptFrontendController();
|
||||
if (!isset($tsfe->additionalJavaScript['JSImgCode'])) {
|
||||
$tsfe->additionalJavaScript['JSImgCode'] = '';
|
||||
}
|
||||
for ($key = 0; $key < $this->WMmenuItems; $key++) {
|
||||
if ($this->result['NO'][$key]['output_file']) {
|
||||
// Initialize the cObj with the page record of the menu item
|
||||
$this->WMcObj->start($this->menuArr[$key], 'pages');
|
||||
$this->I = [];
|
||||
$this->I['key'] = $key;
|
||||
$this->I['INPfix'] = ($this->imgNameNotRandom ? '' : '_' . $this->INPfixMD5) . '_' . $key;
|
||||
$this->I['val'] = $this->result['NO'][$key];
|
||||
$this->I['title'] = $this->getPageTitle($this->menuArr[$key]['title'], $this->menuArr[$key]['nav_title']);
|
||||
$this->I['uid'] = $this->menuArr[$key]['uid'];
|
||||
$this->I['mount_pid'] = $this->menuArr[$key]['mount_pid'];
|
||||
$this->I['pid'] = $this->menuArr[$key]['pid'];
|
||||
$this->I['spacer'] = $this->menuArr[$key]['isSpacer'];
|
||||
if (!$this->I['uid'] && !$this->menuArr[$key]['_OVERRIDE_HREF']) {
|
||||
$this->I['spacer'] = 1;
|
||||
}
|
||||
$this->I['noLink'] = $this->I['spacer'] || $this->I['val']['noLink'] || empty($this->menuArr[$key]);
|
||||
// !count($this->menuArr[$key]) means that this item is a dummyItem
|
||||
$this->I['name'] = '';
|
||||
// Set access key
|
||||
if ($this->mconf['accessKey']) {
|
||||
$this->I['accessKey'] = $this->accessKey($this->I['title']);
|
||||
} else {
|
||||
$this->I['accessKey'] = [];
|
||||
}
|
||||
// Make link tag
|
||||
$this->I['val']['ATagParams'] = $this->WMcObj->getATagParams($this->I['val']);
|
||||
if (isset($this->I['val']['additionalParams.'])) {
|
||||
$this->I['val']['additionalParams'] = $this->WMcObj->stdWrap($this->I['val']['additionalParams'], $this->I['val']['additionalParams.']);
|
||||
}
|
||||
$this->I['linkHREF'] = $this->link($key, $this->I['val']['altTarget'], $this->mconf['forceTypeValue']);
|
||||
// Title attribute of links:
|
||||
$titleAttrValue = isset($this->I['val']['ATagTitle.']) ? $this->WMcObj->stdWrap($this->I['val']['ATagTitle'], $this->I['val']['ATagTitle.']) . $this->I['accessKey']['alt'] : $this->I['val']['ATagTitle'] . $this->I['accessKey']['alt'];
|
||||
if ($titleAttrValue !== '') {
|
||||
$this->I['linkHREF']['title'] = $titleAttrValue;
|
||||
}
|
||||
// Set rollover
|
||||
if ($this->result['RO'][$key] && !$this->I['noLink']) {
|
||||
$this->I['theName'] = $this->imgNamePrefix . $this->I['uid'] . $this->I['INPfix'];
|
||||
$this->I['name'] = ' ' . $this->nameAttribute . '="' . $this->I['theName'] . '"';
|
||||
$this->I['linkHREF']['onMouseover'] = $this->WMfreezePrefix . 'over(' . GeneralUtility::quoteJSvalue($this->I['theName']) . ');';
|
||||
$this->I['linkHREF']['onMouseout'] = $this->WMfreezePrefix . 'out(' . GeneralUtility::quoteJSvalue($this->I['theName']) . ');';
|
||||
$tsfe->additionalJavaScript['JSImgCode'] .= LF . $this->I['theName'] . '_n=new Image(); ' . $this->I['theName'] . '_n.src = ' . GeneralUtility::quoteJSvalue($tsfe->absRefPrefix . $this->I['val']['output_file']) . '; ';
|
||||
$tsfe->additionalJavaScript['JSImgCode'] .= LF . $this->I['theName'] . '_h=new Image(); ' . $this->I['theName'] . '_h.src = ' . GeneralUtility::quoteJSvalue($tsfe->absRefPrefix . $this->result['RO'][$key]['output_file']) . '; ';
|
||||
$tsfe->imagesOnPage[] = $this->result['RO'][$key]['output_file'];
|
||||
$tsfe->setJS('mouseOver');
|
||||
$this->extProc_RO($key);
|
||||
}
|
||||
// Set altText
|
||||
$this->I['altText'] = $this->I['title'] . $this->I['accessKey']['alt'];
|
||||
// Calling extra processing function
|
||||
$this->extProc_beforeLinking($key);
|
||||
// Set linking
|
||||
if (!$this->I['noLink']) {
|
||||
$this->setATagParts();
|
||||
} else {
|
||||
$this->I['A1'] = '';
|
||||
$this->I['A2'] = '';
|
||||
}
|
||||
$this->I['IMG'] = '<img src="' . $tsfe->absRefPrefix . $this->I['val']['output_file'] . '" width="' . $this->I['val']['output_w'] . '" height="' . $this->I['val']['output_h'] . '" ' . $this->parent_cObj->getBorderAttr('border="0"') . ($this->mconf['disableAltText'] ? '' : ' alt="' . htmlspecialchars($this->I['altText']) . '"') . $this->I['name'] . ($this->I['val']['imgParams'] ? ' ' . $this->I['val']['imgParams'] : '') . ' />';
|
||||
// Make before, middle and after parts
|
||||
$this->I['parts'] = [];
|
||||
$this->I['parts']['ATag_begin'] = $this->I['A1'];
|
||||
$this->I['parts']['image'] = $this->I['IMG'];
|
||||
$this->I['parts']['ATag_end'] = $this->I['A2'];
|
||||
// Passing I to a user function
|
||||
if ($this->mconf['IProcFunc']) {
|
||||
$this->I = $this->userProcess('IProcFunc', $this->I);
|
||||
}
|
||||
// Putting the item together.
|
||||
// Merge parts + beforeAllWrap
|
||||
$this->I['theItem'] = implode('', $this->I['parts']);
|
||||
$this->I['theItem'] = $this->extProc_beforeAllWrap($this->I['theItem'], $key);
|
||||
// wrap:
|
||||
$this->I['theItem'] = $this->WMcObj->wrap($this->I['theItem'], $this->I['val']['wrap']);
|
||||
// allWrap:
|
||||
$allWrap = isset($this->I['val']['allWrap.']) ? $this->WMcObj->stdWrap($this->I['val']['allWrap'], $this->I['val']['allWrap.']) : $this->I['val']['allWrap'];
|
||||
$this->I['theItem'] = $this->WMcObj->wrap($this->I['theItem'], $allWrap);
|
||||
if ($this->I['val']['subst_elementUid']) {
|
||||
$this->I['theItem'] = str_replace('{elementUid}', $this->I['uid'], $this->I['theItem']);
|
||||
}
|
||||
// allStdWrap:
|
||||
if (is_array($this->I['val']['allStdWrap.'])) {
|
||||
$this->I['theItem'] = $this->WMcObj->stdWrap($this->I['theItem'], $this->I['val']['allStdWrap.']);
|
||||
}
|
||||
$tsfe->imagesOnPage[] = $this->I['val']['output_file'];
|
||||
$this->extProc_afterLinking($key);
|
||||
}
|
||||
}
|
||||
return $this->extProc_finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called right before the traversing of $this->result begins.
|
||||
* Can be used for various initialization
|
||||
*
|
||||
* @see writeMenu()
|
||||
*/
|
||||
protected function extProc_init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after all processing for RollOver of an element has been done.
|
||||
*
|
||||
* @param int $key Pointer to $this->menuArr[$key] where the current menu element record is found OR $this->result['RO'][$key] where the configuration for that elements RO version is found!
|
||||
* @see writeMenu()
|
||||
*/
|
||||
protected function extProc_RO($key)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called right before the creation of the link for the menu item
|
||||
*
|
||||
* @param int $key Pointer to $this->menuArr[$key] where the current menu element record is found
|
||||
* @see writeMenu()
|
||||
*/
|
||||
protected function extProc_beforeLinking($key)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called right after the creation of links for the menu item. This is also the last function call before the
|
||||
* for-loop traversing menu items goes to the next item.
|
||||
* This function MUST set $this->WMresult.=[HTML for menu item] to add the generated menu item to the internal accumulation of items.
|
||||
* Further this calls the subMenu function in the parent class to create any submenu there might be.
|
||||
*
|
||||
* @param int $key Pointer to $this->menuArr[$key] where the current menu element record is found
|
||||
* @see writeMenu(), AbstractMenuContentObject::subMenu()
|
||||
*/
|
||||
protected function extProc_afterLinking($key)
|
||||
{
|
||||
// Add part to the accumulated result + fetch submenus
|
||||
if (!$this->I['spacer']) {
|
||||
$this->I['theItem'] .= $this->subMenu($this->I['uid'], $this->WMsubmenuObjSuffixes[$key]['sOSuffix']);
|
||||
}
|
||||
$part = isset($this->I['val']['wrapItemAndSub.']) ? $this->WMcObj->stdWrap($this->I['val']['wrapItemAndSub'], $this->I['val']['wrapItemAndSub.']) : $this->I['val']['wrapItemAndSub'];
|
||||
$this->WMresult .= $part ? $this->WMcObj->wrap($this->I['theItem'], $part) : $this->I['theItem'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before the "wrap" happens on the menu item.
|
||||
*
|
||||
* @param string $item The current content of the menu item, $this->I['theItem'], passed along.
|
||||
* @param int $key Pointer to $this->menuArr[$key] where the current menu element record is found (unused)
|
||||
* @return string The modified version of $item, going back into $this->I['theItem']
|
||||
* @see writeMenu()
|
||||
*/
|
||||
protected function extProc_beforeAllWrap($item, $key)
|
||||
{
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before the writeMenu() function returns (only if a menu was generated)
|
||||
*
|
||||
* @return string The total menu content should be returned by this function
|
||||
* @see writeMenu()
|
||||
*/
|
||||
protected function extProc_finish()
|
||||
{
|
||||
// stdWrap:
|
||||
if (is_array($this->mconf['stdWrap.'])) {
|
||||
$this->WMresult = $this->WMcObj->stdWrap($this->WMresult, $this->mconf['stdWrap.']);
|
||||
}
|
||||
return $this->WMcObj->wrap($this->WMresult, $this->mconf['wrap']) . $this->WMextraScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* This explodes a comma-list into an array where the values are parsed through ContentObjectRender::calc() and cast to (int)(so you are sure to have integers in the output array)
|
||||
* Used to split and calculate min and max values for GMENUs.
|
||||
*
|
||||
* @param string $string The string with parts in (where each part is evaluated by ->calc())
|
||||
* @return array And array with evaluated values.
|
||||
* @see ContentObjectRenderer::calc(), makeGifs()
|
||||
*/
|
||||
protected function calcIntExplode($string)
|
||||
{
|
||||
$temp = explode(',', $string);
|
||||
foreach ($temp as $key => $val) {
|
||||
$temp[$key] = (int)$this->parent_cObj->calc($val);
|
||||
}
|
||||
return $temp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu;
|
||||
|
||||
/*
|
||||
* 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\Type\File\ImageInfo;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
|
||||
|
||||
/**
|
||||
* ImageMap based menus
|
||||
*
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
class ImageMenuContentObject extends AbstractMenuContentObject
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
trigger_error('IMGMENU and ImageMenuContentObject will be removed in TYPO3 v10.0, you should build accessible websites with TMENU/Text, and optional images on top, which can be achieved with TypoScript.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls procesItemStates() so that the common configuration for the menu items are resolved into individual configuration per item.
|
||||
* Calls makeImageMap() to generate the image map image-file
|
||||
*
|
||||
* @see AbstractMenuContentObject::procesItemStates(), makeImageMap()
|
||||
*/
|
||||
public function generate()
|
||||
{
|
||||
$NOconf = [];
|
||||
$splitCount = count($this->menuArr);
|
||||
if ($splitCount) {
|
||||
list($NOconf) = $this->procesItemStates($splitCount);
|
||||
}
|
||||
if (!empty($this->mconf['debugItemConf'])) {
|
||||
echo '<h3>$NOconf:</h3>';
|
||||
debug($NOconf);
|
||||
}
|
||||
$this->makeImageMap($NOconf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will traverse input array with configuration per-item and create corresponding GIF files for the menu.
|
||||
* The data of the files are stored in $this->result
|
||||
*
|
||||
* @param array $conf Array with configuration for each item.
|
||||
* @internal
|
||||
* @see generate()
|
||||
*/
|
||||
public function makeImageMap($conf)
|
||||
{
|
||||
if (!is_array($conf)) {
|
||||
$conf = [];
|
||||
}
|
||||
if (is_array($this->mconf['main.'])) {
|
||||
$gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
|
||||
$itemsConf = $conf;
|
||||
$conf = $this->mconf['main.'];
|
||||
if (is_array($conf)) {
|
||||
$sKeyArray = ArrayUtility::filterAndSortByNumericKeys($conf);
|
||||
$gifObjCount = (int)end($sKeyArray);
|
||||
// Now we add graphical objects to the gifbuilder-setup
|
||||
$waArr = [];
|
||||
foreach ($itemsConf as $key => $val) {
|
||||
if (is_array($val)) {
|
||||
$gifObjCount++;
|
||||
$waArr[$key]['free'] = $gifObjCount;
|
||||
$sKeyArray = ArrayUtility::filterAndSortByNumericKeys($val);
|
||||
foreach ($sKeyArray as $theKey) {
|
||||
$theValue = $val[$theKey];
|
||||
if ((int)$theKey && ($theValArr = $val[$theKey . '.'])) {
|
||||
$cObjData = $this->menuArr[$key] ?: [];
|
||||
$gifObjCount++;
|
||||
if ($theValue === 'TEXT') {
|
||||
$waArr[$key]['textNum'] = $gifObjCount;
|
||||
$gifCreator->data = $cObjData;
|
||||
$theValArr = $gifCreator->checkTextObj($theValArr);
|
||||
// if this is not done it seems that imageMaps will be rendered wrong!!
|
||||
unset($theValArr['text.']);
|
||||
// check links
|
||||
$LD = $this->menuTypoLink($this->menuArr[$key], $this->mconf['target'], '', '', [], '', $this->mconf['forceTypeValue']);
|
||||
// If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
|
||||
$this->changeLinksForAccessRestrictedPages($LD, $this->menuArr[$key], $this->mconf['target'], $this->mconf['forceTypeValue']);
|
||||
// Overriding URL / Target if set to do so:
|
||||
if ($this->menuArr[$key]['_OVERRIDE_HREF']) {
|
||||
$LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
|
||||
if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
|
||||
$LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
|
||||
}
|
||||
}
|
||||
// Setting target/url for Image Map:
|
||||
if ($theValArr['imgMap.']['url'] === '') {
|
||||
$theValArr['imgMap.']['url'] = $LD['totalURL'];
|
||||
}
|
||||
if ($theValArr['imgMap.']['target'] === '') {
|
||||
$theValArr['imgMap.']['target'] = $LD['target'];
|
||||
}
|
||||
if (is_array($theValArr['imgMap.']['altText.'])) {
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$cObj->start($cObjData, 'pages');
|
||||
if (isset($theValArr['imgMap.']['altText.'])) {
|
||||
$theValArr['imgMap.']['altText'] = $cObj->stdWrap($theValArr['imgMap.']['altText'], $theValArr['imgMap.']['altText.']);
|
||||
}
|
||||
unset($theValArr['imgMap.']['altText.']);
|
||||
}
|
||||
if (is_array($theValArr['imgMap.']['titleText.'])) {
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$cObj->start($cObjData, 'pages');
|
||||
if (isset($theValArr['imgMap.']['titleText.'])) {
|
||||
$theValArr['imgMap.']['titleText'] = $cObj->stdWrap($theValArr['imgMap.']['titleText'], $theValArr['imgMap.']['titleText.']);
|
||||
}
|
||||
unset($theValArr['imgMap.']['titleText.']);
|
||||
}
|
||||
}
|
||||
// This code goes one level in if the object is an image. If 'file' and/or 'mask' appears to be GIFBUILDER-objects, they are both searched for TEXT objects, and if a textobj is found, it's checked with the currently loaded record!!
|
||||
if ($theValue === 'IMAGE') {
|
||||
if ($theValArr['file'] === 'GIFBUILDER') {
|
||||
$temp_sKeyArray = ArrayUtility::filterAndSortByNumericKeys($theValArr['file.']);
|
||||
foreach ($temp_sKeyArray as $temp_theKey) {
|
||||
if ($theValArr['mask.'][$temp_theKey] === 'TEXT') {
|
||||
$gifCreator->data = $this->menuArr[$key] ?: [];
|
||||
$theValArr['mask.'][$temp_theKey . '.'] = $gifCreator->checkTextObj($theValArr['mask.'][$temp_theKey . '.']);
|
||||
// If this is not done it seems that imageMaps will be rendered wrong!!
|
||||
unset($theValArr['mask.'][$temp_theKey . '.']['text.']);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($theValArr['mask'] === 'GIFBUILDER') {
|
||||
$temp_sKeyArray = ArrayUtility::filterAndSortByNumericKeys($theValArr['mask.']);
|
||||
foreach ($temp_sKeyArray as $temp_theKey) {
|
||||
if ($theValArr['mask.'][$temp_theKey] === 'TEXT') {
|
||||
$gifCreator->data = $this->menuArr[$key] ?: [];
|
||||
$theValArr['mask.'][$temp_theKey . '.'] = $gifCreator->checkTextObj($theValArr['mask.'][$temp_theKey . '.']);
|
||||
// if this is not done it seems that imageMaps will be rendered wrong!!
|
||||
unset($theValArr['mask.'][$temp_theKey . '.']['text.']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Checks if disabled is set...
|
||||
$setObjFlag = 1;
|
||||
if ($theValArr['if.']) {
|
||||
/** @var ContentObjectRenderer $cObj */
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$cObj->start($cObjData, 'pages');
|
||||
if (!empty($theValArr['if.']) && !$cObj->checkIf($theValArr['if.'])) {
|
||||
$setObjFlag = 0;
|
||||
}
|
||||
unset($theValArr['if.']);
|
||||
}
|
||||
// Set the object!
|
||||
if ($setObjFlag) {
|
||||
$conf[$gifObjCount] = $theValue;
|
||||
$conf[$gifObjCount . '.'] = $theValArr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$gifCreator->start($conf, $this->getTypoScriptFrontendController()->page);
|
||||
// calculations
|
||||
$dConf = [];
|
||||
foreach ($waArr as $key => $val) {
|
||||
if ($dConf[$key] = $itemsConf[$key]['distrib']) {
|
||||
$textBB = $gifCreator->objBB[$val['textNum']];
|
||||
$dConf[$key] = str_replace(
|
||||
['textX', 'textY'],
|
||||
[$textBB[0], $textBB[1]],
|
||||
$dConf[$key]
|
||||
);
|
||||
$dConf[$key] = GeneralUtility::intExplode(',', $gifCreator->calcOffset($dConf[$key]));
|
||||
}
|
||||
}
|
||||
$workArea = GeneralUtility::intExplode(',', $gifCreator->calcOffset($this->mconf['dWorkArea']));
|
||||
foreach ($waArr as $key => $val) {
|
||||
$index = $val['free'];
|
||||
$gifCreator->setup[$index] = 'WORKAREA';
|
||||
$workArea[2] = $dConf[$key][2] ?: $dConf[$key][0];
|
||||
$workArea[3] = $dConf[$key][3] ?: $dConf[$key][1];
|
||||
$gifCreator->setup[$index . '.']['set'] = implode(',', $workArea);
|
||||
$workArea[0] += $dConf[$key][0];
|
||||
$workArea[1] += $dConf[$key][1];
|
||||
}
|
||||
if ($this->mconf['debugRenumberedObject']) {
|
||||
echo '<h3>Renumbered GIFBUILDER object:</h3>';
|
||||
debug($gifCreator->setup);
|
||||
}
|
||||
GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/menu/');
|
||||
$gifFileName = $gifCreator->fileName('assets/menu/');
|
||||
// Gets the ImageMap from the cache...
|
||||
$cache = $this->getCache();
|
||||
$imgHash = md5($gifFileName);
|
||||
$imgMap = $cache->get($imgHash);
|
||||
// File exists
|
||||
if ($imgMap && file_exists($gifFileName)) {
|
||||
$imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $gifFileName);
|
||||
$w = $imageInfo->getWidth();
|
||||
$h = $imageInfo->getHeight();
|
||||
} else {
|
||||
// file is generated
|
||||
$gifCreator->make();
|
||||
$w = $gifCreator->w;
|
||||
$h = $gifCreator->h;
|
||||
$gifCreator->output($gifFileName);
|
||||
$gifCreator->destroy();
|
||||
$imgMap = $gifCreator->map;
|
||||
$cache->set($imgHash, $imgMap, ['ident_MENUIMAGEMAP'], 0);
|
||||
}
|
||||
$imgMap .= $this->mconf['imgMapExtras'];
|
||||
$this->result = ['output_file' => $gifFileName, 'output_w' => $w, 'output_h' => $h, 'imgMap' => $imgMap];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML for the image map menu.
|
||||
* If ->result is TRUE it will create the HTML for the image map menu.
|
||||
*
|
||||
* @return string The HTML for the menu
|
||||
*/
|
||||
public function writeMenu()
|
||||
{
|
||||
if ($this->result) {
|
||||
$res = $this->result;
|
||||
// shortMD5 260900
|
||||
$menuName = 'menu_' . GeneralUtility::shortMD5($res['imgMap']);
|
||||
$result = '<img src="' . $this->getTypoScriptFrontendController()->absRefPrefix . $res['output_file'] . '" width="' . $res['output_w'] . '" height="' . $res['output_h'] . '" usemap="#' . $menuName . '" border="0" ' . $this->mconf['params'];
|
||||
// Adding alt attribute if not set.
|
||||
if (!strstr($result, 'alt="')) {
|
||||
$result .= ' alt="Menu Image Map"';
|
||||
}
|
||||
$result .= ' /><map name="' . $menuName . '" id="' . $menuName . '">' . $res['imgMap'] . '</map>';
|
||||
$this->getTypoScriptFrontendController()->imagesOnPage[] = $res['output_file'];
|
||||
return $this->WMcObj->wrap($result, $this->mconf['wrap']);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu;
|
||||
|
||||
/*
|
||||
* 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\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Factory for menu content objects. Allows overriding the default
|
||||
* types like 'GMENU' with an own implementation (only one possible)
|
||||
* and new types can be registered.
|
||||
* @internal this is only used for internal purposes and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class MenuContentObjectFactory implements SingletonInterface
|
||||
{
|
||||
/**
|
||||
* Register of TypoScript keys to according render class
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $menuTypeToClassMapping = [
|
||||
'GMENU' => GraphicalMenuContentObject::class,
|
||||
'TMENU' => TextMenuContentObject::class,
|
||||
'IMGMENU' => ImageMenuContentObject::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets a typo script string like 'TMENU' and returns an object of this type
|
||||
*
|
||||
* @param string $type
|
||||
* @return AbstractMenuContentObject Menu object
|
||||
* @throws Exception\NoSuchMenuTypeException
|
||||
*/
|
||||
public function getMenuObjectByType($type = '')
|
||||
{
|
||||
$upperCasedClassName = strtoupper($type);
|
||||
if (array_key_exists($upperCasedClassName, $this->menuTypeToClassMapping)) {
|
||||
$object = GeneralUtility::makeInstance($this->menuTypeToClassMapping[$upperCasedClassName]);
|
||||
} else {
|
||||
throw new Exception\NoSuchMenuTypeException(
|
||||
'Menu type ' . (string)$type . ' has no implementing class.',
|
||||
1363278130
|
||||
);
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new menu type or override existing type
|
||||
*
|
||||
* @param string $type Menu type to be used in TypoScript
|
||||
* @param string $className Class rendering the menu
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function registerMenuType($type, $className)
|
||||
{
|
||||
if (!is_string($type) || !is_string($className)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'type and className must be strings',
|
||||
1363429303
|
||||
);
|
||||
}
|
||||
$this->menuTypeToClassMapping[strtoupper($type)] = $className;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu;
|
||||
|
||||
/*
|
||||
* 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\TypoScript\TypoScriptService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Extension class creating text based menus
|
||||
*/
|
||||
class TextMenuContentObject extends AbstractMenuContentObject
|
||||
{
|
||||
/**
|
||||
* Calls procesItemStates() so that the common configuration for the menu items are resolved into individual configuration per item.
|
||||
* Sets the result for the new "normal state" in $this->result
|
||||
*
|
||||
* @see AbstractMenuContentObject::procesItemStates()
|
||||
*/
|
||||
public function generate()
|
||||
{
|
||||
$NOconf = [];
|
||||
$splitCount = count($this->menuArr);
|
||||
if ($splitCount) {
|
||||
list($NOconf) = $this->procesItemStates($splitCount);
|
||||
}
|
||||
if (!empty($this->mconf['debugItemConf'])) {
|
||||
echo '<h3>$NOconf:</h3>';
|
||||
debug($NOconf);
|
||||
}
|
||||
$this->result = $NOconf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the ->result array of menu items configuration (made by ->generate()) and renders each item.
|
||||
* During the execution of this function many internal methods prefixed "extProc_" from this class is called and
|
||||
* many of these are for now dummy functions.
|
||||
* An instance of ContentObjectRenderer is also made and for each menu item rendered it is loaded with
|
||||
* the record for that page so that any stdWrap properties that applies will have the current menu items record available.
|
||||
*
|
||||
* @return string The HTML for the menu (returns result through $this->extProc_finish(); )
|
||||
*/
|
||||
public function writeMenu()
|
||||
{
|
||||
if (empty($this->result)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->WMresult = '';
|
||||
$this->INPfixMD5 = substr(md5(microtime() . 'tmenu'), 0, 4);
|
||||
$this->WMmenuItems = count($this->result);
|
||||
$typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
|
||||
$this->WMsubmenuObjSuffixes = $typoScriptService->explodeConfigurationForOptionSplit(['sOSuffix' => $this->mconf['submenuObjSuffixes']], $this->WMmenuItems);
|
||||
$this->extProc_init();
|
||||
foreach ($this->result as $key => $val) {
|
||||
$GLOBALS['TSFE']->register['count_HMENU_MENUOBJ']++;
|
||||
$GLOBALS['TSFE']->register['count_MENUOBJ']++;
|
||||
// Initialize the cObj with the page record of the menu item
|
||||
$this->WMcObj->start($this->menuArr[$key], 'pages');
|
||||
$this->I = [];
|
||||
$this->I['key'] = $key;
|
||||
$this->I['INPfix'] = ($this->imgNameNotRandom ? '' : '_' . $this->INPfixMD5) . '_' . $key;
|
||||
$this->I['val'] = $val;
|
||||
$this->I['title'] = isset($this->I['val']['stdWrap.']) ? $this->WMcObj->stdWrap($this->getPageTitle($this->menuArr[$key]['title'], $this->menuArr[$key]['nav_title']), $this->I['val']['stdWrap.']) : $this->getPageTitle($this->menuArr[$key]['title'], $this->menuArr[$key]['nav_title']);
|
||||
$this->I['uid'] = $this->menuArr[$key]['uid'];
|
||||
$this->I['mount_pid'] = $this->menuArr[$key]['mount_pid'];
|
||||
$this->I['pid'] = $this->menuArr[$key]['pid'];
|
||||
$this->I['spacer'] = $this->menuArr[$key]['isSpacer'];
|
||||
// Set access key
|
||||
if ($this->mconf['accessKey']) {
|
||||
$this->I['accessKey'] = $this->accessKey($this->I['title']);
|
||||
} else {
|
||||
$this->I['accessKey'] = [];
|
||||
}
|
||||
// Make link tag
|
||||
$this->I['val']['ATagParams'] = $this->WMcObj->getATagParams($this->I['val']);
|
||||
if (isset($this->I['val']['additionalParams.'])) {
|
||||
$this->I['val']['additionalParams'] = $this->WMcObj->stdWrap($this->I['val']['additionalParams'], $this->I['val']['additionalParams.']);
|
||||
}
|
||||
$this->I['linkHREF'] = $this->link($key, $this->I['val']['altTarget'], $this->mconf['forceTypeValue']);
|
||||
if (empty($this->I['linkHREF'])) {
|
||||
$this->I['val']['doNotLinkIt'] = 1;
|
||||
}
|
||||
// Title attribute of links:
|
||||
$titleAttrValue = isset($this->I['val']['ATagTitle.']) ? $this->WMcObj->stdWrap($this->I['val']['ATagTitle'], $this->I['val']['ATagTitle.']) . $this->I['accessKey']['alt'] : $this->I['val']['ATagTitle'] . $this->I['accessKey']['alt'];
|
||||
if ($titleAttrValue !== '') {
|
||||
$this->I['linkHREF']['title'] = $titleAttrValue;
|
||||
}
|
||||
|
||||
// Calling extra processing function
|
||||
$this->extProc_beforeLinking($key);
|
||||
// stdWrap for doNotLinkIt
|
||||
if (isset($this->I['val']['doNotLinkIt.'])) {
|
||||
$this->I['val']['doNotLinkIt'] = $this->WMcObj->stdWrap($this->I['val']['doNotLinkIt'], $this->I['val']['doNotLinkIt.']);
|
||||
}
|
||||
// Compile link tag
|
||||
if (!$this->I['val']['doNotLinkIt']) {
|
||||
$this->I['val']['doNotLinkIt'] = 0;
|
||||
}
|
||||
if (!$this->I['spacer'] && $this->I['val']['doNotLinkIt'] != 1) {
|
||||
$this->setATagParts();
|
||||
} else {
|
||||
$this->I['A1'] = '';
|
||||
$this->I['A2'] = '';
|
||||
}
|
||||
// ATagBeforeWrap processing:
|
||||
if ($this->I['val']['ATagBeforeWrap']) {
|
||||
$wrapPartsBefore = explode('|', $this->I['val']['linkWrap']);
|
||||
$wrapPartsAfter = ['', ''];
|
||||
} else {
|
||||
$wrapPartsBefore = ['', ''];
|
||||
$wrapPartsAfter = explode('|', $this->I['val']['linkWrap']);
|
||||
}
|
||||
if ($this->I['val']['stdWrap2'] || isset($this->I['val']['stdWrap2.'])) {
|
||||
$stdWrap2 = isset($this->I['val']['stdWrap2.']) ? $this->WMcObj->stdWrap('|', $this->I['val']['stdWrap2.']) : '|';
|
||||
$wrapPartsStdWrap = explode($this->I['val']['stdWrap2'] ? $this->I['val']['stdWrap2'] : '|', $stdWrap2);
|
||||
} else {
|
||||
$wrapPartsStdWrap = ['', ''];
|
||||
}
|
||||
// Make before, middle and after parts
|
||||
$this->I['parts'] = [];
|
||||
$this->I['parts']['before'] = $this->getBeforeAfter('before');
|
||||
$this->I['parts']['stdWrap2_begin'] = $wrapPartsStdWrap[0];
|
||||
// stdWrap for doNotShowLink
|
||||
if (isset($this->I['val']['doNotShowLink.'])) {
|
||||
$this->I['val']['doNotShowLink'] = $this->WMcObj->stdWrap($this->I['val']['doNotShowLink'], $this->I['val']['doNotShowLink.']);
|
||||
}
|
||||
if (!$this->I['val']['doNotShowLink']) {
|
||||
$this->I['parts']['notATagBeforeWrap_begin'] = $wrapPartsAfter[0];
|
||||
$this->I['parts']['ATag_begin'] = $this->I['A1'];
|
||||
$this->I['parts']['ATagBeforeWrap_begin'] = $wrapPartsBefore[0];
|
||||
$this->I['parts']['title'] = $this->I['title'];
|
||||
$this->I['parts']['ATagBeforeWrap_end'] = $wrapPartsBefore[1];
|
||||
$this->I['parts']['ATag_end'] = $this->I['A2'];
|
||||
$this->I['parts']['notATagBeforeWrap_end'] = $wrapPartsAfter[1];
|
||||
}
|
||||
$this->I['parts']['stdWrap2_end'] = $wrapPartsStdWrap[1];
|
||||
$this->I['parts']['after'] = $this->getBeforeAfter('after');
|
||||
// Passing I to a user function
|
||||
if ($this->mconf['IProcFunc']) {
|
||||
$this->I = $this->userProcess('IProcFunc', $this->I);
|
||||
}
|
||||
// Merge parts + beforeAllWrap
|
||||
$this->I['theItem'] = implode('', $this->I['parts']);
|
||||
$this->I['theItem'] = $this->extProc_beforeAllWrap($this->I['theItem'], $key);
|
||||
// allWrap:
|
||||
$allWrap = isset($this->I['val']['allWrap.']) ? $this->WMcObj->stdWrap($this->I['val']['allWrap'], $this->I['val']['allWrap.']) : $this->I['val']['allWrap'];
|
||||
$this->I['theItem'] = $this->WMcObj->wrap($this->I['theItem'], $allWrap);
|
||||
if ($this->I['val']['subst_elementUid']) {
|
||||
$this->I['theItem'] = str_replace('{elementUid}', $this->I['uid'], $this->I['theItem']);
|
||||
}
|
||||
// allStdWrap:
|
||||
if (is_array($this->I['val']['allStdWrap.'])) {
|
||||
$this->I['theItem'] = $this->WMcObj->stdWrap($this->I['theItem'], $this->I['val']['allStdWrap.']);
|
||||
}
|
||||
// Calling extra processing function
|
||||
$this->extProc_afterLinking($key);
|
||||
}
|
||||
return $this->extProc_finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the before* and after* images for TMENUs
|
||||
*
|
||||
* @param string $pref Can be "before" or "after" and determines which kind of image to create (basically this is the prefix of the TypoScript properties that are read from the ->I['val'] array
|
||||
* @return string The resulting HTML of the image, if any.
|
||||
*/
|
||||
protected function getBeforeAfter($pref)
|
||||
{
|
||||
$res = '';
|
||||
if ($imgInfo = $this->WMcObj->getImgResource($this->I['val'][$pref . 'Img'], $this->I['val'][$pref . 'Img.'])) {
|
||||
$theName = $this->imgNamePrefix . $this->I['uid'] . $this->I['INPfix'] . $pref;
|
||||
$name = ' ' . $this->nameAttribute . '="' . $theName . '"';
|
||||
$GLOBALS['TSFE']->imagesOnPage[] = $imgInfo[3];
|
||||
$res = '<img' . ' src="' . $GLOBALS['TSFE']->absRefPrefix . $imgInfo[3] . '"' . ' width="' . $imgInfo[0] . '"' . ' height="' . $imgInfo[1] . '"' . $name . ($this->I['val'][$pref . 'ImgTagParams'] ? ' ' . $this->I['val'][$pref . 'ImgTagParams'] : '') . $this->parent_cObj->getBorderAttr(' border="0"');
|
||||
if (!strstr($res, 'alt="')) {
|
||||
// Adding alt attribute if not set.
|
||||
$res .= ' alt=""';
|
||||
}
|
||||
$res .= ' />';
|
||||
if ($this->I['val'][$pref . 'ImgLink']) {
|
||||
$res = $this->I['A1'] . $res . $this->I['A2'];
|
||||
}
|
||||
}
|
||||
$processedPref = isset($this->I['val'][$pref . '.']) ? $this->WMcObj->stdWrap($this->I['val'][$pref], $this->I['val'][$pref . '.']) : $this->I['val'][$pref];
|
||||
if (isset($this->I['val'][$pref . 'Wrap'])) {
|
||||
return $this->WMcObj->wrap($res . $processedPref, $this->I['val'][$pref . 'Wrap']);
|
||||
}
|
||||
return $res . $processedPref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called right before the traversing of $this->result begins.
|
||||
* Can be used for various initialization
|
||||
*
|
||||
* @see writeMenu()
|
||||
*/
|
||||
protected function extProc_init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called right before the creation of the link for the menu item
|
||||
*
|
||||
* @param int $key Pointer to $this->menuArr[$key] where the current menu element record is found
|
||||
* @see writeMenu()
|
||||
*/
|
||||
protected function extProc_beforeLinking($key)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called right after the creation of links for the menu item. This is also the last function call before the while-loop traversing menu items goes to the next item.
|
||||
* This function MUST set $this->WMresult.=[HTML for menu item] to add the generated menu item to the internal accumulation of items.
|
||||
*
|
||||
* @param int $key Pointer to $this->menuArr[$key] where the current menu element record is found
|
||||
* @see writeMenu()
|
||||
*/
|
||||
protected function extProc_afterLinking($key)
|
||||
{
|
||||
// Add part to the accumulated result + fetch submenus
|
||||
if (!$this->I['spacer']) {
|
||||
$this->I['theItem'] .= $this->subMenu($this->I['uid'], $this->WMsubmenuObjSuffixes[$key]['sOSuffix']);
|
||||
}
|
||||
$part = isset($this->I['val']['wrapItemAndSub.']) ? $this->WMcObj->stdWrap($this->I['val']['wrapItemAndSub'], $this->I['val']['wrapItemAndSub.']) : $this->I['val']['wrapItemAndSub'];
|
||||
$this->WMresult .= $part ? $this->WMcObj->wrap($this->I['theItem'], $part) : $this->I['theItem'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before the "allWrap" happens on the menu item.
|
||||
*
|
||||
* @param string $item The current content of the menu item, $this->I['theItem'], passed along.
|
||||
* @param int $key Pointer to $this->menuArr[$key] where the current menu element record is found
|
||||
* @return string The modified version of $item, going back into $this->I['theItem']
|
||||
* @see writeMenu()
|
||||
*/
|
||||
protected function extProc_beforeAllWrap($item, $key)
|
||||
{
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before the writeMenu() function returns (only if a menu was generated)
|
||||
*
|
||||
* @return string The total menu content should be returned by this function
|
||||
* @see writeMenu()
|
||||
*/
|
||||
protected function extProc_finish()
|
||||
{
|
||||
// stdWrap:
|
||||
if (is_array($this->mconf['stdWrap.'])) {
|
||||
$this->WMresult = $this->WMcObj->stdWrap($this->WMresult, $this->mconf['stdWrap.']);
|
||||
}
|
||||
return $this->WMcObj->wrap($this->WMresult, $this->mconf['wrap']) . $this->WMextraScript;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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\RelationHandler;
|
||||
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Category\Collection\CategoryCollection;
|
||||
use TYPO3\CMS\Frontend\Page\PageRepository;
|
||||
|
||||
/**
|
||||
* Contains RECORDS class object.
|
||||
*/
|
||||
class RecordsContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* List of all items with table and uid information
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $itemArray = [];
|
||||
|
||||
/**
|
||||
* List of all selected records with full data, arranged per table
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* Rendering the cObject, RECORDS
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
// Reset items and data
|
||||
$this->itemArray = [];
|
||||
$this->data = [];
|
||||
|
||||
$theValue = '';
|
||||
$originalRec = $GLOBALS['TSFE']->currentRecord;
|
||||
// If the currentRecord is set, we register, that this record has invoked this function.
|
||||
// It's should not be allowed to do this again then!!
|
||||
if ($originalRec) {
|
||||
++$GLOBALS['TSFE']->recordRegister[$originalRec];
|
||||
}
|
||||
|
||||
$tables = isset($conf['tables.']) ? $this->cObj->stdWrap($conf['tables'], $conf['tables.']) : $conf['tables'];
|
||||
if ($tables) {
|
||||
$tablesArray = array_unique(GeneralUtility::trimExplode(',', $tables, true));
|
||||
// Add tables which have a configuration (note that this may create duplicate entries)
|
||||
if (is_array($conf['conf.'])) {
|
||||
foreach ($conf['conf.'] as $key => $value) {
|
||||
if (substr($key, -1) !== '.' && !in_array($key, $tablesArray)) {
|
||||
$tablesArray[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the data, depending on collection method.
|
||||
// Property "source" is considered more precise and thus takes precedence over "categories"
|
||||
$source = isset($conf['source.']) ? $this->cObj->stdWrap($conf['source'], $conf['source.']) : $conf['source'];
|
||||
$categories = isset($conf['categories.']) ? $this->cObj->stdWrap($conf['categories'], $conf['categories.']) : $conf['categories'];
|
||||
if ($source) {
|
||||
$this->collectRecordsFromSource($source, $tablesArray);
|
||||
} elseif ($categories) {
|
||||
$relationField = isset($conf['categories.']['relation.']) ? $this->cObj->stdWrap($conf['categories.']['relation'], $conf['categories.']['relation.']) : $conf['categories.']['relation'];
|
||||
$this->collectRecordsFromCategories($categories, $tablesArray, $relationField);
|
||||
}
|
||||
$itemArrayCount = count($this->itemArray);
|
||||
if ($itemArrayCount > 0) {
|
||||
/** @var ContentObjectRenderer $cObj */
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$cObj->setParent($this->cObj->data, $this->cObj->currentRecord);
|
||||
$this->cObj->currentRecordNumber = 0;
|
||||
$this->cObj->currentRecordTotal = $itemArrayCount;
|
||||
foreach ($this->itemArray as $val) {
|
||||
$row = $this->data[$val['table']][$val['id']];
|
||||
// Perform overlays if necessary (records coming from category collections are already overlaid)
|
||||
if ($source) {
|
||||
// Versioning preview
|
||||
$this->getPageRepository()->versionOL($val['table'], $row);
|
||||
// Language overlay
|
||||
if (is_array($row)) {
|
||||
$row = $this->getPageRepository()->getLanguageOverlay($val['table'], $row);
|
||||
}
|
||||
}
|
||||
// Might be unset during the overlay process
|
||||
if (is_array($row)) {
|
||||
$dontCheckPid = isset($conf['dontCheckPid.']) ? $this->cObj->stdWrap($conf['dontCheckPid'], $conf['dontCheckPid.']) : $conf['dontCheckPid'];
|
||||
if (!$dontCheckPid) {
|
||||
$row = $this->cObj->checkPid($row['pid']) ? $row : '';
|
||||
}
|
||||
if ($row && !$GLOBALS['TSFE']->recordRegister[$val['table'] . ':' . $val['id']]) {
|
||||
$renderObjName = $conf['conf.'][$val['table']] ?: '<' . $val['table'];
|
||||
$renderObjKey = $conf['conf.'][$val['table']] ? 'conf.' . $val['table'] : '';
|
||||
$renderObjConf = $conf['conf.'][$val['table'] . '.'];
|
||||
$this->cObj->currentRecordNumber++;
|
||||
$cObj->parentRecordNumber = $this->cObj->currentRecordNumber;
|
||||
$GLOBALS['TSFE']->currentRecord = $val['table'] . ':' . $val['id'];
|
||||
$this->cObj->lastChanged($row['tstamp']);
|
||||
$cObj->start($row, $val['table']);
|
||||
$tmpValue = $cObj->cObjGetSingle($renderObjName, $renderObjConf, $renderObjKey);
|
||||
$theValue .= $tmpValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$wrap = isset($conf['wrap.']) ? $this->cObj->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
|
||||
if ($wrap) {
|
||||
$theValue = $this->cObj->wrap($theValue, $wrap);
|
||||
}
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']);
|
||||
}
|
||||
// Restore
|
||||
$GLOBALS['TSFE']->currentRecord = $originalRec;
|
||||
if ($originalRec) {
|
||||
--$GLOBALS['TSFE']->recordRegister[$originalRec];
|
||||
}
|
||||
return $theValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects records according to the configured source
|
||||
*
|
||||
* @param string $source Source of records
|
||||
* @param array $tables List of tables
|
||||
*/
|
||||
protected function collectRecordsFromSource($source, array $tables)
|
||||
{
|
||||
/** @var RelationHandler $loadDB*/
|
||||
$loadDB = GeneralUtility::makeInstance(RelationHandler::class);
|
||||
$loadDB->setFetchAllFields(true);
|
||||
$loadDB->start($source, implode(',', $tables));
|
||||
foreach ($loadDB->tableArray as $table => $v) {
|
||||
if (isset($GLOBALS['TCA'][$table])) {
|
||||
$loadDB->additionalWhere[$table] = $this->getPageRepository()->enableFields($table);
|
||||
}
|
||||
}
|
||||
$this->data = $loadDB->getFromDB();
|
||||
reset($loadDB->itemArray);
|
||||
$this->itemArray = $loadDB->itemArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects records for all selected tables and categories.
|
||||
*
|
||||
* @param string $selectedCategories Comma-separated list of categories
|
||||
* @param array $tables List of tables
|
||||
* @param string $relationField Name of the field containing the categories relation
|
||||
*/
|
||||
protected function collectRecordsFromCategories($selectedCategories, array $tables, $relationField)
|
||||
{
|
||||
$selectedCategories = array_unique(GeneralUtility::intExplode(',', $selectedCategories, true));
|
||||
|
||||
// Loop on all selected tables
|
||||
foreach ($tables as $table) {
|
||||
|
||||
// Get the records for each selected category
|
||||
$tableRecords = [];
|
||||
$categoriesPerRecord = [];
|
||||
foreach ($selectedCategories as $aCategory) {
|
||||
try {
|
||||
$collection = CategoryCollection::load(
|
||||
$aCategory,
|
||||
true,
|
||||
$table,
|
||||
$relationField
|
||||
);
|
||||
if ($collection->count() > 0) {
|
||||
// Add items to the collection of records for the current table
|
||||
foreach ($collection as $item) {
|
||||
$tableRecords[$item['uid']] = $item;
|
||||
// Keep track of all categories a given item belongs to
|
||||
if (!isset($categoriesPerRecord[$item['uid']])) {
|
||||
$categoriesPerRecord[$item['uid']] = [];
|
||||
}
|
||||
$categoriesPerRecord[$item['uid']][] = $aCategory;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$message = sprintf(
|
||||
'Could not get records for category id %d. Error: %s (%d)',
|
||||
$aCategory,
|
||||
$e->getMessage(),
|
||||
$e->getCode()
|
||||
);
|
||||
$this->getTimeTracker()->setTSlogMessage($message, 2);
|
||||
}
|
||||
}
|
||||
// Store the resulting records into the itemArray and data results array
|
||||
if (!empty($tableRecords)) {
|
||||
$this->data[$table] = [];
|
||||
foreach ($tableRecords as $record) {
|
||||
$this->itemArray[] = [
|
||||
'id' => $record['uid'],
|
||||
'table' => $table
|
||||
];
|
||||
// Add to the record the categories it belongs to
|
||||
$record['_categories'] = implode(',', $categoriesPerRecord[$record['uid']]);
|
||||
$this->data[$table][$record['uid']] = $record;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TimeTracker
|
||||
*/
|
||||
protected function getTimeTracker()
|
||||
{
|
||||
return GeneralUtility::makeInstance(TimeTracker::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PageRepository
|
||||
*/
|
||||
protected function getPageRepository(): PageRepository
|
||||
{
|
||||
return $GLOBALS['TSFE']->sys_page ?: GeneralUtility::makeInstance(PageRepository::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains RESTORE_REGISTER class object.
|
||||
*/
|
||||
class RestoreRegisterContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, RESTORE_REGISTER
|
||||
* NOTICE: This cObject does NOT return any content since it just sets internal data based on the TypoScript properties.
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Empty string (the cObject only sets internal data!)
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
$GLOBALS['TSFE']->register = array_pop($GLOBALS['TSFE']->registerStack);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
|
||||
/**
|
||||
* Contains SVG content object.
|
||||
*/
|
||||
class ScalableVectorGraphicsContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, SVG
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string
|
||||
*/
|
||||
public function render($conf = []): string
|
||||
{
|
||||
$renderMode = isset($conf['renderMode.'])
|
||||
? $this->cObj->stdWrap($conf['renderMode'], $conf['renderMode.'])
|
||||
: $conf['renderMode'];
|
||||
|
||||
if ($renderMode === 'inline') {
|
||||
return $this->renderInline($conf);
|
||||
}
|
||||
|
||||
return $this->renderObject($conf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $conf
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function renderInline(array $conf): string
|
||||
{
|
||||
$src = $this->resolveAbsoluteSourcePath($conf);
|
||||
list($width, $height, $isDefaultWidth, $isDefaultHeight) = $this->getDimensions($conf);
|
||||
|
||||
$content = '';
|
||||
if (file_exists($src)) {
|
||||
$svgContent = file_get_contents($src);
|
||||
$svgContent = preg_replace('/<script[\s\S]*?>[\s\S]*?<\/script>/i', '', $svgContent);
|
||||
// Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
|
||||
$previousValueOfEntityLoader = libxml_disable_entity_loader();
|
||||
$svgElement = simplexml_load_string($svgContent);
|
||||
libxml_disable_entity_loader($previousValueOfEntityLoader);
|
||||
|
||||
$domXml = dom_import_simplexml($svgElement);
|
||||
if (!$isDefaultWidth) {
|
||||
$domXml->setAttribute('width', $width);
|
||||
}
|
||||
if (!$isDefaultHeight) {
|
||||
$domXml->setAttribute('height', $height);
|
||||
}
|
||||
// remove xml version tag
|
||||
$content = $domXml->ownerDocument->saveXML($domXml->ownerDocument->documentElement);
|
||||
} else {
|
||||
$value = isset($conf['value.']) ? $this->cObj->stdWrap($conf['value'], $conf['value.']) : $conf['value'];
|
||||
if (!empty($value)) {
|
||||
$content = [];
|
||||
$content[] = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' . (int)$width . '" height="' . (int)$height . '">';
|
||||
$content[] = $value;
|
||||
$content[] = '</svg>';
|
||||
$content = implode(LF, $content);
|
||||
}
|
||||
}
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the SVG as <object> tag
|
||||
* @param array $conf
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function renderObject(array $conf): string
|
||||
{
|
||||
$src = $this->resolveAbsoluteSourcePath($conf);
|
||||
list($width, $height) = $this->getDimensions($conf);
|
||||
|
||||
$src = $src === '' ? null : PathUtility::getAbsoluteWebPath($src);
|
||||
|
||||
$value = isset($conf['value.']) ? $this->cObj->stdWrap($conf['value'], $conf['value.']) : $conf['value'];
|
||||
$noscript = isset($conf['noscript.']) ? $this->cObj->stdWrap($conf['noscript'], $conf['noscript.']) : $conf['noscript'];
|
||||
|
||||
// @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0 - see method usages
|
||||
if (!empty($conf['noscript.']) || !empty($conf['noscript'])) {
|
||||
trigger_error('SVG cObject: The option "noscript" will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
|
||||
}
|
||||
if (!empty($conf['value.']) || !empty($conf['value'])) {
|
||||
trigger_error('SVG cObject: The option "value" without setting renderMode=inline will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$content = [];
|
||||
if ($src) {
|
||||
$content[] = '<!--[if IE]>';
|
||||
$content[] = ' <object src="' . htmlspecialchars($src) . '" classid="image/svg+xml" width="' . (int)$width . '" height="' . (int)$height . '">';
|
||||
$content[] = '<![endif]-->';
|
||||
$content[] = '<!--[if !IE]>-->';
|
||||
$content[] = ' <object data="' . htmlspecialchars($src) . '" type="image/svg+xml" width="' . (int)$width . '" height="' . (int)$height . '">';
|
||||
$content[] = '<!--<![endif]-->';
|
||||
$content[] = $noscript;
|
||||
$content[] = '</object>';
|
||||
} else {
|
||||
// @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0 - see method usages
|
||||
$content[] = '<script type="image/svg+xml">';
|
||||
$content[] = ' <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' . (int)$width . '" height="' . (int)$height . '">';
|
||||
$content[] = $value;
|
||||
$content[] = ' </svg>';
|
||||
$content[] = '</script>';
|
||||
$content[] = '<noscript>';
|
||||
$content[] = $noscript;
|
||||
$content[] = '</noscript>';
|
||||
}
|
||||
$content = implode(LF, $content);
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $conf
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveAbsoluteSourcePath(array $conf): string
|
||||
{
|
||||
$src = isset($conf['src.']) ? $this->cObj->stdWrap($conf['src'], $conf['src.']) : $conf['src'];
|
||||
return GeneralUtility::getFileAbsFileName($src);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $conf
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDimensions(array $conf): array
|
||||
{
|
||||
$isDefaultWidth = false;
|
||||
$isDefaultHeight = false;
|
||||
$width = isset($conf['width.']) ? $this->cObj->stdWrap($conf['width'], $conf['width.']) : $conf['width'];
|
||||
$height = isset($conf['height.']) ? $this->cObj->stdWrap($conf['height'], $conf['height.']) : $conf['height'];
|
||||
|
||||
if (empty($width)) {
|
||||
$isDefaultWidth = true;
|
||||
$width = 600;
|
||||
}
|
||||
if (empty($height)) {
|
||||
$isDefaultHeight = true;
|
||||
$height = 400;
|
||||
}
|
||||
|
||||
return [$width, $height, $isDefaultWidth, $isDefaultHeight];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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\Html\HtmlParser;
|
||||
use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Contains TEMPLATE class object.
|
||||
*/
|
||||
class TemplateContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* @var MarkerBasedTemplateService
|
||||
*/
|
||||
protected $templateService;
|
||||
|
||||
/**
|
||||
* Default constructor, which also instantiates the MarkerBasedTemplateService.
|
||||
*
|
||||
* @param ContentObjectRenderer $cObj
|
||||
*/
|
||||
public function __construct(ContentObjectRenderer $cObj)
|
||||
{
|
||||
$this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
|
||||
parent::__construct($cObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendering the cObject, TEMPLATE
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
* @see substituteMarkerArrayCached()
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
$subparts = [];
|
||||
$marks = [];
|
||||
$wraps = [];
|
||||
$markerWrap = isset($conf['markerWrap.']) ? $this->cObj->stdWrap($conf['markerWrap'], $conf['markerWrap.']) : $conf['markerWrap'];
|
||||
if (!$markerWrap) {
|
||||
$markerWrap = '### | ###';
|
||||
}
|
||||
list($PRE, $POST) = explode('|', $markerWrap);
|
||||
$POST = trim($POST);
|
||||
$PRE = trim($PRE);
|
||||
// Getting the content
|
||||
$content = $this->cObj->cObjGetSingle($conf['template'], $conf['template.'], 'template');
|
||||
$workOnSubpart = isset($conf['workOnSubpart.']) ? $this->cObj->stdWrap($conf['workOnSubpart'], $conf['workOnSubpart.']) : $conf['workOnSubpart'];
|
||||
if ($workOnSubpart) {
|
||||
$content = $this->templateService->getSubpart($content, $PRE . $workOnSubpart . $POST);
|
||||
}
|
||||
// Fixing all relative paths found:
|
||||
if ($conf['relPathPrefix']) {
|
||||
$htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
|
||||
$content = $htmlParser->prefixResourcePath($conf['relPathPrefix'], $content, $conf['relPathPrefix.']);
|
||||
}
|
||||
if ($content) {
|
||||
$nonCachedSubst = isset($conf['nonCachedSubst.']) ? $this->cObj->stdWrap($conf['nonCachedSubst'], $conf['nonCachedSubst.']) : $conf['nonCachedSubst'];
|
||||
// NON-CACHED:
|
||||
if ($nonCachedSubst) {
|
||||
// Getting marks
|
||||
if (is_array($conf['marks.'])) {
|
||||
foreach ($conf['marks.'] as $theKey => $theValue) {
|
||||
if (!strstr($theKey, '.')) {
|
||||
$content = str_replace($PRE . $theKey . $POST, $this->cObj->cObjGetSingle($theValue, $conf['marks.'][$theKey . '.'], 'marks.' . $theKey), $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Getting subparts.
|
||||
if (is_array($conf['subparts.'])) {
|
||||
foreach ($conf['subparts.'] as $theKey => $theValue) {
|
||||
if (!strstr($theKey, '.')) {
|
||||
$subpart = $this->templateService->getSubpart($content, $PRE . $theKey . $POST);
|
||||
if ($subpart) {
|
||||
$this->cObj->setCurrentVal($subpart);
|
||||
$content = $this->templateService->substituteSubpart($content, $PRE . $theKey . $POST, $this->cObj->cObjGetSingle($theValue, $conf['subparts.'][$theKey . '.'], 'subparts.' . $theKey), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Getting subpart wraps
|
||||
if (is_array($conf['wraps.'])) {
|
||||
foreach ($conf['wraps.'] as $theKey => $theValue) {
|
||||
if (!strstr($theKey, '.')) {
|
||||
$subpart = $this->templateService->getSubpart($content, $PRE . $theKey . $POST);
|
||||
if ($subpart) {
|
||||
$this->cObj->setCurrentVal($subpart);
|
||||
$content = $this->templateService->substituteSubpart($content, $PRE . $theKey . $POST, explode('|', $this->cObj->cObjGetSingle($theValue, $conf['wraps.'][$theKey . '.'], 'wraps.' . $theKey)), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// CACHED
|
||||
// Getting subparts.
|
||||
if (is_array($conf['subparts.'])) {
|
||||
foreach ($conf['subparts.'] as $theKey => $theValue) {
|
||||
if (!strstr($theKey, '.')) {
|
||||
$subpart = $this->templateService->getSubpart($content, $PRE . $theKey . $POST);
|
||||
if ($subpart) {
|
||||
$GLOBALS['TSFE']->register['SUBPART_' . $theKey] = $subpart;
|
||||
$subparts[$theKey]['name'] = $theValue;
|
||||
$subparts[$theKey]['conf'] = $conf['subparts.'][$theKey . '.'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Getting marks
|
||||
if (is_array($conf['marks.'])) {
|
||||
foreach ($conf['marks.'] as $theKey => $theValue) {
|
||||
if (!strstr($theKey, '.')) {
|
||||
$marks[$theKey]['name'] = $theValue;
|
||||
$marks[$theKey]['conf'] = $conf['marks.'][$theKey . '.'];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Getting subpart wraps
|
||||
if (is_array($conf['wraps.'])) {
|
||||
foreach ($conf['wraps.'] as $theKey => $theValue) {
|
||||
if (!strstr($theKey, '.')) {
|
||||
$wraps[$theKey]['name'] = $theValue;
|
||||
$wraps[$theKey]['conf'] = $conf['wraps.'][$theKey . '.'];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Getting subparts
|
||||
$subpartArray = [];
|
||||
foreach ($subparts as $theKey => $theValue) {
|
||||
// Set current with the content of the subpart...
|
||||
$this->cObj->data[$this->cObj->currentValKey] = $GLOBALS['TSFE']->register['SUBPART_' . $theKey];
|
||||
// Get subpart cObject and substitute it!
|
||||
$subpartArray[$PRE . $theKey . $POST] = $this->cObj->cObjGetSingle($theValue['name'], $theValue['conf'], 'subparts.' . $theKey);
|
||||
}
|
||||
// Reset current to empty
|
||||
$this->cObj->data[$this->cObj->currentValKey] = '';
|
||||
// Getting marks
|
||||
$markerArray = [];
|
||||
foreach ($marks as $theKey => $theValue) {
|
||||
$markerArray[$PRE . $theKey . $POST] = $this->cObj->cObjGetSingle($theValue['name'], $theValue['conf'], 'marks.' . $theKey);
|
||||
}
|
||||
// Getting wraps
|
||||
$subpartWraps = [];
|
||||
foreach ($wraps as $theKey => $theValue) {
|
||||
$subpartWraps[$PRE . $theKey . $POST] = explode('|', $this->cObj->cObjGetSingle($theValue['name'], $theValue['conf'], 'wraps.' . $theKey));
|
||||
}
|
||||
// Substitution
|
||||
$substMarksSeparately = isset($conf['substMarksSeparately.']) ? $this->cObj->stdWrap($conf['substMarksSeparately'], $conf['substMarksSeparately.']) : $conf['substMarksSeparately'];
|
||||
if ($substMarksSeparately) {
|
||||
$content = $this->templateService->substituteMarkerArrayCached($content, [], $subpartArray, $subpartWraps);
|
||||
$content = $this->templateService->substituteMarkerArray($content, $markerArray);
|
||||
} else {
|
||||
$content = $this->templateService->substituteMarkerArrayCached($content, $markerArray, $subpartArray, $subpartWraps);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains TEXT class object.
|
||||
*/
|
||||
class TextContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, TEXT
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
if (!is_array($conf)) {
|
||||
return '';
|
||||
}
|
||||
$content = '';
|
||||
if (isset($conf['value'])) {
|
||||
$content = $conf['value'];
|
||||
unset($conf['value']);
|
||||
}
|
||||
if (isset($conf['value.'])) {
|
||||
$content = $this->cObj->stdWrap($content, $conf['value.']);
|
||||
unset($conf['value.']);
|
||||
}
|
||||
if (!empty($conf)) {
|
||||
$content = $this->cObj->stdWrap($content, $conf);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* interface for classes which hook into \TYPO3\CMS\Frontend\ContentObjectRenderer and wish to modify the typolink
|
||||
* configuration of the page link.
|
||||
*/
|
||||
interface TypolinkModifyLinkConfigForPageLinksHookInterface
|
||||
{
|
||||
/**
|
||||
* Modifies the typolink page link configuration array.
|
||||
*
|
||||
* @param array $linkConfiguration The link configuration (for options see TSRef -> typolink)
|
||||
* @param array $linkDetails Additional information for the link
|
||||
* @param array $pageRow The complete page row for the page to link to
|
||||
*
|
||||
* @return array The modified $linkConfiguration
|
||||
*/
|
||||
public function modifyPageLinkConfiguration(array $linkConfiguration, array $linkDetails, array $pageRow): array;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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\TimeTracker\TimeTracker;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Contains USER class object.
|
||||
*/
|
||||
class UserContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, USER
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
if (!is_array($conf) || empty($conf)) {
|
||||
$this->getTimeTracker()->setTSlogMessage('USER without configuration.', 2);
|
||||
return '';
|
||||
}
|
||||
$content = '';
|
||||
if ($this->cObj->getUserObjectType() === false) {
|
||||
// Come here only if we are not called from $TSFE->INTincScript_process()!
|
||||
$this->cObj->setUserObjectType(ContentObjectRenderer::OBJECTTYPE_USER);
|
||||
}
|
||||
$tempContent = $this->cObj->callUserFunction($conf['userFunc'], $conf, '');
|
||||
if ($this->cObj->doConvertToUserIntObject) {
|
||||
$this->cObj->doConvertToUserIntObject = false;
|
||||
$content = $this->cObj->cObjGetSingle('USER_INT', $conf);
|
||||
} else {
|
||||
$content .= $tempContent;
|
||||
// Only executed when the element is not converted to USER_INT
|
||||
if (isset($conf['stdWrap.'])) {
|
||||
$content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
|
||||
}
|
||||
}
|
||||
$this->cObj->setUserObjectType(false);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TimeTracker
|
||||
*/
|
||||
protected function getTimeTracker()
|
||||
{
|
||||
return GeneralUtility::makeInstance(TimeTracker::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\ContentObject;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains USER_INT class object.
|
||||
*/
|
||||
class UserInternalContentObject extends AbstractContentObject
|
||||
{
|
||||
/**
|
||||
* Rendering the cObject, USER_INT
|
||||
*
|
||||
* @param array $conf Array of TypoScript properties
|
||||
* @return string Output
|
||||
*/
|
||||
public function render($conf = [])
|
||||
{
|
||||
$this->cObj->setUserObjectType(ContentObjectRenderer::OBJECTTYPE_USER_INT);
|
||||
$tsfe = $this->getTypoScriptFrontendController();
|
||||
$substKey = 'INT_SCRIPT.' . $tsfe->uniqueHash();
|
||||
$content = '<!--' . $substKey . '-->';
|
||||
$tsfe->config['INTincScript'][$substKey] = [
|
||||
'conf' => $conf,
|
||||
'cObj' => serialize($this->cObj),
|
||||
'type' => 'FUNC'
|
||||
];
|
||||
$this->cObj->setUserObjectType(false);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
|
||||
*/
|
||||
protected function getTypoScriptFrontendController()
|
||||
{
|
||||
return $GLOBALS['TSFE'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Controller\ErrorPageController;
|
||||
use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
|
||||
use TYPO3\CMS\Core\Error\Http\ServiceUnavailableException;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerNotConfiguredException;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Handles "Page Not Found" or "Page Unavailable" requests,
|
||||
* returns a response object.
|
||||
*/
|
||||
class ErrorController
|
||||
{
|
||||
/**
|
||||
* Used for creating a 500 response ("Page unavailable"), usually due some misconfiguration
|
||||
* but if configured, a RedirectResponse could be returned as well.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param string $message
|
||||
* @param array $reasons
|
||||
* @return ResponseInterface
|
||||
* @throws ServiceUnavailableException
|
||||
*/
|
||||
public function unavailableAction(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
|
||||
{
|
||||
if (!$this->isPageUnavailableHandlerConfigured()) {
|
||||
throw new ServiceUnavailableException($message, 1518472181);
|
||||
}
|
||||
$errorHandler = $this->getErrorHandlerFromSite($request, 500);
|
||||
if ($errorHandler instanceof PageErrorHandlerInterface) {
|
||||
return $errorHandler->handlePageError($request, $message, $reasons);
|
||||
}
|
||||
return $this->handlePageError(
|
||||
$GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'],
|
||||
$GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling_statheader'],
|
||||
$message,
|
||||
$reasons
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for creating a 404 response ("Page Not Found"),
|
||||
* but if configured, a RedirectResponse could be returned as well.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param string $message
|
||||
* @param array $reasons
|
||||
* @return ResponseInterface
|
||||
* @throws PageNotFoundException
|
||||
*/
|
||||
public function pageNotFoundAction(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
|
||||
{
|
||||
$errorHandler = $this->getErrorHandlerFromSite($request, 404);
|
||||
if ($errorHandler instanceof PageErrorHandlerInterface) {
|
||||
return $errorHandler->handlePageError($request, $message, $reasons);
|
||||
}
|
||||
if (!$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling']) {
|
||||
throw new PageNotFoundException($message, 1518472189);
|
||||
}
|
||||
return $this->handlePageError(
|
||||
$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'],
|
||||
$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_statheader'],
|
||||
$message,
|
||||
$reasons
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for creating a 403 response ("Access denied"),
|
||||
* but if configured, a RedirectResponse could be returned as well.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param string $message
|
||||
* @param array $reasons
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function accessDeniedAction(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
|
||||
{
|
||||
$errorHandler = $this->getErrorHandlerFromSite($request, 403);
|
||||
if ($errorHandler instanceof PageErrorHandlerInterface) {
|
||||
return $errorHandler->handlePageError($request, $message, $reasons);
|
||||
}
|
||||
return $this->handlePageError(
|
||||
$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'],
|
||||
$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_accessdeniedheader'],
|
||||
$message,
|
||||
$reasons
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the pageUnavailableHandler should be used. To be used, pageUnavailable_handling must be set
|
||||
* and devIPMask must not match the current visitor's IP address.
|
||||
*
|
||||
* @return bool TRUE/FALSE whether the pageUnavailable_handler should be used.
|
||||
*/
|
||||
protected function isPageUnavailableHandlerConfigured(): bool
|
||||
{
|
||||
return
|
||||
$GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling']
|
||||
&& !GeneralUtility::cmpIP(
|
||||
GeneralUtility::getIndpEnv('REMOTE_ADDR'),
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic error page handler.
|
||||
*
|
||||
* @param mixed $errorHandler See docs of ['FE']['pageNotFound_handling'] and ['FE']['pageUnavailable_handling'] for all possible values
|
||||
* @param string $header If set, this is passed directly to the PHP function, header()
|
||||
* @param string $reason If set, error messages will also mention this as the reason for the page-not-found.
|
||||
* @param array $pageAccessFailureReasons
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function handlePageError($errorHandler, string $header = '', string $reason = '', array $pageAccessFailureReasons = []): ResponseInterface
|
||||
{
|
||||
$response = null;
|
||||
$content = '';
|
||||
// Simply boolean; Just shows TYPO3 error page with reason:
|
||||
if (is_bool($errorHandler) || strtolower($errorHandler) === 'true' || (string)$errorHandler === '1') {
|
||||
$content = GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
|
||||
'Page Not Found',
|
||||
'The page did not exist or was inaccessible.' . ($reason ? ' Reason: ' . $reason : '')
|
||||
);
|
||||
} elseif (GeneralUtility::isFirstPartOfStr($errorHandler, 'USER_FUNCTION:')) {
|
||||
$funcRef = trim(substr($errorHandler, 14));
|
||||
$params = [
|
||||
'currentUrl' => GeneralUtility::getIndpEnv('REQUEST_URI'),
|
||||
'reasonText' => $reason,
|
||||
'pageAccessFailureReasons' => $pageAccessFailureReasons
|
||||
];
|
||||
try {
|
||||
$content = GeneralUtility::callUserFunction($funcRef, $params, $this);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException('Error: 404 page by USER_FUNCTION "' . $funcRef . '" failed.', 1518472235, $e);
|
||||
}
|
||||
} elseif (GeneralUtility::isFirstPartOfStr($errorHandler, 'READFILE:')) {
|
||||
$readFile = GeneralUtility::getFileAbsFileName(trim(substr($errorHandler, 9)));
|
||||
if (@is_file($readFile)) {
|
||||
$content = str_replace(
|
||||
[
|
||||
'###CURRENT_URL###',
|
||||
'###REASON###'
|
||||
],
|
||||
[
|
||||
GeneralUtility::getIndpEnv('REQUEST_URI'),
|
||||
htmlspecialchars($reason)
|
||||
],
|
||||
file_get_contents($readFile)
|
||||
);
|
||||
} else {
|
||||
throw new \RuntimeException('Configuration Error: 404 page "' . $readFile . '" could not be found.', 1518472245);
|
||||
}
|
||||
} elseif (GeneralUtility::isFirstPartOfStr($errorHandler, 'REDIRECT:')) {
|
||||
$response = new RedirectResponse(substr($errorHandler, 9));
|
||||
} elseif ($errorHandler !== '') {
|
||||
// Check if URL is relative
|
||||
$urlParts = parse_url($errorHandler);
|
||||
// parse_url could return an array without the key "host", the empty check works better than strict check
|
||||
if (empty($urlParts['host'])) {
|
||||
$urlParts['host'] = GeneralUtility::getIndpEnv('HTTP_HOST');
|
||||
if ($errorHandler[0] === '/') {
|
||||
$errorHandler = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST') . $errorHandler;
|
||||
} else {
|
||||
$errorHandler = GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR') . $errorHandler;
|
||||
}
|
||||
$checkBaseTag = false;
|
||||
} else {
|
||||
$checkBaseTag = true;
|
||||
}
|
||||
// Check recursion
|
||||
if ($errorHandler === GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')) {
|
||||
$reason = $reason ?: 'Page cannot be found.';
|
||||
$reason .= LF . LF . 'Additionally, ' . $errorHandler . ' was not found while trying to retrieve the error document.';
|
||||
throw new \RuntimeException(nl2br(htmlspecialchars($reason)), 1518472252);
|
||||
}
|
||||
// Prepare headers
|
||||
$requestHeaders = [
|
||||
'User-agent' => GeneralUtility::getIndpEnv('HTTP_USER_AGENT'),
|
||||
'Referer' => GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')
|
||||
];
|
||||
$report = [];
|
||||
$res = GeneralUtility::getUrl($errorHandler, 1, $requestHeaders, $report);
|
||||
if ((int)$report['error'] !== 0 && (int)$report['error'] !== 200) {
|
||||
throw new \RuntimeException('Failed to fetch error page "' . $errorHandler . '", reason: ' . $report['message'], 1518472257);
|
||||
}
|
||||
if ($res === false) {
|
||||
// Last chance -- redirect
|
||||
$response = new RedirectResponse($errorHandler);
|
||||
} else {
|
||||
// Header and content are separated by an empty line
|
||||
list($returnedHeaders, $content) = explode(CRLF . CRLF, $res, 2);
|
||||
$content .= CRLF;
|
||||
// Forward these response headers to the client
|
||||
$forwardHeaders = [
|
||||
'Content-Type:'
|
||||
];
|
||||
$headerArr = preg_split('/\\r|\\n/', $returnedHeaders, -1, PREG_SPLIT_NO_EMPTY);
|
||||
foreach ($headerArr as $headerLine) {
|
||||
foreach ($forwardHeaders as $h) {
|
||||
if (preg_match('/^' . $h . '/', $headerLine)) {
|
||||
$header .= CRLF . $headerLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Put <base> if necessary
|
||||
if ($checkBaseTag) {
|
||||
// If content already has <base> tag, we do not need to do anything
|
||||
if (false === stristr($content, '<base ')) {
|
||||
// Generate href for base tag
|
||||
$base = $urlParts['scheme'] . '://';
|
||||
if ($urlParts['user'] != '') {
|
||||
$base .= $urlParts['user'];
|
||||
if ($urlParts['pass'] != '') {
|
||||
$base .= ':' . $urlParts['pass'];
|
||||
}
|
||||
$base .= '@';
|
||||
}
|
||||
$base .= $urlParts['host'];
|
||||
// Add path portion skipping possible file name
|
||||
$base .= preg_replace('/(.*\\/)[^\\/]*/', '${1}', $urlParts['path']);
|
||||
// Put it into content (generate also <head> if necessary)
|
||||
$replacement = LF . '<base href="' . htmlentities($base) . '" />' . LF;
|
||||
if (stristr($content, '<head>')) {
|
||||
$content = preg_replace('/(<head>)/i', '\\1' . $replacement, $content);
|
||||
} else {
|
||||
$content = preg_replace('/(<html[^>]*>)/i', '\\1<head>' . $replacement . '</head>', $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$content = GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
|
||||
'Page Not Found',
|
||||
$reason ? 'Reason: ' . $reason : 'Page cannot be found.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!$response) {
|
||||
$response = new HtmlResponse($content);
|
||||
}
|
||||
return $this->applySanitizedHeadersToResponse($response, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Headers which have been requested, will be added to the response object.
|
||||
* If a header is part of the HTTP Response code, the response object will be annotated as well.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @param string $headers
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function applySanitizedHeadersToResponse(ResponseInterface $response, string $headers): ResponseInterface
|
||||
{
|
||||
if (!empty($headers)) {
|
||||
$headerArr = preg_split('/\\r|\\n/', $headers, -1, PREG_SPLIT_NO_EMPTY);
|
||||
foreach ($headerArr as $headerLine) {
|
||||
if (strpos($headerLine, 'HTTP/') === 0 && strpos($headerLine, ':') === false) {
|
||||
list($protocolVersion, $statusCode, $reasonPhrase) = explode(' ', $headerLine, 3);
|
||||
list(, $protocolVersion) = explode('/', $protocolVersion, 2);
|
||||
$response = $response
|
||||
->withProtocolVersion((int)$protocolVersion)
|
||||
->withStatus($statusCode, $reasonPhrase);
|
||||
} else {
|
||||
list($headerName, $value) = GeneralUtility::trimExplode(':', $headerLine, 2);
|
||||
$response = $response->withHeader($headerName, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a site is configured, and an error handler is configured for this specific status code.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param int $statusCode
|
||||
* @return PageErrorHandlerInterface|null
|
||||
*/
|
||||
protected function getErrorHandlerFromSite(ServerRequestInterface $request, int $statusCode): ?PageErrorHandlerInterface
|
||||
{
|
||||
$site = $request->getAttribute('site');
|
||||
if ($site instanceof Site) {
|
||||
try {
|
||||
return $site->getErrorHandler($statusCode);
|
||||
} catch (PageErrorHandlerNotConfiguredException $e) {
|
||||
// No error handler found, so fallback back to the generic TYPO3 error handler.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
use TYPO3\CMS\Core\Http\Response;
|
||||
use TYPO3\CMS\Core\Resource\FileInterface;
|
||||
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* eID-Script "tx_cms_showpic"
|
||||
*
|
||||
* Shows a picture from FAL in enlarged format in a separate window.
|
||||
* Picture file and settings is supplied by GET-parameters:
|
||||
*
|
||||
* - file = fileUid or Combined Identifier
|
||||
* - encoded in an parameter Array (with weird format - see ContentObjectRenderer about ll. 1500)
|
||||
* - width, height = usual width an height, m/c supported
|
||||
* - frame
|
||||
* - bodyTag
|
||||
* - title
|
||||
*
|
||||
* @internal this is a concrete TYPO3 implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class ShowImageController
|
||||
{
|
||||
protected const ALLOWED_PARAMETER_NAMES = ['width', 'height', 'crop', 'bodyTag', 'title'];
|
||||
|
||||
/**
|
||||
* @var \Psr\Http\Message\ServerRequestInterface
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Resource\File
|
||||
*/
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $width;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $height;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $crop;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $frame;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $bodyTag = '<body>';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title = 'Image';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $content = <<<EOF
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>###TITLE###</title>
|
||||
<meta name="robots" content="noindex,follow" />
|
||||
</head>
|
||||
###BODY###
|
||||
###IMAGE###
|
||||
</body>
|
||||
</html>
|
||||
EOF;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $imageTag = '<img src="###publicUrl###" alt="###alt###" title="###title###" width="###width###" height="###height###" />';
|
||||
|
||||
/**
|
||||
* Init function, setting the input vars in the global space.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
$fileUid = $this->request->getQueryParams()['file'] ?? null;
|
||||
$parametersArray = $this->request->getQueryParams()['parameters'] ?? null;
|
||||
|
||||
// If no file-param or parameters are given, we must exit
|
||||
if (!$fileUid || !isset($parametersArray) || !is_array($parametersArray)) {
|
||||
throw new \InvalidArgumentException('No valid fileUid given', 1476048455);
|
||||
}
|
||||
|
||||
// rebuild the parameter array and check if the HMAC is correct
|
||||
$parametersEncoded = implode('', $parametersArray);
|
||||
|
||||
/* For backwards compatibility the HMAC is transported within the md5 param */
|
||||
$hmacParameter = $this->request->getQueryParams()['md5'] ?? null;
|
||||
$hmac = GeneralUtility::hmac(implode('|', [$fileUid, $parametersEncoded]));
|
||||
if (!is_string($hmacParameter) || !hash_equals($hmac, $hmacParameter)) {
|
||||
throw new \InvalidArgumentException('hash does not match', 1476048456);
|
||||
}
|
||||
|
||||
// decode the parameters Array - `bodyTag` contains HTML if set and would lead
|
||||
// to a false-positive XSS-detection, that's why parameters are base64-encoded
|
||||
$parameters = json_decode(base64_decode($parametersEncoded), true) ?? [];
|
||||
foreach ($parameters as $parameterName => $parameterValue) {
|
||||
if (in_array($parameterName, static::ALLOWED_PARAMETER_NAMES, true)) {
|
||||
$this->{$parameterName} = $parameterValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
|
||||
$this->file = ResourceFactory::getInstance()->getFileObject((int)$fileUid);
|
||||
} else {
|
||||
$this->file = ResourceFactory::getInstance()->retrieveFileOrFolderObject($fileUid);
|
||||
}
|
||||
if ($this->file !== null && !$this->isFileValid($this->file)) {
|
||||
throw new Exception('File processing for local storage is denied', 1594043425);
|
||||
}
|
||||
|
||||
$this->frame = $this->request->getQueryParams()['frame'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function which creates the image if needed and outputs the HTML code for the page displaying the image.
|
||||
* Accumulates the content in $this->content
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
$processedImage = $this->processImage();
|
||||
$imageTagMarkers = [
|
||||
'###publicUrl###' => htmlspecialchars($processedImage->getPublicUrl()),
|
||||
'###alt###' => htmlspecialchars($this->file->getProperty('alternative') ?: $this->title),
|
||||
'###title###' => htmlspecialchars($this->file->getProperty('title') ?: $this->title),
|
||||
'###width###' => $processedImage->getProperty('width'),
|
||||
'###height###' => $processedImage->getProperty('height')
|
||||
];
|
||||
$this->imageTag = str_replace(array_keys($imageTagMarkers), array_values($imageTagMarkers), $this->imageTag);
|
||||
$markerArray = [
|
||||
'###TITLE###' => $this->file->getProperty('title') ?: $this->title,
|
||||
'###IMAGE###' => $this->imageTag,
|
||||
'###BODY###' => $this->bodyTag
|
||||
];
|
||||
|
||||
$this->content = str_replace(array_keys($markerArray), array_values($markerArray), $this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual image processing
|
||||
*
|
||||
* @return \TYPO3\CMS\Core\Resource\ProcessedFile
|
||||
*/
|
||||
protected function processImage()
|
||||
{
|
||||
if (strstr($this->width . $this->height, 'm')) {
|
||||
$max = 'm';
|
||||
} else {
|
||||
$max = '';
|
||||
}
|
||||
$this->height = MathUtility::forceIntegerInRange($this->height, 0);
|
||||
$this->width = MathUtility::forceIntegerInRange($this->width, 0) . $max;
|
||||
|
||||
$processingConfiguration = [
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
'frame' => $this->frame,
|
||||
'crop' => $this->crop,
|
||||
];
|
||||
return $this->file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the content and builds a content file out of it
|
||||
*
|
||||
* @param ServerRequestInterface $request the current request object
|
||||
* @return ResponseInterface the modified response
|
||||
*/
|
||||
public function processRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->request = $request;
|
||||
|
||||
try {
|
||||
$this->initialize();
|
||||
$this->main();
|
||||
$response = new Response();
|
||||
$response->getBody()->write($this->content);
|
||||
return $response;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// add a 410 "gone" if invalid parameters given
|
||||
return (new Response)->withStatus(410);
|
||||
} catch (Exception $e) {
|
||||
return (new Response)->withStatus(404);
|
||||
}
|
||||
}
|
||||
|
||||
protected function isFileValid(FileInterface $file): bool
|
||||
{
|
||||
return $file->getStorage()->getDriverType() !== 'Local'
|
||||
|| GeneralUtility::verifyFilenameAgainstDenyPattern(basename($file->getIdentifier()));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\DataProcessing;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\CsvUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
|
||||
/**
|
||||
* This data processor will take field data formatted as a string, where each line, separated by line feed,
|
||||
* represents a row. By default columns are separated by the delimiter character "comma ,",
|
||||
* and can be enclosed by the character 'quotation mark "', like the default in a regular CSV file.
|
||||
*
|
||||
* An example of such a field is "bodytext" in the CType "table".
|
||||
*
|
||||
* The table data is transformed to a multi dimensional array, taking the delimiter and enclosure into account,
|
||||
* before it is passed to the view.
|
||||
*
|
||||
* Example field data:
|
||||
*
|
||||
* This is row 1 column 1|This is row 1 column 2|This is row 1 column 3
|
||||
* This is row 2 column 1|This is row 2 column 2|This is row 2 column 3
|
||||
* This is row 3 column 1|This is row 3 column 2|This is row 3 column 3
|
||||
*
|
||||
* Example TypoScript configuration:
|
||||
*
|
||||
* 10 = TYPO3\CMS\Frontend\DataProcessing\CommaSeparatedValueProcessor
|
||||
* 10 {
|
||||
* if.isTrue.field = bodytext
|
||||
* fieldName = bodytext
|
||||
* fieldDelimiter = |
|
||||
* fieldEnclosure =
|
||||
* maximumColumns = 2
|
||||
* as = table
|
||||
* }
|
||||
*
|
||||
* whereas "table" can be used as a variable {table} inside Fluid for iteration.
|
||||
*
|
||||
* Using maximumColumns limits the amount of columns in the multi dimensional array.
|
||||
* In the example, field data of the last column will be stripped off.
|
||||
*
|
||||
* Multi line cells are taken into account.
|
||||
*/
|
||||
class CommaSeparatedValueProcessor implements DataProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Process CSV field data to split into a multi dimensional array
|
||||
*
|
||||
* @param ContentObjectRenderer $cObj The data of the content element or page
|
||||
* @param array $contentObjectConfiguration The configuration of Content Object
|
||||
* @param array $processorConfiguration The configuration of this processor
|
||||
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
|
||||
* @return array the processed data as key/value store
|
||||
*/
|
||||
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
|
||||
{
|
||||
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
// The field name to process
|
||||
$fieldName = $cObj->stdWrapValue('fieldName', $processorConfiguration);
|
||||
if (empty($fieldName)) {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
$originalValue = $cObj->data[$fieldName];
|
||||
|
||||
// Set the target variable
|
||||
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, $fieldName);
|
||||
|
||||
// Set the maximum amount of columns
|
||||
$maximumColumns = $cObj->stdWrapValue('maximumColumns', $processorConfiguration, 0);
|
||||
|
||||
// Set the field delimiter which is "," by default
|
||||
$fieldDelimiter = $cObj->stdWrapValue('fieldDelimiter', $processorConfiguration, ',');
|
||||
|
||||
// Set the field enclosure which is " by default
|
||||
$fieldEnclosure = $cObj->stdWrapValue('fieldEnclosure', $processorConfiguration, '"');
|
||||
|
||||
$processedData[$targetVariableName] = CsvUtility::csvToArray(
|
||||
$originalValue,
|
||||
$fieldDelimiter,
|
||||
$fieldEnclosure,
|
||||
(int)$maximumColumns
|
||||
);
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\DataProcessing;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
|
||||
/**
|
||||
* Fetch records from the database, using the default .select syntax from TypoScript.
|
||||
*
|
||||
* This way, e.g. a FLUIDTEMPLATE cObject can iterate over the array of records.
|
||||
*
|
||||
* Example TypoScript configuration:
|
||||
*
|
||||
* 10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor
|
||||
* 10 {
|
||||
* table = tt_address
|
||||
* pidInList = 123
|
||||
* where = company="Acme" AND first_name="Ralph"
|
||||
* orderBy = sorting DESC
|
||||
* as = addresses
|
||||
* dataProcessing {
|
||||
* 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
|
||||
* 10 {
|
||||
* references.fieldName = image
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* where "as" means the variable to be containing the result-set from the DB query.
|
||||
*/
|
||||
class DatabaseQueryProcessor implements DataProcessorInterface
|
||||
{
|
||||
/**
|
||||
* @var ContentDataProcessor
|
||||
*/
|
||||
protected $contentDataProcessor;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches records from the database as an array
|
||||
*
|
||||
* @param ContentObjectRenderer $cObj The data of the content element or page
|
||||
* @param array $contentObjectConfiguration The configuration of Content Object
|
||||
* @param array $processorConfiguration The configuration of this processor
|
||||
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
|
||||
*
|
||||
* @return array the processed data as key/value store
|
||||
*/
|
||||
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
|
||||
{
|
||||
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
// the table to query, if none given, exit
|
||||
$tableName = $cObj->stdWrapValue('table', $processorConfiguration);
|
||||
if (empty($tableName)) {
|
||||
return $processedData;
|
||||
}
|
||||
if (isset($processorConfiguration['table.'])) {
|
||||
unset($processorConfiguration['table.']);
|
||||
}
|
||||
if (isset($processorConfiguration['table'])) {
|
||||
unset($processorConfiguration['table']);
|
||||
}
|
||||
|
||||
// The variable to be used within the result
|
||||
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'records');
|
||||
|
||||
// Execute a SQL statement to fetch the records
|
||||
$records = $cObj->getRecords($tableName, $processorConfiguration);
|
||||
$processedRecordVariables = [];
|
||||
foreach ($records as $key => $record) {
|
||||
/** @var ContentObjectRenderer $recordContentObjectRenderer */
|
||||
$recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$recordContentObjectRenderer->start($record, $tableName);
|
||||
$processedRecordVariables[$key] = ['data' => $record];
|
||||
$processedRecordVariables[$key] = $this->contentDataProcessor->process($recordContentObjectRenderer, $processorConfiguration, $processedRecordVariables[$key]);
|
||||
}
|
||||
|
||||
$processedData[$targetVariableName] = $processedRecordVariables;
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\DataProcessing;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
use TYPO3\CMS\Frontend\Resource\FileCollector;
|
||||
|
||||
/**
|
||||
* This data processor can be used for processing data for record which contain
|
||||
* relations to sys_file records (e.g. sys_file_reference records) or for fetching
|
||||
* files directly from UIDs or from folders or collections.
|
||||
*
|
||||
*
|
||||
* Example TypoScript configuration:
|
||||
*
|
||||
* 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
|
||||
* 10 {
|
||||
* references.fieldName = image
|
||||
* collections = 13,15
|
||||
* as = myfiles
|
||||
* }
|
||||
*
|
||||
* whereas "myfiles" can further be used as a variable {myfiles} inside a Fluid template for iteration.
|
||||
*/
|
||||
class FilesProcessor implements DataProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Process data of a record to resolve File objects to the view
|
||||
*
|
||||
* @param ContentObjectRenderer $cObj The data of the content element or page
|
||||
* @param array $contentObjectConfiguration The configuration of Content Object
|
||||
* @param array $processorConfiguration The configuration of this processor
|
||||
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
|
||||
* @return array the processed data as key/value store
|
||||
*/
|
||||
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
|
||||
{
|
||||
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
// gather data
|
||||
/** @var FileCollector $fileCollector */
|
||||
$fileCollector = GeneralUtility::makeInstance(FileCollector::class);
|
||||
|
||||
// references / relations
|
||||
if (!empty($processorConfiguration['references.'])) {
|
||||
$referenceConfiguration = $processorConfiguration['references.'];
|
||||
$relationField = $cObj->stdWrapValue('fieldName', $referenceConfiguration);
|
||||
|
||||
// If no reference fieldName is set, there's nothing to do
|
||||
if (!empty($relationField)) {
|
||||
// Fetch the references of the default element
|
||||
$relationTable = $cObj->stdWrapValue('table', $referenceConfiguration, $cObj->getCurrentTable());
|
||||
if (!empty($relationTable)) {
|
||||
$fileCollector->addFilesFromRelation($relationTable, $relationField, $cObj->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// files
|
||||
$files = $cObj->stdWrapValue('files', $processorConfiguration);
|
||||
if ($files) {
|
||||
$files = GeneralUtility::intExplode(',', $files, true);
|
||||
$fileCollector->addFiles($files);
|
||||
}
|
||||
|
||||
// collections
|
||||
$collections = $cObj->stdWrapValue('collections', $processorConfiguration);
|
||||
if (!empty($collections)) {
|
||||
$collections = GeneralUtility::trimExplode(',', $collections, true);
|
||||
$fileCollector->addFilesFromFileCollections($collections);
|
||||
}
|
||||
|
||||
// folders
|
||||
$folders = $cObj->stdWrapValue('folders', $processorConfiguration);
|
||||
if (!empty($folders)) {
|
||||
$folders = GeneralUtility::trimExplode(',', $folders, true);
|
||||
$fileCollector->addFilesFromFolders($folders, !empty($processorConfiguration['folders.']['recursive']));
|
||||
}
|
||||
|
||||
// make sure to sort the files
|
||||
$sortingProperty = $cObj->stdWrapValue('sorting', $processorConfiguration);
|
||||
if ($sortingProperty) {
|
||||
$sortingDirection = $cObj->stdWrapValue(
|
||||
'direction',
|
||||
$processorConfiguration['sorting.'] ?? [],
|
||||
'ascending'
|
||||
);
|
||||
|
||||
$fileCollector->sort($sortingProperty, $sortingDirection);
|
||||
}
|
||||
|
||||
// set the files into a variable, default "files"
|
||||
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'files');
|
||||
$processedData[$targetVariableName] = $fileCollector->getFiles();
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\DataProcessing;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
|
||||
use TYPO3\CMS\Core\Resource\FileInterface;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
|
||||
|
||||
/**
|
||||
* This data processor will calculate rows, columns and dimensions for a gallery
|
||||
* based on several settings and can be used for f.i. the CType "textmedia"
|
||||
*
|
||||
* The output will be an array which contains the rows and columns,
|
||||
* including the file references and the calculated width and height for each media element,
|
||||
* but also some more information of the gallery, like position, width and counters
|
||||
*
|
||||
* Example TypoScript configuration:
|
||||
*
|
||||
* 10 = TYPO3\CMS\Frontend\DataProcessing\GalleryProcessor
|
||||
* 10 {
|
||||
* filesProcessedDataKey = files
|
||||
* mediaOrientation.field = imageorient
|
||||
* numberOfColumns.field = imagecols
|
||||
* equalMediaHeight.field = imageheight
|
||||
* equalMediaWidth.field = imagewidth
|
||||
* columnSpacing = 0
|
||||
* borderEnabled.field = imageborder
|
||||
* borderPadding = 0
|
||||
* borderWidth = 0
|
||||
* maxGalleryWidth = {$styles.content.mediatext.maxW}
|
||||
* maxGalleryWidthInText = {$styles.content.mediatext.maxWInText}
|
||||
* as = gallery
|
||||
* }
|
||||
*
|
||||
* Output example:
|
||||
*
|
||||
* gallery {
|
||||
* position {
|
||||
* horizontal = center
|
||||
* vertical = above
|
||||
* noWrap = FALSE
|
||||
* }
|
||||
* width = 600
|
||||
* count {
|
||||
* files = 2
|
||||
* columns = 1
|
||||
* rows = 2
|
||||
* }
|
||||
* rows {
|
||||
* 1 {
|
||||
* columns {
|
||||
* 1 {
|
||||
* media = TYPO3\CMS\Core\Resource\FileReference
|
||||
* dimensions {
|
||||
* width = 600
|
||||
* height = 400
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* 2 {
|
||||
* columns {
|
||||
* 1 {
|
||||
* media = TYPO3\CMS\Core\Resource\FileReference
|
||||
* dimensions {
|
||||
* width = 600
|
||||
* height = 400
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* columnSpacing = 0
|
||||
* border {
|
||||
* enabled = FALSE
|
||||
* width = 0
|
||||
* padding = 0
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class GalleryProcessor implements DataProcessorInterface
|
||||
{
|
||||
/**
|
||||
* The content object renderer
|
||||
*
|
||||
* @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
|
||||
*/
|
||||
protected $contentObjectRenderer;
|
||||
|
||||
/**
|
||||
* The processor configuration
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $processorConfiguration;
|
||||
|
||||
/**
|
||||
* Matching the tt_content field towards the imageOrient option
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $availableGalleryPositions = [
|
||||
'horizontal' => [
|
||||
'center' => [0, 8],
|
||||
'right' => [1, 9, 17, 25],
|
||||
'left' => [2, 10, 18, 26]
|
||||
],
|
||||
'vertical' => [
|
||||
'above' => [0, 1, 2],
|
||||
'intext' => [17, 18, 25, 26],
|
||||
'below' => [8, 9, 10]
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Storage for processed data
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $galleryData = [
|
||||
'position' => [
|
||||
'horizontal' => '',
|
||||
'vertical' => '',
|
||||
'noWrap' => false
|
||||
],
|
||||
'width' => 0,
|
||||
'count' => [
|
||||
'files' => 0,
|
||||
'columns' => 0,
|
||||
'rows' => 0,
|
||||
],
|
||||
'columnSpacing' => 0,
|
||||
'border' => [
|
||||
'enabled' => false,
|
||||
'width' => 0,
|
||||
'padding' => 0,
|
||||
],
|
||||
'rows' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $numberOfColumns;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $mediaOrientation;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $maxGalleryWidth;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $maxGalleryWidthInText;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $equalMediaHeight;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $equalMediaWidth;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $columnSpacing;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $borderEnabled;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $borderWidth;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $borderPadding;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cropVariant = 'default';
|
||||
|
||||
/**
|
||||
* The (filtered) media files to be used in the gallery
|
||||
*
|
||||
* @var FileInterface[]
|
||||
*/
|
||||
protected $fileObjects = [];
|
||||
|
||||
/**
|
||||
* The calculated dimensions for each media element
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $mediaDimensions = [];
|
||||
|
||||
/**
|
||||
* Process data for a gallery, for instance the CType "textmedia"
|
||||
*
|
||||
* @param ContentObjectRenderer $cObj The content object renderer, which contains data of the content element
|
||||
* @param array $contentObjectConfiguration The configuration of Content Object
|
||||
* @param array $processorConfiguration The configuration of this processor
|
||||
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
|
||||
* @return array the processed data as key/value store
|
||||
* @throws ContentRenderingException
|
||||
*/
|
||||
public function process(
|
||||
ContentObjectRenderer $cObj,
|
||||
array $contentObjectConfiguration,
|
||||
array $processorConfiguration,
|
||||
array $processedData
|
||||
) {
|
||||
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
$this->contentObjectRenderer = $cObj;
|
||||
$this->processorConfiguration = $processorConfiguration;
|
||||
|
||||
$filesProcessedDataKey = (string)$cObj->stdWrapValue(
|
||||
'filesProcessedDataKey',
|
||||
$processorConfiguration,
|
||||
'files'
|
||||
);
|
||||
if (isset($processedData[$filesProcessedDataKey]) && is_array($processedData[$filesProcessedDataKey])) {
|
||||
$this->fileObjects = $processedData[$filesProcessedDataKey];
|
||||
$this->galleryData['count']['files'] = count($this->fileObjects);
|
||||
} else {
|
||||
throw new ContentRenderingException('No files found for key ' . $filesProcessedDataKey . ' in $processedData.', 1436809789);
|
||||
}
|
||||
|
||||
$this->numberOfColumns = (int)$this->getConfigurationValue('numberOfColumns', 'imagecols');
|
||||
$this->mediaOrientation = (int)$this->getConfigurationValue('mediaOrientation', 'imageorient');
|
||||
$this->maxGalleryWidth = (int)$this->getConfigurationValue('maxGalleryWidth') ?: 600;
|
||||
$this->maxGalleryWidthInText = (int)$this->getConfigurationValue('maxGalleryWidthInText') ?: 300;
|
||||
$this->equalMediaHeight = (int)$this->getConfigurationValue('equalMediaHeight', 'imageheight');
|
||||
$this->equalMediaWidth = (int)$this->getConfigurationValue('equalMediaWidth', 'imagewidth');
|
||||
$this->columnSpacing = (int)$this->getConfigurationValue('columnSpacing');
|
||||
$this->borderEnabled = (bool)$this->getConfigurationValue('borderEnabled', 'imageborder');
|
||||
$this->borderWidth = (int)$this->getConfigurationValue('borderWidth');
|
||||
$this->borderPadding = (int)$this->getConfigurationValue('borderPadding');
|
||||
$this->cropVariant = $this->getConfigurationValue('cropVariant') ?: 'default';
|
||||
|
||||
$this->determineGalleryPosition();
|
||||
$this->determineMaximumGalleryWidth();
|
||||
|
||||
$this->calculateRowsAndColumns();
|
||||
$this->calculateMediaWidthsAndHeights();
|
||||
|
||||
$this->prepareGalleryData();
|
||||
|
||||
$targetFieldName = (string)$cObj->stdWrapValue(
|
||||
'as',
|
||||
$processorConfiguration,
|
||||
'gallery'
|
||||
);
|
||||
|
||||
$processedData[$targetFieldName] = $this->galleryData;
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration value from processorConfiguration
|
||||
* with when $dataArrayKey fallback to value from cObj->data array
|
||||
*
|
||||
* @param string $key
|
||||
* @param string|null $dataArrayKey
|
||||
* @return string
|
||||
*/
|
||||
protected function getConfigurationValue($key, $dataArrayKey = null)
|
||||
{
|
||||
$defaultValue = '';
|
||||
if ($dataArrayKey && isset($this->contentObjectRenderer->data[$dataArrayKey])) {
|
||||
$defaultValue = $this->contentObjectRenderer->data[$dataArrayKey];
|
||||
}
|
||||
return $this->contentObjectRenderer->stdWrapValue(
|
||||
$key,
|
||||
$this->processorConfiguration,
|
||||
$defaultValue
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the gallery position
|
||||
*
|
||||
* Gallery has a horizontal and a vertical position towards the text
|
||||
* and a possible wrapping of the text around the gallery.
|
||||
*/
|
||||
protected function determineGalleryPosition()
|
||||
{
|
||||
foreach ($this->availableGalleryPositions as $positionDirectionKey => $positionDirectionValue) {
|
||||
foreach ($positionDirectionValue as $positionKey => $positionArray) {
|
||||
if (in_array($this->mediaOrientation, $positionArray, true)) {
|
||||
$this->galleryData['position'][$positionDirectionKey] = $positionKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->mediaOrientation === 25 || $this->mediaOrientation === 26) {
|
||||
$this->galleryData['position']['noWrap'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gallery width based on vertical position
|
||||
*/
|
||||
protected function determineMaximumGalleryWidth()
|
||||
{
|
||||
if ($this->galleryData['position']['vertical'] === 'intext') {
|
||||
$this->galleryData['width'] = $this->maxGalleryWidthInText;
|
||||
} else {
|
||||
$this->galleryData['width'] = $this->maxGalleryWidth;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the amount of rows and columns
|
||||
*/
|
||||
protected function calculateRowsAndColumns()
|
||||
{
|
||||
|
||||
// If no columns defined, set it to 1
|
||||
$columns = max((int)$this->numberOfColumns, 1);
|
||||
|
||||
// When more columns than media elements, set the columns to the amount of media elements
|
||||
if ($columns > $this->galleryData['count']['files']) {
|
||||
$columns = $this->galleryData['count']['files'];
|
||||
}
|
||||
|
||||
if ($columns === 0) {
|
||||
$columns = 1;
|
||||
}
|
||||
|
||||
// Calculate the rows from the amount of files and the columns
|
||||
$rows = ceil($this->galleryData['count']['files'] / $columns);
|
||||
|
||||
$this->galleryData['count']['columns'] = $columns;
|
||||
$this->galleryData['count']['rows'] = (int)$rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the width/height of the media elements
|
||||
*
|
||||
* Based on the width of the gallery, defined equal width or height by a user, the spacing between columns and
|
||||
* the use of a border, defined by user, where the border width and padding are taken into account
|
||||
*
|
||||
* File objects MUST already be filtered. They need a height and width to be shown in the gallery
|
||||
*/
|
||||
protected function calculateMediaWidthsAndHeights()
|
||||
{
|
||||
$columnSpacingTotal = ($this->galleryData['count']['columns'] - 1) * $this->columnSpacing;
|
||||
|
||||
$galleryWidthMinusBorderAndSpacing = max($this->galleryData['width'] - $columnSpacingTotal, 1);
|
||||
|
||||
if ($this->borderEnabled) {
|
||||
$borderPaddingTotal = ($this->galleryData['count']['columns'] * 2) * $this->borderPadding;
|
||||
$borderWidthTotal = ($this->galleryData['count']['columns'] * 2) * $this->borderWidth;
|
||||
$galleryWidthMinusBorderAndSpacing = $galleryWidthMinusBorderAndSpacing - $borderPaddingTotal - $borderWidthTotal;
|
||||
}
|
||||
|
||||
// User entered a predefined height
|
||||
if ($this->equalMediaHeight) {
|
||||
$mediaScalingCorrection = 1;
|
||||
$maximumRowWidth = 0;
|
||||
|
||||
// Calculate the scaling correction when the total of media elements is wider than the gallery width
|
||||
for ($row = 1; $row <= $this->galleryData['count']['rows']; $row++) {
|
||||
$totalRowWidth = 0;
|
||||
for ($column = 1; $column <= $this->galleryData['count']['columns']; $column++) {
|
||||
$fileKey = (($row - 1) * $this->galleryData['count']['columns']) + $column - 1;
|
||||
if ($fileKey > $this->galleryData['count']['files'] - 1) {
|
||||
break 2;
|
||||
}
|
||||
$currentMediaScaling = $this->equalMediaHeight / max($this->getCroppedDimensionalProperty($this->fileObjects[$fileKey], 'height'), 1);
|
||||
$totalRowWidth += $this->getCroppedDimensionalProperty($this->fileObjects[$fileKey], 'width') * $currentMediaScaling;
|
||||
}
|
||||
$maximumRowWidth = max($totalRowWidth, $maximumRowWidth);
|
||||
$mediaInRowScaling = $totalRowWidth / $galleryWidthMinusBorderAndSpacing;
|
||||
$mediaScalingCorrection = max($mediaInRowScaling, $mediaScalingCorrection);
|
||||
}
|
||||
|
||||
// Set the corrected dimensions for each media element
|
||||
foreach ($this->fileObjects as $key => $fileObject) {
|
||||
$mediaHeight = floor($this->equalMediaHeight / $mediaScalingCorrection);
|
||||
$mediaWidth = floor(
|
||||
$this->getCroppedDimensionalProperty($fileObject, 'width') * ($mediaHeight / max($this->getCroppedDimensionalProperty($fileObject, 'height'), 1))
|
||||
);
|
||||
$this->mediaDimensions[$key] = [
|
||||
'width' => $mediaWidth,
|
||||
'height' => $mediaHeight
|
||||
];
|
||||
}
|
||||
|
||||
// Recalculate gallery width
|
||||
$this->galleryData['width'] = floor($maximumRowWidth / $mediaScalingCorrection);
|
||||
|
||||
// User entered a predefined width
|
||||
} elseif ($this->equalMediaWidth) {
|
||||
$mediaScalingCorrection = 1;
|
||||
|
||||
// Calculate the scaling correction when the total of media elements is wider than the gallery width
|
||||
$totalRowWidth = $this->galleryData['count']['columns'] * $this->equalMediaWidth;
|
||||
$mediaInRowScaling = $totalRowWidth / $galleryWidthMinusBorderAndSpacing;
|
||||
$mediaScalingCorrection = max($mediaInRowScaling, $mediaScalingCorrection);
|
||||
|
||||
// Set the corrected dimensions for each media element
|
||||
foreach ($this->fileObjects as $key => $fileObject) {
|
||||
$mediaWidth = floor($this->equalMediaWidth / $mediaScalingCorrection);
|
||||
$mediaHeight = floor(
|
||||
$this->getCroppedDimensionalProperty($fileObject, 'height') * ($mediaWidth / max($this->getCroppedDimensionalProperty($fileObject, 'width'), 1))
|
||||
);
|
||||
$this->mediaDimensions[$key] = [
|
||||
'width' => $mediaWidth,
|
||||
'height' => $mediaHeight
|
||||
];
|
||||
}
|
||||
|
||||
// Recalculate gallery width
|
||||
$this->galleryData['width'] = floor($totalRowWidth / $mediaScalingCorrection);
|
||||
|
||||
// Automatic setting of width and height
|
||||
} else {
|
||||
$maxMediaWidth = (int)($galleryWidthMinusBorderAndSpacing / $this->galleryData['count']['columns']);
|
||||
foreach ($this->fileObjects as $key => $fileObject) {
|
||||
$croppedWidth = $this->getCroppedDimensionalProperty($fileObject, 'width');
|
||||
$mediaWidth = $croppedWidth > 0 ? min($maxMediaWidth, $croppedWidth) : $maxMediaWidth;
|
||||
$mediaHeight = floor(
|
||||
$this->getCroppedDimensionalProperty($fileObject, 'height') * ($mediaWidth / max($this->getCroppedDimensionalProperty($fileObject, 'width'), 1))
|
||||
);
|
||||
$this->mediaDimensions[$key] = [
|
||||
'width' => $mediaWidth,
|
||||
'height' => $mediaHeight
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When retrieving the height or width for a media file
|
||||
* a possible cropping needs to be taken into account.
|
||||
*
|
||||
* @param FileInterface $fileObject
|
||||
* @param string $dimensionalProperty 'width' or 'height'
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getCroppedDimensionalProperty(FileInterface $fileObject, $dimensionalProperty)
|
||||
{
|
||||
if (!$fileObject->hasProperty('crop') || empty($fileObject->getProperty('crop'))) {
|
||||
return $fileObject->getProperty($dimensionalProperty);
|
||||
}
|
||||
|
||||
$croppingConfiguration = $fileObject->getProperty('crop');
|
||||
$cropVariantCollection = CropVariantCollection::create((string)$croppingConfiguration);
|
||||
return (int)$cropVariantCollection->getCropArea($this->cropVariant)->makeAbsoluteBasedOnFile($fileObject)->asArray()[$dimensionalProperty];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the gallery data
|
||||
*
|
||||
* Make an array for rows, columns and configuration
|
||||
*/
|
||||
protected function prepareGalleryData()
|
||||
{
|
||||
for ($row = 1; $row <= $this->galleryData['count']['rows']; $row++) {
|
||||
for ($column = 1; $column <= $this->galleryData['count']['columns']; $column++) {
|
||||
$fileKey = (($row - 1) * $this->galleryData['count']['columns']) + $column - 1;
|
||||
|
||||
$this->galleryData['rows'][$row]['columns'][$column] = [
|
||||
'media' => $this->fileObjects[$fileKey] ?? null,
|
||||
'dimensions' => [
|
||||
'width' => $this->mediaDimensions[$fileKey]['width'] ?? null,
|
||||
'height' => $this->mediaDimensions[$fileKey]['height'] ?? null
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->galleryData['columnSpacing'] = $this->columnSpacing;
|
||||
$this->galleryData['border']['enabled'] = $this->borderEnabled;
|
||||
$this->galleryData['border']['width'] = $this->borderWidth;
|
||||
$this->galleryData['border']['padding'] = $this->borderPadding;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Frontend\DataProcessing;
|
||||
|
||||
/*
|
||||
* 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\Routing\SiteMatcher;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
use TYPO3\CMS\Frontend\Utility\CanonicalizationUtility;
|
||||
|
||||
/**
|
||||
* This menu processor generates a json encoded menu string that will be
|
||||
* decoded again and assigned to FLUIDTEMPLATE as variable.
|
||||
*
|
||||
* Options:
|
||||
* if - TypoScript if condition
|
||||
* languages - A list of languages id's (e.g. 0,1,2) to use for the menu
|
||||
* creation or 'auto' to load from system or site languages
|
||||
* as - The variable to be used within the result
|
||||
*
|
||||
* Example TypoScript configuration:
|
||||
* 10 = TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor
|
||||
* 10 {
|
||||
* as = languagenavigation
|
||||
* }
|
||||
*/
|
||||
class LanguageMenuProcessor implements DataProcessorInterface
|
||||
{
|
||||
protected const LINK_PLACEHOLDER = '###LINKPLACEHOLDER###';
|
||||
|
||||
/**
|
||||
* The content object renderer
|
||||
*
|
||||
* @var ContentObjectRenderer
|
||||
*/
|
||||
public $cObj;
|
||||
|
||||
/**
|
||||
* The processor configuration
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $processorConfiguration;
|
||||
|
||||
/**
|
||||
* Allowed configuration keys for menu generation, other keys
|
||||
* will throw an exception to prevent configuration errors.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $allowedConfigurationKeys = [
|
||||
'if',
|
||||
'if.',
|
||||
'languages',
|
||||
'languages.',
|
||||
'as',
|
||||
'addQueryString',
|
||||
'addQueryString.'
|
||||
];
|
||||
|
||||
/**
|
||||
* Remove keys from configuration that should not be passed
|
||||
* to HMENU to prevent configuration errors
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $removeConfigurationKeysForHmenu = [
|
||||
'languages',
|
||||
'languages.',
|
||||
'as'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $menuConfig = [
|
||||
'special' => 'language',
|
||||
'addQueryString' => 1,
|
||||
'addQueryString.' => [
|
||||
'method' => 'GET'
|
||||
],
|
||||
'wrap' => '[|]'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $menuLevelConfig = [
|
||||
'doNotLinkIt' => '1',
|
||||
'wrapItemAndSub' => '{|}, |*| {|}, |*| {|}',
|
||||
'stdWrap.' => [
|
||||
'cObject' => 'COA',
|
||||
'cObject.' => [
|
||||
'1' => 'LOAD_REGISTER',
|
||||
'1.' => [
|
||||
'languageId.' => [
|
||||
'cObject' => 'TEXT',
|
||||
'cObject.' => [
|
||||
'value.' => [
|
||||
'data' => 'register:languages_HMENU'
|
||||
],
|
||||
'listNum.' => [
|
||||
'stdWrap.' => [
|
||||
'data' => 'register:count_HMENU_MENUOBJ',
|
||||
'wrap' => '|-1'
|
||||
],
|
||||
'splitChar' => ','
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'10' => 'TEXT',
|
||||
'10.' => [
|
||||
'stdWrap.' => [
|
||||
'data' => 'register:languageId'
|
||||
],
|
||||
'wrap' => '"languageId":|'
|
||||
],
|
||||
'11' => 'USER',
|
||||
'11.' => [
|
||||
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
|
||||
'language.' => [
|
||||
'data' => 'register:languageId'
|
||||
],
|
||||
'field' => 'locale',
|
||||
'stdWrap.' => [
|
||||
'wrap' => ',"locale":|'
|
||||
]
|
||||
],
|
||||
'20' => 'USER',
|
||||
'20.' => [
|
||||
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
|
||||
'language.' => [
|
||||
'data' => 'register:languageId'
|
||||
],
|
||||
'field' => 'title',
|
||||
'stdWrap.' => [
|
||||
'wrap' => ',"title":|'
|
||||
]
|
||||
],
|
||||
'21' => 'USER',
|
||||
'21.' => [
|
||||
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
|
||||
'language.' => [
|
||||
'data' => 'register:languageId'
|
||||
],
|
||||
'field' => 'navigationTitle',
|
||||
'stdWrap.' => [
|
||||
'wrap' => ',"navigationTitle":|'
|
||||
]
|
||||
],
|
||||
'22' => 'USER',
|
||||
'22.' => [
|
||||
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
|
||||
'language.' => [
|
||||
'data' => 'register:languageId'
|
||||
],
|
||||
'field' => 'twoLetterIsoCode',
|
||||
'stdWrap.' => [
|
||||
'wrap' => ',"twoLetterIsoCode":|'
|
||||
]
|
||||
],
|
||||
'23' => 'USER',
|
||||
'23.' => [
|
||||
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
|
||||
'language.' => [
|
||||
'data' => 'register:languageId'
|
||||
],
|
||||
'field' => 'hreflang',
|
||||
'stdWrap.' => [
|
||||
'wrap' => ',"hreflang":|'
|
||||
]
|
||||
],
|
||||
'24' => 'USER',
|
||||
'24.' => [
|
||||
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
|
||||
'language.' => [
|
||||
'data' => 'register:languageId'
|
||||
],
|
||||
'field' => 'direction',
|
||||
'stdWrap.' => [
|
||||
'wrap' => ',"direction":|'
|
||||
]
|
||||
],
|
||||
'90' => 'TEXT',
|
||||
'90.' => [
|
||||
'value' => self::LINK_PLACEHOLDER,
|
||||
'wrap' => ',"link":|',
|
||||
],
|
||||
'91' => 'TEXT',
|
||||
'91.' => [
|
||||
'value' => '0',
|
||||
'wrap' => ',"active":|'
|
||||
],
|
||||
'92' => 'TEXT',
|
||||
'92.' => [
|
||||
'value' => '0',
|
||||
'wrap' => ',"current":|'
|
||||
],
|
||||
'93' => 'TEXT',
|
||||
'93.' => [
|
||||
'value' => '1',
|
||||
'wrap' => ',"available":|'
|
||||
],
|
||||
'99' => 'RESTORE_REGISTER'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $menuDefaults = [
|
||||
'as' => 'languagemenu'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $menuTargetVariableName;
|
||||
|
||||
/**
|
||||
* @var ContentDataProcessor
|
||||
*/
|
||||
protected $contentDataProcessor;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration value from processorConfiguration
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function getConfigurationValue(string $key): string
|
||||
{
|
||||
return $this->cObj->stdWrapValue($key, $this->processorConfiguration, $this->menuDefaults[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TypoScriptFrontendController
|
||||
*/
|
||||
protected function getTypoScriptFrontendController(): TypoScriptFrontendController
|
||||
{
|
||||
return $GLOBALS['TSFE'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently configured "site" if a site is configured (= resolved) in the current request.
|
||||
*
|
||||
* @return SiteInterface
|
||||
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
|
||||
*/
|
||||
protected function getCurrentSite(): SiteInterface
|
||||
{
|
||||
$matcher = GeneralUtility::makeInstance(SiteMatcher::class);
|
||||
return $matcher->matchByPageId((int)$this->getTypoScriptFrontendController()->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON Encode
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
protected function jsonEncode($value): string
|
||||
{
|
||||
return json_encode($value, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function validateConfiguration()
|
||||
{
|
||||
$invalidArguments = [];
|
||||
foreach ($this->processorConfiguration as $key => $value) {
|
||||
if (!in_array($key, $this->allowedConfigurationKeys)) {
|
||||
$invalidArguments[str_replace('.', '', $key)] = $key;
|
||||
}
|
||||
}
|
||||
if (!empty($invalidArguments)) {
|
||||
throw new \InvalidArgumentException('LanguageMenuProcessor configuration contains invalid arguments: ' . implode(', ', $invalidArguments), 1522959188);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process languages and filter the configuration
|
||||
*/
|
||||
protected function prepareConfiguration(): void
|
||||
{
|
||||
$this->menuConfig = array_merge($this->menuConfig, $this->processorConfiguration);
|
||||
|
||||
// Process languages
|
||||
if (empty($this->menuConfig['languages']) && empty($this->menuConfig['languages.'])) {
|
||||
$this->menuConfig['special.']['value'] = 'auto';
|
||||
} elseif (!empty($this->menuConfig['languages.'])) {
|
||||
$this->menuConfig['special.']['value'] = $this->cObj->stdWrap($this->menuConfig['languages'], $this->menuConfig['languages.']);
|
||||
} else {
|
||||
$this->menuConfig['special.']['value'] = $this->menuConfig['languages'];
|
||||
}
|
||||
|
||||
// Filter configuration
|
||||
foreach ($this->menuConfig as $key => $value) {
|
||||
if (in_array($key, $this->removeConfigurationKeysForHmenu, true)) {
|
||||
unset($this->menuConfig[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$paramsToExclude = CanonicalizationUtility::getParamsToExcludeForCanonicalizedUrl(
|
||||
(int)$this->getTypoScriptFrontendController()->id,
|
||||
(array)$GLOBALS['TYPO3_CONF_VARS']['FE']['additionalCanonicalizedUrlParameters']
|
||||
);
|
||||
|
||||
$this->menuConfig['addQueryString.']['exclude'] = implode(
|
||||
',',
|
||||
array_merge(
|
||||
GeneralUtility::trimExplode(',', $this->menuConfig['addQueryString.']['exclude'] ?? '', true),
|
||||
$paramsToExclude
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the menu configuration so it can be treated by HMENU cObject
|
||||
*/
|
||||
protected function buildConfiguration(): void
|
||||
{
|
||||
$this->menuConfig['1'] = 'TMENU';
|
||||
$this->menuConfig['1.']['IProcFunc'] = LanguageMenuProcessor::class . '->replacePlaceholderInRenderedMenuItem';
|
||||
$this->menuConfig['1.']['NO'] = '1';
|
||||
$this->menuConfig['1.']['NO.'] = $this->menuLevelConfig;
|
||||
$this->menuConfig['1.']['ACT'] = $this->menuConfig['1.']['NO'];
|
||||
$this->menuConfig['1.']['ACT.'] = $this->menuConfig['1.']['NO.'];
|
||||
$this->menuConfig['1.']['ACT.']['stdWrap.']['cObject.']['91.']['value'] = '1';
|
||||
$this->menuConfig['1.']['CUR'] = $this->menuConfig['1.']['ACT'];
|
||||
$this->menuConfig['1.']['CUR.'] = $this->menuConfig['1.']['ACT.'];
|
||||
$this->menuConfig['1.']['CUR.']['stdWrap.']['cObject.']['92.']['value'] = '1';
|
||||
$this->menuConfig['1.']['USERDEF1'] = $this->menuConfig['1.']['NO'];
|
||||
$this->menuConfig['1.']['USERDEF1.'] = $this->menuConfig['1.']['NO.'];
|
||||
$this->menuConfig['1.']['USERDEF1.']['stdWrap.']['cObject.']['93.']['value'] = '0';
|
||||
$this->menuConfig['1.']['USERDEF2'] = $this->menuConfig['1.']['ACT'];
|
||||
$this->menuConfig['1.']['USERDEF2.'] = $this->menuConfig['1.']['ACT.'];
|
||||
$this->menuConfig['1.']['USERDEF2.']['stdWrap.']['cObject.']['93.']['value'] = '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and Build the menu configuration so it can be treated by HMENU cObject
|
||||
*/
|
||||
protected function validateAndBuildConfiguration(): void
|
||||
{
|
||||
// Validate Configuration
|
||||
$this->validateConfiguration();
|
||||
|
||||
// Build Configuration
|
||||
$this->prepareConfiguration();
|
||||
$this->buildConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContentObjectRenderer $cObj The data of the content element or page
|
||||
* @param array $contentObjectConfiguration The configuration of Content Object
|
||||
* @param array $processorConfiguration The configuration of this processor
|
||||
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
|
||||
* @return array the processed data as key/value store
|
||||
*/
|
||||
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData): array
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
$this->processorConfiguration = $processorConfiguration;
|
||||
|
||||
// Get Configuration
|
||||
$this->menuTargetVariableName = $this->getConfigurationValue('as');
|
||||
|
||||
// Validate and Build Configuration
|
||||
$this->validateAndBuildConfiguration();
|
||||
|
||||
// Process Configuration
|
||||
$menuContentObject = $cObj->getContentObject('HMENU');
|
||||
$renderedMenu = $menuContentObject->render($this->menuConfig);
|
||||
if ($renderedMenu) {
|
||||
// Process menu
|
||||
$menu = json_decode($renderedMenu, true);
|
||||
$processedMenu = [];
|
||||
|
||||
foreach ($menu as $key => $language) {
|
||||
$processedMenu[$key] = $language;
|
||||
}
|
||||
|
||||
$processedData[$this->menuTargetVariableName] = $processedMenu;
|
||||
}
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* This UserFunc gets the link and the target
|
||||
*
|
||||
* @param array $menuItem
|
||||
* @return array
|
||||
*/
|
||||
public function replacePlaceholderInRenderedMenuItem(array $menuItem): array
|
||||
{
|
||||
$link = $this->jsonEncode($menuItem['linkHREF']['HREF']);
|
||||
|
||||
$menuItem['parts']['title'] = str_replace(self::LINK_PLACEHOLDER, $link, $menuItem['parts']['title']);
|
||||
|
||||
return $menuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data from the field and language submitted by $conf in JSON format
|
||||
*
|
||||
* @param string Empty string (no content to process)
|
||||
* @param array TypoScript configuration
|
||||
* @return string JSON encoded data
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
|
||||
*/
|
||||
public function getFieldAsJson(string $content, array $conf): string
|
||||
{
|
||||
// Support of stdWrap for parameters
|
||||
if (isset($conf['language.'])) {
|
||||
$conf['language'] = $this->cObj->stdWrap($conf['language'], $conf['language.']);
|
||||
unset($conf['language.']);
|
||||
}
|
||||
if (isset($conf['field.'])) {
|
||||
$conf['field'] = $this->cObj->stdWrap($conf['field'], $conf['field.']);
|
||||
unset($conf['field.']);
|
||||
}
|
||||
|
||||
// Check required fields
|
||||
if ($conf['language'] === '') {
|
||||
throw new \InvalidArgumentException('Argument \'language\' must be supplied.', 1522959186);
|
||||
}
|
||||
if ($conf['field'] === '') {
|
||||
throw new \InvalidArgumentException('Argument \'field\' must be supplied.', 1522959187);
|
||||
}
|
||||
|
||||
// Get and check current site
|
||||
$site = $this->getCurrentSite();
|
||||
|
||||
// Throws InvalidArgumentException in case language is not found which is fine
|
||||
$language = $site->getLanguageById((int)$conf['language']);
|
||||
if ($language->enabled()) {
|
||||
$language = $language->toArray();
|
||||
} else {
|
||||
$language = null;
|
||||
}
|
||||
|
||||
// Check field for return exists
|
||||
if ($language !== null && !isset($language[$conf['field']])) {
|
||||
throw new \InvalidArgumentException('Invalid value \'' . $conf['field'] . '\' for argument \'field\' supplied.', 1524063160);
|
||||
}
|
||||
|
||||
return $this->jsonEncode($language[$conf['field']]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,550 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\DataProcessing;
|
||||
|
||||
/*
|
||||
* 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\Routing\SiteMatcher;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* This menu processor utilizes HMENU to generate a json encoded menu
|
||||
* string that will be decoded again and assigned to FLUIDTEMPLATE as
|
||||
* variable. Additional DataProcessing is supported and will be applied
|
||||
* to each record.
|
||||
*
|
||||
* Options:
|
||||
* as - The variable to be used within the result
|
||||
* levels - Number of levels of the menu
|
||||
* expandAll = If false, submenus will only render if the parent page is active
|
||||
* includeSpacer = If true, pagetype spacer will be included in the menu
|
||||
* titleField = Field that should be used for the title
|
||||
*
|
||||
* See HMENU docs for more options.
|
||||
* https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Hmenu/Index.html
|
||||
*
|
||||
*
|
||||
* Example TypoScript configuration:
|
||||
*
|
||||
* 10 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor
|
||||
* 10 {
|
||||
* special = list
|
||||
* special.value.field = pages
|
||||
* levels = 7
|
||||
* as = menu
|
||||
* expandAll = 1
|
||||
* includeSpacer = 1
|
||||
* titleField = nav_title // title
|
||||
* dataProcessing {
|
||||
* 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
|
||||
* 10 {
|
||||
* references.fieldName = media
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class MenuProcessor implements DataProcessorInterface
|
||||
{
|
||||
const LINK_PLACEHOLDER = '###LINKPLACEHOLDER###';
|
||||
const TARGET_PLACEHOLDER = '###TARGETPLACEHOLDER###';
|
||||
|
||||
/**
|
||||
* The content object renderer
|
||||
*
|
||||
* @var ContentObjectRenderer
|
||||
*/
|
||||
public $cObj;
|
||||
|
||||
/**
|
||||
* The processor configuration
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $processorConfiguration;
|
||||
|
||||
/**
|
||||
* Allowed configuration keys for menu generation, other keys
|
||||
* will throw an exception to prevent configuration errors.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $allowedConfigurationKeys = [
|
||||
'cache_period',
|
||||
'entryLevel',
|
||||
'entryLevel.',
|
||||
'special',
|
||||
'special.',
|
||||
'minItems',
|
||||
'minItems.',
|
||||
'maxItems',
|
||||
'maxItems.',
|
||||
'begin',
|
||||
'begin.',
|
||||
'alternativeSortingField',
|
||||
'alternativeSortingField.',
|
||||
'showAccessRestrictedPages',
|
||||
'showAccessRestrictedPages.',
|
||||
'excludeUidList',
|
||||
'excludeUidList.',
|
||||
'excludeDoktypes',
|
||||
'includeNotInMenu',
|
||||
'includeNotInMenu.',
|
||||
'alwaysActivePIDlist',
|
||||
'alwaysActivePIDlist.',
|
||||
'protectLvar',
|
||||
'addQueryString',
|
||||
'addQueryString.',
|
||||
'if',
|
||||
'if.',
|
||||
'levels',
|
||||
'levels.',
|
||||
'expandAll',
|
||||
'expandAll.',
|
||||
'includeSpacer',
|
||||
'includeSpacer.',
|
||||
'as',
|
||||
'titleField',
|
||||
'titleField.',
|
||||
'dataProcessing',
|
||||
'dataProcessing.'
|
||||
];
|
||||
|
||||
/**
|
||||
* Remove keys from configuration that should not be passed
|
||||
* to HMENU to prevent configuration errors
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $removeConfigurationKeysForHmenu = [
|
||||
'levels',
|
||||
'levels.',
|
||||
'expandAll',
|
||||
'expandAll.',
|
||||
'includeSpacer',
|
||||
'includeSpacer.',
|
||||
'as',
|
||||
'titleField',
|
||||
'titleField.',
|
||||
'dataProcessing',
|
||||
'dataProcessing.'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $menuConfig = [
|
||||
'wrap' => '[|]'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $menuLevelConfig = [
|
||||
'doNotLinkIt' => '1',
|
||||
'wrapItemAndSub' => '{|}, |*| {|}, |*| {|}',
|
||||
'stdWrap.' => [
|
||||
'cObject' => 'COA',
|
||||
'cObject.' => [
|
||||
'10' => 'USER',
|
||||
'10.' => [
|
||||
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\MenuProcessor->getDataAsJson',
|
||||
'stdWrap.' => [
|
||||
'wrap' => '"data":|'
|
||||
]
|
||||
],
|
||||
'20' => 'TEXT',
|
||||
'20.' => [
|
||||
'field' => 'nav_title // title',
|
||||
'trim' => '1',
|
||||
'wrap' => ',"title":|',
|
||||
'preUserFunc' => 'TYPO3\CMS\Frontend\DataProcessing\MenuProcessor->jsonEncodeUserFunc'
|
||||
],
|
||||
'21' => 'TEXT',
|
||||
'21.' => [
|
||||
'value' => self::LINK_PLACEHOLDER,
|
||||
'wrap' => ',"link":|',
|
||||
],
|
||||
'22' => 'TEXT',
|
||||
'22.' => [
|
||||
'value' => self::TARGET_PLACEHOLDER,
|
||||
'wrap' => ',"target":|',
|
||||
],
|
||||
'30' => 'TEXT',
|
||||
'30.' => [
|
||||
'value' => '0',
|
||||
'wrap' => ',"active":|'
|
||||
],
|
||||
'40' => 'TEXT',
|
||||
'40.' => [
|
||||
'value' => '0',
|
||||
'wrap' => ',"current":|'
|
||||
],
|
||||
'50' => 'TEXT',
|
||||
'50.' => [
|
||||
'value' => '0',
|
||||
'wrap' => ',"spacer":|'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $menuDefaults = [
|
||||
'levels' => 1,
|
||||
'expandAll' => 1,
|
||||
'includeSpacer' => 0,
|
||||
'as' => 'menu',
|
||||
'titleField' => 'nav_title // title'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $menuLevels;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $menuExpandAll;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $menuIncludeSpacer;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $menuTitleField;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $menuAlternativeSortingField;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $menuTargetVariableName;
|
||||
|
||||
/**
|
||||
* @var ContentDataProcessor
|
||||
*/
|
||||
protected $contentDataProcessor;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration value from processorConfiguration
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function getConfigurationValue($key)
|
||||
{
|
||||
return $this->cObj->stdWrapValue($key, $this->processorConfiguration, $this->menuDefaults[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate configuration
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function validateConfiguration()
|
||||
{
|
||||
$invalidArguments = [];
|
||||
foreach ($this->processorConfiguration as $key => $value) {
|
||||
if (!in_array($key, $this->allowedConfigurationKeys)) {
|
||||
$invalidArguments[str_replace('.', '', $key)] = $key;
|
||||
}
|
||||
}
|
||||
if (!empty($invalidArguments)) {
|
||||
throw new \InvalidArgumentException('MenuProcessor Configuration contains invalid Arguments: ' . implode(', ', $invalidArguments), 1478806566);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare Configuration
|
||||
*/
|
||||
public function prepareConfiguration()
|
||||
{
|
||||
$this->menuConfig += $this->processorConfiguration;
|
||||
// Filter configuration
|
||||
foreach ($this->menuConfig as $key => $value) {
|
||||
if (in_array($key, $this->removeConfigurationKeysForHmenu)) {
|
||||
unset($this->menuConfig[$key]);
|
||||
}
|
||||
}
|
||||
// Process special value
|
||||
if (isset($this->menuConfig['special.']['value.'])) {
|
||||
$this->menuConfig['special.']['value'] = $this->cObj->stdWrap($this->menuConfig['special.']['value'], $this->menuConfig['special.']['value.']);
|
||||
unset($this->menuConfig['special.']['value.']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare configuration for a certain menu level in the hierarchy
|
||||
*/
|
||||
public function prepareLevelConfiguration()
|
||||
{
|
||||
$this->menuLevelConfig['stdWrap.']['cObject.'] = array_replace_recursive(
|
||||
$this->menuLevelConfig['stdWrap.']['cObject.'],
|
||||
[
|
||||
'20.' => [
|
||||
'field' => $this->menuTitleField,
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the configuration when rendering a language menu
|
||||
*/
|
||||
public function prepareLevelLanguageConfiguration()
|
||||
{
|
||||
if ($this->menuConfig['special'] === 'language') {
|
||||
$languageUids = $this->menuConfig['special.']['value'];
|
||||
if ($this->menuConfig['special.']['value'] === 'auto') {
|
||||
$site = $this->getCurrentSite();
|
||||
$languageUids = implode(',', array_keys($site->getLanguages()));
|
||||
}
|
||||
$this->menuLevelConfig['stdWrap.']['cObject.'] = array_replace_recursive(
|
||||
$this->menuLevelConfig['stdWrap.']['cObject.'],
|
||||
[
|
||||
'60' => 'TEXT',
|
||||
'60.' => [
|
||||
'value' => '1',
|
||||
'wrap' => ',"available":|'
|
||||
],
|
||||
'70' => 'TEXT',
|
||||
'70.' => [
|
||||
'value' => $languageUids,
|
||||
'listNum.' => [
|
||||
'stdWrap.' => [
|
||||
'data' => 'register:count_HMENU_MENUOBJ',
|
||||
'wrap' => '|-1'
|
||||
],
|
||||
'splitChar' => ','
|
||||
],
|
||||
'wrap' => ',"languageUid":"|"'
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the menu configuration so it can be treated by HMENU cObject
|
||||
*/
|
||||
public function buildConfiguration()
|
||||
{
|
||||
for ($i = 1; $i <= $this->menuLevels; $i++) {
|
||||
$this->menuConfig[$i] = 'TMENU';
|
||||
$this->menuConfig[$i . '.']['IProcFunc'] = 'TYPO3\CMS\Frontend\DataProcessing\MenuProcessor->replacePlaceholderInRenderedMenuItem';
|
||||
if ($i > 1) {
|
||||
$this->menuConfig[$i . '.']['stdWrap.']['wrap'] = ',"children": [|]';
|
||||
}
|
||||
if (array_key_exists('showAccessRestrictedPages', $this->menuConfig)) {
|
||||
$this->menuConfig[$i . '.']['showAccessRestrictedPages'] = $this->menuConfig['showAccessRestrictedPages'];
|
||||
if (array_key_exists('showAccessRestrictedPages.', $this->menuConfig)
|
||||
&& is_array($this->menuConfig['showAccessRestrictedPages.'])) {
|
||||
$this->menuConfig[$i . '.']['showAccessRestrictedPages.'] = $this->menuConfig['showAccessRestrictedPages.'];
|
||||
}
|
||||
}
|
||||
$this->menuConfig[$i . '.']['expAll'] = $this->menuExpandAll;
|
||||
$this->menuConfig[$i . '.']['alternativeSortingField'] = $this->menuAlternativeSortingField;
|
||||
$this->menuConfig[$i . '.']['NO'] = '1';
|
||||
$this->menuConfig[$i . '.']['NO.'] = $this->menuLevelConfig;
|
||||
if ($this->menuIncludeSpacer) {
|
||||
$this->menuConfig[$i . '.']['SPC'] = '1';
|
||||
$this->menuConfig[$i . '.']['SPC.'] = $this->menuConfig[$i . '.']['NO.'];
|
||||
$this->menuConfig[$i . '.']['SPC.']['stdWrap.']['cObject.']['50.']['value'] = '1';
|
||||
}
|
||||
$this->menuConfig[$i . '.']['IFSUB'] = '1';
|
||||
$this->menuConfig[$i . '.']['IFSUB.'] = $this->menuConfig[$i . '.']['NO.'];
|
||||
$this->menuConfig[$i . '.']['ACT'] = '1';
|
||||
$this->menuConfig[$i . '.']['ACT.'] = $this->menuConfig[$i . '.']['NO.'];
|
||||
$this->menuConfig[$i . '.']['ACT.']['stdWrap.']['cObject.']['30.']['value'] = '1';
|
||||
$this->menuConfig[$i . '.']['ACTIFSUB'] = '1';
|
||||
$this->menuConfig[$i . '.']['ACTIFSUB.'] = $this->menuConfig[$i . '.']['ACT.'];
|
||||
$this->menuConfig[$i . '.']['CUR'] = '1';
|
||||
$this->menuConfig[$i . '.']['CUR.'] = $this->menuConfig[$i . '.']['ACT.'];
|
||||
$this->menuConfig[$i . '.']['CUR.']['stdWrap.']['cObject.']['40.']['value'] = '1';
|
||||
$this->menuConfig[$i . '.']['CURIFSUB'] = '1';
|
||||
$this->menuConfig[$i . '.']['CURIFSUB.'] = $this->menuConfig[$i . '.']['CUR.'];
|
||||
if ($this->menuConfig['special'] === 'language') {
|
||||
$this->menuConfig[$i . '.']['USERDEF1'] = $this->menuConfig[$i . '.']['NO'];
|
||||
$this->menuConfig[$i . '.']['USERDEF1.'] = $this->menuConfig[$i . '.']['NO.'];
|
||||
$this->menuConfig[$i . '.']['USERDEF1.']['stdWrap.']['cObject.']['60.']['value'] = '0';
|
||||
$this->menuConfig[$i . '.']['USERDEF2'] = $this->menuConfig[$i . '.']['ACT'];
|
||||
$this->menuConfig[$i . '.']['USERDEF2.'] = $this->menuConfig[$i . '.']['ACT.'];
|
||||
$this->menuConfig[$i . '.']['USERDEF2.']['stdWrap.']['cObject.']['60.']['value'] = '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContentObjectRenderer $cObj The data of the content element or page
|
||||
* @param array $contentObjectConfiguration The configuration of Content Object
|
||||
* @param array $processorConfiguration The configuration of this processor
|
||||
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
|
||||
* @return array the processed data as key/value store
|
||||
*/
|
||||
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
$this->processorConfiguration = $processorConfiguration;
|
||||
|
||||
// Get Configuration
|
||||
$this->menuLevels = (int)$this->getConfigurationValue('levels') ?: 1;
|
||||
$this->menuExpandAll = (int)$this->getConfigurationValue('expandAll');
|
||||
$this->menuIncludeSpacer = (int)$this->getConfigurationValue('includeSpacer');
|
||||
$this->menuTargetVariableName = $this->getConfigurationValue('as');
|
||||
$this->menuTitleField = $this->getConfigurationValue('titleField');
|
||||
$this->menuAlternativeSortingField = $this->getConfigurationValue('alternativeSortingField');
|
||||
|
||||
// Validate Configuration
|
||||
$this->validateConfiguration();
|
||||
|
||||
// Build Configuration
|
||||
$this->prepareConfiguration();
|
||||
$this->prepareLevelConfiguration();
|
||||
$this->prepareLevelLanguageConfiguration();
|
||||
$this->buildConfiguration();
|
||||
|
||||
// Process Configuration
|
||||
$menuContentObject = $cObj->getContentObject('HMENU');
|
||||
$renderedMenu = $menuContentObject->render($this->menuConfig);
|
||||
if (!$renderedMenu) {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
// Process menu
|
||||
$menu = json_decode($renderedMenu, true);
|
||||
$processedMenu = [];
|
||||
|
||||
foreach ($menu as $key => $page) {
|
||||
$processedMenu[$key] = $this->processAdditionalDataProcessors($page, $processorConfiguration);
|
||||
}
|
||||
|
||||
// Return processed data
|
||||
$processedData[$this->menuTargetVariableName] = $processedMenu;
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process additional data processors
|
||||
*
|
||||
* @param array $page
|
||||
* @param array $processorConfiguration
|
||||
* @return array
|
||||
*/
|
||||
protected function processAdditionalDataProcessors($page, $processorConfiguration)
|
||||
{
|
||||
if (is_array($page['children'])) {
|
||||
foreach ($page['children'] as $key => $item) {
|
||||
$page['children'][$key] = $this->processAdditionalDataProcessors($item, $processorConfiguration);
|
||||
}
|
||||
}
|
||||
/** @var ContentObjectRenderer $recordContentObjectRenderer */
|
||||
$recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$recordContentObjectRenderer->start($page['data'], 'pages');
|
||||
$processedPage = $this->contentDataProcessor->process($recordContentObjectRenderer, $processorConfiguration, $page);
|
||||
return $processedPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data of the current record in JSON format
|
||||
*
|
||||
* @return string JSON encoded data
|
||||
*/
|
||||
public function getDataAsJson()
|
||||
{
|
||||
return $this->jsonEncode($this->cObj->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* This UserFunc encodes the content as Json
|
||||
*
|
||||
* @param string $content
|
||||
* @param array $conf
|
||||
* @return string JSON encoded content
|
||||
*/
|
||||
public function jsonEncodeUserFunc($content, $conf)
|
||||
{
|
||||
$content = $this->jsonEncode($content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON Encode
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
public function jsonEncode($value)
|
||||
{
|
||||
return json_encode($value, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This UserFunc gets the link and the target
|
||||
*
|
||||
* @param array $menuItem
|
||||
* @param array $conf
|
||||
* @return array
|
||||
*/
|
||||
public function replacePlaceholderInRenderedMenuItem($menuItem, $conf)
|
||||
{
|
||||
$link = $this->jsonEncode($menuItem['linkHREF']['HREF']);
|
||||
$target = $this->jsonEncode($menuItem['linkHREF']['TARGET']);
|
||||
|
||||
$menuItem['parts']['title'] = str_replace(self::LINK_PLACEHOLDER, $link, $menuItem['parts']['title']);
|
||||
$menuItem['parts']['title'] = str_replace(self::TARGET_PLACEHOLDER, $target, $menuItem['parts']['title']);
|
||||
|
||||
return $menuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently configured "site" if a site is configured (= resolved) in the current request.
|
||||
*
|
||||
* @return SiteInterface
|
||||
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
|
||||
*/
|
||||
protected function getCurrentSite(): SiteInterface
|
||||
{
|
||||
$matcher = GeneralUtility::makeInstance(SiteMatcher::class);
|
||||
return $matcher->matchByPageId((int)$this->getTypoScriptFrontendController()->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TypoScriptFrontendController
|
||||
*/
|
||||
protected function getTypoScriptFrontendController()
|
||||
{
|
||||
return $GLOBALS['TSFE'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\DataProcessing;
|
||||
|
||||
/*
|
||||
* 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\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Routing\SiteMatcher;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
|
||||
/**
|
||||
* Fetch the site object containing all information about the current site
|
||||
*
|
||||
* Example TypoScript configuration:
|
||||
*
|
||||
* 10 = TYPO3\CMS\Frontend\DataProcessing\SiteProcessor
|
||||
* 10 {
|
||||
* as = site
|
||||
* }
|
||||
*
|
||||
* where "as" names the variable containing the site object
|
||||
*/
|
||||
class SiteProcessor implements DataProcessorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param ContentObjectRenderer $cObj The data of the content element or page
|
||||
* @param array $contentObjectConfiguration The configuration of Content Object
|
||||
* @param array $processorConfiguration The configuration of this processor
|
||||
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
|
||||
* @return array the processed data as key/value store
|
||||
*/
|
||||
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData): array
|
||||
{
|
||||
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'site');
|
||||
$processedData[$targetVariableName] = $this->getCurrentSite();
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently configured "site" if a site is configured (= resolved) in the current request.
|
||||
*
|
||||
* @return SiteInterface|null
|
||||
*/
|
||||
protected function getCurrentSite(): ?SiteInterface
|
||||
{
|
||||
try {
|
||||
return $this->getMatcher()->matchByPageId($this->getCurrentPageId());
|
||||
} catch (SiteNotFoundException $e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SiteMatcher
|
||||
*/
|
||||
protected function getMatcher(): SiteMatcher
|
||||
{
|
||||
return GeneralUtility::makeInstance(SiteMatcher::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
protected function getCurrentPageId(): int
|
||||
{
|
||||
return (int)$GLOBALS['TSFE']->id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\DataProcessing;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
|
||||
/**
|
||||
* This data processor can be used for processing data for the content elements which have split contents in one field
|
||||
* like e.g. "bullets". It will split the field data in an array ready to be iterated over in Fluid.
|
||||
*
|
||||
* Example field data:
|
||||
*
|
||||
* This is bullet 1, This is bullet 2, This is bullet 3
|
||||
*
|
||||
* Example TypoScript configuration:
|
||||
*
|
||||
* 10 = TYPO3\CMS\Frontend\DataProcessing\SplitProcessor
|
||||
* 10 {
|
||||
* if.isTrue.field = bodytext
|
||||
* delimiter = ,
|
||||
* fieldName = bodytext
|
||||
* removeEmptyEntries = 1
|
||||
* filterIntegers = 1
|
||||
* filterUnique = 1
|
||||
* as = bullets
|
||||
* }
|
||||
*
|
||||
* whereas "bullets" can be used as a variable {bullets} inside Fluid for iteration.
|
||||
*/
|
||||
class SplitProcessor implements DataProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Process field data to split in an array
|
||||
*
|
||||
* @param ContentObjectRenderer $cObj The data of the content element or page
|
||||
* @param array $contentObjectConfiguration The configuration of Content Object
|
||||
* @param array $processorConfiguration The configuration of this processor
|
||||
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
|
||||
* @return array the processed data as key/value store
|
||||
*/
|
||||
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
|
||||
{
|
||||
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
// The field name to process
|
||||
$fieldName = $cObj->stdWrapValue('fieldName', $processorConfiguration);
|
||||
if (empty($fieldName)) {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
$originalValue = $cObj->data[$fieldName];
|
||||
|
||||
// Set the target variable
|
||||
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, $fieldName);
|
||||
|
||||
// Set the delimiter which is "LF" by default
|
||||
$delimiter = $cObj->stdWrapValue('delimiter', $processorConfiguration, LF);
|
||||
|
||||
// Filter integers
|
||||
$filterIntegers = (bool)$cObj->stdWrapValue('filterIntegers', $processorConfiguration, false);
|
||||
|
||||
// Filter unique
|
||||
$filterUnique = (bool)$cObj->stdWrapValue('filterUnique', $processorConfiguration, false);
|
||||
|
||||
// Remove empty entries
|
||||
$removeEmptyEntries = (bool)$cObj->stdWrapValue('removeEmptyEntries', $processorConfiguration, false);
|
||||
|
||||
if ($filterIntegers === true) {
|
||||
$processedData[$targetVariableName] = GeneralUtility::intExplode($delimiter, $originalValue, $removeEmptyEntries);
|
||||
} else {
|
||||
$processedData[$targetVariableName] = GeneralUtility::trimExplode($delimiter, $originalValue, $removeEmptyEntries);
|
||||
}
|
||||
|
||||
if ($filterUnique === true) {
|
||||
$processedData[$targetVariableName] = array_unique($processedData[$targetVariableName]);
|
||||
}
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend;
|
||||
|
||||
/*
|
||||
* 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\Exception as CoreException;
|
||||
|
||||
/**
|
||||
* A generic Frontend exception
|
||||
*/
|
||||
class Exception extends CoreException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Frontend\Hooks;
|
||||
|
||||
/*
|
||||
* 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\Localization\LanguageService;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* Uses frontend hooks to show preview information
|
||||
* @internal this is a concrete TYPO3 hook implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class FrontendHooks
|
||||
{
|
||||
/**
|
||||
* Include the preview block in case we're looking at a hidden page
|
||||
* in the LIVE workspace
|
||||
*
|
||||
* @param array $params
|
||||
* @param TypoScriptFrontendController $controller
|
||||
*/
|
||||
public function displayPreviewInfoMessage($params, TypoScriptFrontendController $controller)
|
||||
{
|
||||
if (!$controller->fePreview || $controller->doWorkspacePreview() || ($controller->config['config']['disablePreviewNotification'] ?? false)) {
|
||||
return;
|
||||
}
|
||||
if ($controller->config['config']['message_preview']) {
|
||||
$message = $controller->config['config']['message_preview'];
|
||||
} else {
|
||||
$label = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_tsfe.xlf:preview');
|
||||
$styles = [];
|
||||
$styles[] = 'position: fixed';
|
||||
$styles[] = 'top: 15px';
|
||||
$styles[] = 'right: 15px';
|
||||
$styles[] = 'padding: 8px 18px';
|
||||
$styles[] = 'background: #fff3cd';
|
||||
$styles[] = 'border: 1px solid #ffeeba';
|
||||
$styles[] = 'font-family: sans-serif';
|
||||
$styles[] = 'font-size: 14px';
|
||||
$styles[] = 'font-weight: bold';
|
||||
$styles[] = 'color: #856404';
|
||||
$styles[] = 'z-index: 20000';
|
||||
$styles[] = 'user-select: none';
|
||||
$styles[] = 'pointer-events: none';
|
||||
$styles[] = 'text-align: center';
|
||||
$styles[] = 'border-radius: 2px';
|
||||
$message = '<div id="typo3-preview-info" style="' . implode(';', $styles) . '">' . htmlspecialchars($label) . '</div>';
|
||||
}
|
||||
if (!empty($message)) {
|
||||
$controller->content = str_ireplace('</body>', $message . '</body>', $controller->content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Hooks;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Adds extra fields into 'media' flexform
|
||||
* @internal this is a concrete TYPO3 hook implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class MediaItemHooks implements \TYPO3\CMS\Core\SingletonInterface
|
||||
{
|
||||
/**
|
||||
* Load extra render types if they exist
|
||||
*
|
||||
* @param array $params Existing types by reference
|
||||
* @param array $conf Config array
|
||||
*/
|
||||
public function customMediaRenderTypes(&$params, $conf)
|
||||
{
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/hooks/class.tx_cms_mediaitems.php']['customMediaRenderTypes'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
$hookObj->customMediaRenderTypes($params, $conf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load extra predefined media params if they exist
|
||||
*
|
||||
* @param array $params Existing types by reference
|
||||
* @param array $conf Config array
|
||||
*/
|
||||
public function customMediaParams(&$params, $conf)
|
||||
{
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/hooks/class.tx_cms_mediaitems.php']['customMediaParams'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
$hookObj->customMediaParams($params, $conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Hooks\PageLayoutView;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains a preview rendering for the page module of CType="image"
|
||||
* @internal this is a concrete TYPO3 hook implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class ImagePreviewRenderer implements \TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface
|
||||
{
|
||||
/**
|
||||
* Preprocesses the preview rendering of the content element "image".
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\View\PageLayoutView $parentObject Calling parent object
|
||||
* @param bool $drawItem Whether to draw the item using the default functionalities
|
||||
* @param string $headerContent Header content
|
||||
* @param string $itemContent Item content
|
||||
* @param array $row Record row of tt_content
|
||||
*/
|
||||
public function preProcess(
|
||||
\TYPO3\CMS\Backend\View\PageLayoutView &$parentObject,
|
||||
&$drawItem,
|
||||
&$headerContent,
|
||||
&$itemContent,
|
||||
array &$row
|
||||
) {
|
||||
if ($row['CType'] === 'image') {
|
||||
if ($row['image']) {
|
||||
$itemContent .= $parentObject->linkEditContent($parentObject->getThumbCodeUnlinked($row, 'tt_content', 'image'), $row);
|
||||
|
||||
$fileReferences = \TYPO3\CMS\Backend\Utility\BackendUtility::resolveFileReferences('tt_content', 'image', $row);
|
||||
|
||||
if (!empty($fileReferences)) {
|
||||
$linkedContent = '';
|
||||
|
||||
foreach ($fileReferences as $fileReference) {
|
||||
$description = $fileReference->getDescription();
|
||||
if ($description !== null && $description !== '') {
|
||||
$linkedContent .= htmlspecialchars($description) . '<br />';
|
||||
}
|
||||
}
|
||||
|
||||
$itemContent .= $parentObject->linkEditContent($linkedContent, $row);
|
||||
|
||||
unset($linkedContent);
|
||||
}
|
||||
}
|
||||
|
||||
$drawItem = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Hooks\PageLayoutView;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains a preview rendering for the page module of CType="text"
|
||||
* @internal this is a concrete TYPO3 hook implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class TextPreviewRenderer implements \TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface
|
||||
{
|
||||
/**
|
||||
* Preprocesses the preview rendering of the content element "text".
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\View\PageLayoutView $parentObject Calling parent object
|
||||
* @param bool $drawItem Whether to draw the item using the default functionalities
|
||||
* @param string $headerContent Header content
|
||||
* @param string $itemContent Item content
|
||||
* @param array $row Record row of tt_content
|
||||
*/
|
||||
public function preProcess(
|
||||
\TYPO3\CMS\Backend\View\PageLayoutView &$parentObject,
|
||||
&$drawItem,
|
||||
&$headerContent,
|
||||
&$itemContent,
|
||||
array &$row
|
||||
) {
|
||||
if ($row['CType'] === 'text') {
|
||||
if ($row['bodytext']) {
|
||||
$itemContent .= $parentObject->linkEditContent($parentObject->renderText($row['bodytext']), $row) . '<br />';
|
||||
}
|
||||
|
||||
$drawItem = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Hooks\PageLayoutView;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\PageLayoutView;
|
||||
use TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface;
|
||||
|
||||
/**
|
||||
* Contains a preview rendering for the page module of CType="textmedia"
|
||||
* @internal this is a concrete TYPO3 hook implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class TextmediaPreviewRenderer implements PageLayoutViewDrawItemHookInterface
|
||||
{
|
||||
/**
|
||||
* Preprocesses the preview rendering of a content element of type "textmedia"
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\View\PageLayoutView $parentObject Calling parent object
|
||||
* @param bool $drawItem Whether to draw the item using the default functionality
|
||||
* @param string $headerContent Header content
|
||||
* @param string $itemContent Item content
|
||||
* @param array $row Record row of tt_content
|
||||
*/
|
||||
public function preProcess(
|
||||
PageLayoutView &$parentObject,
|
||||
&$drawItem,
|
||||
&$headerContent,
|
||||
&$itemContent,
|
||||
array &$row
|
||||
) {
|
||||
if ($row['CType'] === 'textmedia') {
|
||||
if ($row['bodytext']) {
|
||||
$itemContent .= $parentObject->linkEditContent($parentObject->renderText($row['bodytext']), $row) . '<br />';
|
||||
}
|
||||
|
||||
if ($row['assets']) {
|
||||
$itemContent .= $parentObject->linkEditContent($parentObject->getThumbCodeUnlinked($row, 'tt_content', 'assets'), $row) . '<br />';
|
||||
|
||||
$fileReferences = BackendUtility::resolveFileReferences('tt_content', 'assets', $row);
|
||||
|
||||
if (!empty($fileReferences)) {
|
||||
$linkedContent = '';
|
||||
|
||||
foreach ($fileReferences as $fileReference) {
|
||||
$description = $fileReference->getDescription();
|
||||
if ($description !== null && $description !== '') {
|
||||
$linkedContent .= htmlspecialchars($description) . '<br />';
|
||||
}
|
||||
}
|
||||
|
||||
$itemContent .= $parentObject->linkEditContent($linkedContent, $row);
|
||||
|
||||
unset($linkedContent);
|
||||
}
|
||||
}
|
||||
|
||||
$drawItem = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Hooks\PageLayoutView;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains a preview rendering for the page module of CType="textpic"
|
||||
* @internal this is a concrete TYPO3 hook implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class TextpicPreviewRenderer implements \TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface
|
||||
{
|
||||
/**
|
||||
* Preprocesses the preview rendering of the content element "textpic".
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\View\PageLayoutView $parentObject Calling parent object
|
||||
* @param bool $drawItem Whether to draw the item using the default functionalities
|
||||
* @param string $headerContent Header content
|
||||
* @param string $itemContent Item content
|
||||
* @param array $row Record row of tt_content
|
||||
*/
|
||||
public function preProcess(
|
||||
\TYPO3\CMS\Backend\View\PageLayoutView &$parentObject,
|
||||
&$drawItem,
|
||||
&$headerContent,
|
||||
&$itemContent,
|
||||
array &$row
|
||||
) {
|
||||
if ($row['CType'] === 'textpic') {
|
||||
if ($row['bodytext']) {
|
||||
$itemContent .= $parentObject->linkEditContent($parentObject->renderText($row['bodytext']), $row) . '<br />';
|
||||
}
|
||||
|
||||
if ($row['image']) {
|
||||
$itemContent .= $parentObject->linkEditContent($parentObject->getThumbCodeUnlinked($row, 'tt_content', 'image'), $row);
|
||||
|
||||
$fileReferences = \TYPO3\CMS\Backend\Utility\BackendUtility::resolveFileReferences('tt_content', 'image', $row);
|
||||
|
||||
if (!empty($fileReferences)) {
|
||||
$linkedContent = '';
|
||||
|
||||
foreach ($fileReferences as $fileReference) {
|
||||
$description = $fileReference->getDescription();
|
||||
if ($description !== null && $description !== '') {
|
||||
$linkedContent .= htmlspecialchars($description) . '<br />';
|
||||
}
|
||||
}
|
||||
|
||||
$itemContent .= $parentObject->linkEditContent($linkedContent, $row);
|
||||
|
||||
unset($linkedContent);
|
||||
}
|
||||
}
|
||||
|
||||
$drawItem = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Hooks;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Hooks / manipulation data for TCA columns e.g. to sort items within itemsProcFunc
|
||||
* @internal this is a concrete TYPO3 hook implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class TableColumnHooks
|
||||
{
|
||||
/**
|
||||
* sort list items (used for plugins, list_type) by name
|
||||
* @param array $parameters
|
||||
*/
|
||||
public function sortPluginList(array &$parameters)
|
||||
{
|
||||
@usort(
|
||||
$parameters['items'],
|
||||
function ($item1, $item2) {
|
||||
return strcasecmp($this->getLanguageService()->sL($item1[0]), $this->getLanguageService()->sL($item2[0]));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns LanguageService
|
||||
*
|
||||
* @return \TYPO3\CMS\Core\Localization\LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Hooks;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Class that hooks into DataHandler and listens for updates to pages to update the
|
||||
* treelist cache
|
||||
* @internal this is a concrete TYPO3 hook implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class TreelistCacheUpdateHooks
|
||||
{
|
||||
/**
|
||||
* Should not be manipulated from others except through the
|
||||
* configuration provided @see __construct()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $updateRequiringFields = [
|
||||
'pid',
|
||||
'php_tree_stop',
|
||||
'extendToSubpages'
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor, adds update requiring fields to the default ones
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// As enableFields can be set dynamically we add them here
|
||||
$pagesEnableFields = $GLOBALS['TCA']['pages']['ctrl']['enablecolumns'];
|
||||
foreach ($pagesEnableFields as $pagesEnableField) {
|
||||
$this->updateRequiringFields[] = $pagesEnableField;
|
||||
}
|
||||
$this->updateRequiringFields[] = $GLOBALS['TCA']['pages']['ctrl']['delete'];
|
||||
// Extension can add fields to the pages table that require an
|
||||
// update of the treelist cache, too; so we also add those
|
||||
// example: $TYPO3_CONF_VARS['BE']['additionalTreelistUpdateFields'] .= ',my_field';
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'])) {
|
||||
$additionalTreelistUpdateFields = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'], true);
|
||||
$this->updateRequiringFields = array_merge($this->updateRequiringFields, $additionalTreelistUpdateFields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* waits for DataHandler commands and looks for changed pages, if found further
|
||||
* changes take place to determine whether the cache needs to be updated
|
||||
*
|
||||
* @param string $status DataHandler operation status, either 'new' or 'update'
|
||||
* @param string $table The DB table the operation was carried out on
|
||||
* @param mixed $recordId The record's uid for update records, a string to look the record's uid up after it has been created
|
||||
* @param array $updatedFields Array of changed fields and their new values
|
||||
* @param DataHandler $dataHandler DataHandler parent object
|
||||
*/
|
||||
public function processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, DataHandler $dataHandler)
|
||||
{
|
||||
if ($table === 'pages' && $this->requiresUpdate($updatedFields)) {
|
||||
$affectedPagePid = 0;
|
||||
$affectedPageUid = 0;
|
||||
if ($status === 'new') {
|
||||
// Detect new pages
|
||||
// Resolve the uid
|
||||
$affectedPageUid = $dataHandler->substNEWwithIDs[$recordId];
|
||||
$affectedPagePid = $updatedFields['pid'];
|
||||
} elseif ($status === 'update') {
|
||||
// Detect updated pages
|
||||
$affectedPageUid = $recordId;
|
||||
// When updating a page the pid is not directly available so we
|
||||
// need to retrieve it ourselves.
|
||||
$fullPageRecord = BackendUtility::getRecord($table, $recordId);
|
||||
$affectedPagePid = $fullPageRecord['pid'];
|
||||
}
|
||||
$clearCacheActions = $this->determineClearCacheActions($status, $updatedFields);
|
||||
$this->processClearCacheActions($affectedPageUid, $affectedPagePid, $updatedFields, $clearCacheActions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for DataHandler commands and looks for deleted pages or swapped pages, if found
|
||||
* further changes take place to determine whether the cache needs to be updated
|
||||
*
|
||||
* @param string $command The TCE command
|
||||
* @param string $table The record's table
|
||||
* @param int $recordId The record's uid
|
||||
* @param array $commandValue The commands value, typically an array with more detailed command information
|
||||
* @param DataHandler $dataHandler The DataHandler parent object
|
||||
*/
|
||||
public function processCmdmap_postProcess($command, $table, $recordId, $commandValue, DataHandler $dataHandler)
|
||||
{
|
||||
$action = (is_array($commandValue) && isset($commandValue['action'])) ? (string)$commandValue['action'] : '';
|
||||
if ($table === 'pages' && ($command === 'delete' || ($command === 'version' && $action === 'swap'))) {
|
||||
$affectedRecord = BackendUtility::getRecord($table, $recordId, '*', '', false);
|
||||
$affectedPageUid = $affectedRecord['uid'];
|
||||
$affectedPagePid = $affectedRecord['pid'];
|
||||
|
||||
// Faking the updated fields
|
||||
$updatedFields = [];
|
||||
if ($command === 'delete') {
|
||||
$updatedFields['deleted'] = 1;
|
||||
} else {
|
||||
// page was published to live (swapped)
|
||||
$updatedFields['t3ver_wsid'] = 0;
|
||||
}
|
||||
$clearCacheActions = $this->determineClearCacheActions(
|
||||
'update',
|
||||
$updatedFields
|
||||
);
|
||||
|
||||
$this->processClearCacheActions($affectedPageUid, $affectedPagePid, $updatedFields, $clearCacheActions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* waits for DataHandler commands and looks for moved pages, if found further
|
||||
* changes take place to determine whether the cache needs to be updated
|
||||
*
|
||||
* @param string $table Table name of the moved record
|
||||
* @param int $recordId The record's uid
|
||||
* @param int $destinationPid The record's destination page id
|
||||
* @param array $movedRecord The record that moved
|
||||
* @param array $updatedFields Array of changed fields
|
||||
* @param DataHandler $dataHandler DataHandler parent object
|
||||
*/
|
||||
public function moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
|
||||
{
|
||||
if ($table === 'pages' && $this->requiresUpdate($updatedFields)) {
|
||||
$affectedPageUid = $recordId;
|
||||
$affectedPageOldPid = $movedRecord['pid'];
|
||||
$affectedPageNewPid = $updatedFields['pid'];
|
||||
$clearCacheActions = $this->determineClearCacheActions('update', $updatedFields);
|
||||
// Clear treelist entries for old parent page
|
||||
$this->processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
|
||||
// Clear treelist entries for new parent page
|
||||
$this->processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for DataHandler commands and looks for moved pages, if found further
|
||||
* changes take place to determine whether the cache needs to be updated
|
||||
*
|
||||
* @param string $table Table name of the moved record
|
||||
* @param int $recordId The record's uid
|
||||
* @param int $destinationPid The record's destination page id
|
||||
* @param int $originalDestinationPid (negative) page id th page has been moved after
|
||||
* @param array $movedRecord The record that moved
|
||||
* @param array $updatedFields Array of changed fields
|
||||
* @param DataHandler $dataHandler DataHandler parent object
|
||||
*/
|
||||
public function moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
|
||||
{
|
||||
if ($table === 'pages' && $this->requiresUpdate($updatedFields)) {
|
||||
$affectedPageUid = $recordId;
|
||||
$affectedPageOldPid = $movedRecord['pid'];
|
||||
$affectedPageNewPid = $updatedFields['pid'];
|
||||
$clearCacheActions = $this->determineClearCacheActions('update', $updatedFields);
|
||||
// Clear treelist entries for old parent page
|
||||
$this->processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
|
||||
// Clear treelist entries for new parent page
|
||||
$this->processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the change requires an update of the treelist cache
|
||||
*
|
||||
* @param array $updatedFields Array of changed fields
|
||||
* @return bool TRUE if the treelist cache needs to be updated, FALSE if no update to the cache is required
|
||||
*/
|
||||
protected function requiresUpdate(array $updatedFields)
|
||||
{
|
||||
$requiresUpdate = false;
|
||||
$updatedFieldNames = array_keys($updatedFields);
|
||||
foreach ($updatedFieldNames as $updatedFieldName) {
|
||||
if (in_array($updatedFieldName, $this->updateRequiringFields, true)) {
|
||||
$requiresUpdate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $requiresUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the cache maintenance functions according to the determined actions
|
||||
*
|
||||
* @param int $affectedPage uid of the affected page
|
||||
* @param int $affectedParentPage parent uid of the affected page
|
||||
* @param array $updatedFields Array of updated fields and their new values
|
||||
* @param array $actions Array of actions to carry out
|
||||
*/
|
||||
protected function processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions)
|
||||
{
|
||||
$actionNames = array_keys($actions);
|
||||
foreach ($actionNames as $actionName) {
|
||||
switch ($actionName) {
|
||||
case 'allParents':
|
||||
$this->clearCacheForAllParents($affectedParentPage);
|
||||
break;
|
||||
case 'setExpiration':
|
||||
// Only used when setting an end time for a page
|
||||
$expirationTime = $updatedFields['endtime'];
|
||||
$this->setCacheExpiration($affectedPage, $expirationTime);
|
||||
break;
|
||||
case 'uidInTreelist':
|
||||
$this->clearCacheWhereUidInTreelist($affectedPage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// From time to time clean the cache from expired entries
|
||||
// (theoretically every 1000 calls)
|
||||
$randomNumber = rand(1, 1000);
|
||||
if ($randomNumber === 500) {
|
||||
$this->removeExpiredCacheEntries();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the treelist cache for all parents of a changed page.
|
||||
* gets called after creating a new page and after moving a page
|
||||
*
|
||||
* @param int $affectedParentPage Parent page id of the changed page, the page to start clearing from
|
||||
*/
|
||||
protected function clearCacheForAllParents($affectedParentPage)
|
||||
{
|
||||
$rootLine = BackendUtility::BEgetRootLine($affectedParentPage);
|
||||
$rootLineIds = [];
|
||||
foreach ($rootLine as $page) {
|
||||
if ($page['uid'] != 0) {
|
||||
$rootLineIds[] = $page['uid'];
|
||||
}
|
||||
}
|
||||
if (!empty($rootLineIds)) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('cache_treelist');
|
||||
$queryBuilder
|
||||
->delete('cache_treelist')
|
||||
->where(
|
||||
$queryBuilder->expr()->in(
|
||||
'pid',
|
||||
$queryBuilder->createNamedParameter($rootLineIds, Connection::PARAM_INT_ARRAY)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the treelist cache for all pages where the affected page is found
|
||||
* in the treelist
|
||||
*
|
||||
* @param int $affectedPage ID of the changed page
|
||||
*/
|
||||
protected function clearCacheWhereUidInTreelist($affectedPage)
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('cache_treelist');
|
||||
$queryBuilder
|
||||
->delete('cache_treelist')
|
||||
->where(
|
||||
$queryBuilder->expr()->inSet('treelist', $queryBuilder->quote($affectedPage))
|
||||
)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an expiration time for all cache entries having the changed page in
|
||||
* the treelist.
|
||||
*
|
||||
* @param int $affectedPage Uid of the changed page
|
||||
* @param int $expirationTime
|
||||
*/
|
||||
protected function setCacheExpiration($affectedPage, $expirationTime)
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('cache_treelist');
|
||||
$queryBuilder
|
||||
->update('cache_treelist')
|
||||
->where(
|
||||
$queryBuilder->expr()->inSet('treelist', $queryBuilder->quote($affectedPage))
|
||||
)
|
||||
->set('expires', $expirationTime)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all expired treelist cache entries
|
||||
*/
|
||||
protected function removeExpiredCacheEntries()
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('cache_treelist');
|
||||
$queryBuilder
|
||||
->delete('cache_treelist')
|
||||
->where(
|
||||
$queryBuilder->expr()->lte(
|
||||
'expires',
|
||||
$queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what happened to the page record, this is necessary to clear
|
||||
* as less cache entries as needed later
|
||||
*
|
||||
* @param string $status DataHandler operation status, either 'new' or 'update'
|
||||
* @param array $updatedFields Array of updated fields
|
||||
* @return array List of actions that happened to the page record
|
||||
*/
|
||||
protected function determineClearCacheActions($status, $updatedFields): array
|
||||
{
|
||||
$actions = [];
|
||||
if ($status === 'new') {
|
||||
// New page
|
||||
$actions['allParents'] = true;
|
||||
} elseif ($status === 'update') {
|
||||
$updatedFieldNames = array_keys($updatedFields);
|
||||
foreach ($updatedFieldNames as $updatedFieldName) {
|
||||
switch ($updatedFieldName) {
|
||||
case 'pid':
|
||||
|
||||
case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']:
|
||||
|
||||
case $GLOBALS['TCA']['pages']['ctrl']['delete']:
|
||||
|
||||
case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['starttime']:
|
||||
|
||||
case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['fe_group']:
|
||||
|
||||
case 'extendToSubpages':
|
||||
|
||||
case 't3ver_wsid':
|
||||
|
||||
case 'php_tree_stop':
|
||||
// php_tree_stop
|
||||
$actions['allParents'] = true;
|
||||
$actions['uidInTreelist'] = true;
|
||||
break;
|
||||
case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['endtime']:
|
||||
// end time set/unset
|
||||
// When setting an end time the cache entry needs an
|
||||
// expiration time. When unsetting the end time the
|
||||
// page must become listed in the treelist again.
|
||||
if ($updatedFields['endtime'] > 0) {
|
||||
$actions['setExpiration'] = true;
|
||||
} else {
|
||||
$actions['uidInTreelist'] = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (in_array($updatedFieldName, $this->updateRequiringFields, true)) {
|
||||
$actions['uidInTreelist'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Http;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Context\DateTimeAspect;
|
||||
use TYPO3\CMS\Core\Context\UserAspect;
|
||||
use TYPO3\CMS\Core\Context\VisibilityAspect;
|
||||
use TYPO3\CMS\Core\Context\WorkspaceAspect;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Http\AbstractApplication;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Entry point for the TYPO3 Frontend
|
||||
*/
|
||||
class Application extends AbstractApplication
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $requestHandler = RequestHandler::class;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $middlewareStack = 'frontend';
|
||||
|
||||
/**
|
||||
* @var ConfigurationManager
|
||||
*/
|
||||
protected $configurationManager;
|
||||
|
||||
/**
|
||||
* @param ConfigurationManager $configurationManager
|
||||
*/
|
||||
public function __construct(ConfigurationManager $configurationManager)
|
||||
{
|
||||
$this->configurationManager = $configurationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (!$this->checkIfEssentialConfigurationExists()) {
|
||||
return $this->installToolRedirect();
|
||||
}
|
||||
$this->initializeContext();
|
||||
return parent::handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if LocalConfiguration.php and PackageStates.php exist
|
||||
*
|
||||
* @return bool TRUE when the essential configuration is available, otherwise FALSE
|
||||
*/
|
||||
protected function checkIfEssentialConfigurationExists(): bool
|
||||
{
|
||||
return file_exists($this->configurationManager->getLocalConfigurationFileLocation())
|
||||
&& file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a PSR-7 Response that redirects to the install tool
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function installToolRedirect(): ResponseInterface
|
||||
{
|
||||
$path = TYPO3_mainDir . 'install.php';
|
||||
return new RedirectResponse($path, 302);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Context used for accessing data and finding out the current state of the application
|
||||
* Will be moved to a DI-like concept once introduced, for now, this is a singleton
|
||||
*/
|
||||
protected function initializeContext()
|
||||
{
|
||||
GeneralUtility::makeInstance(Context::class, [
|
||||
'date' => new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])),
|
||||
'visibility' => new VisibilityAspect(),
|
||||
'workspace' => new WorkspaceAspect(0),
|
||||
'backend.user' => new UserAspect(null),
|
||||
'frontend.user' => new UserAspect(null, [0, -1]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Http;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
use TYPO3\CMS\Core\Http\Dispatcher;
|
||||
use TYPO3\CMS\Core\Http\NullResponse;
|
||||
use TYPO3\CMS\Core\Http\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Http\Response;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Middleware\EidHandler as EidMiddleware;
|
||||
|
||||
/**
|
||||
* Lightweight alternative to the regular RequestHandler used when $_GET[eID] is set.
|
||||
* In the future, logic from the EidUtility will be moved to this class.
|
||||
*
|
||||
* @deprecated since TYPO3 v9.2, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
class EidRequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Constructor handing over the bootstrap and the original request
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
trigger_error(self::class . ' will be removed in TYPO3 v10.0. Use ' . EidMiddleware::class . ' instead.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a frontend request based on the _GP "eID" variable.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
trigger_error(self::class . ' will be removed in TYPO3 v10.0. Use ' . EidMiddleware::class . ' instead.', E_USER_DEPRECATED);
|
||||
return $this->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* This request handler can handle any frontend request.
|
||||
*
|
||||
* @param ServerRequestInterface $request The request to process
|
||||
* @return bool If the request is not an eID request, TRUE otherwise FALSE
|
||||
*/
|
||||
public function canHandleRequest(ServerRequestInterface $request): bool
|
||||
{
|
||||
trigger_error(self::class . ' will be removed in TYPO3 v10.0. Use ' . EidMiddleware::class . ' instead.', E_USER_DEPRECATED);
|
||||
return !empty($request->getQueryParams()['eID']) || !empty($request->getParsedBody()['eID']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the priority - how eager the handler is to actually handle the
|
||||
* request.
|
||||
*
|
||||
* @return int The priority of the request handler.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
trigger_error(self::class . ' will be removed in TYPO3 v10.0. Use ' . EidMiddleware::class . ' instead.', E_USER_DEPRECATED);
|
||||
return 80;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the request to the corresponding eID class or eID script
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
trigger_error(self::class . ' will be removed in TYPO3 v10.0. Use ' . EidMiddleware::class . ' instead.', E_USER_DEPRECATED);
|
||||
// Remove any output produced until now
|
||||
ob_clean();
|
||||
|
||||
/** @var Response $response */
|
||||
$response = GeneralUtility::makeInstance(Response::class);
|
||||
|
||||
$eID = $request->getParsedBody()['eID'] ?? $request->getQueryParams()['eID'] ?? '';
|
||||
|
||||
if (empty($eID) || !isset($GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include'][$eID])) {
|
||||
return $response->withStatus(404, 'eID not registered');
|
||||
}
|
||||
|
||||
$configuration = $GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include'][$eID];
|
||||
|
||||
// Simple check to make sure that it's not an absolute file (to use the fallback)
|
||||
if (strpos($configuration, '::') !== false || is_callable($configuration)) {
|
||||
/** @var Dispatcher $dispatcher */
|
||||
$dispatcher = GeneralUtility::makeInstance(Dispatcher::class);
|
||||
$request = $request->withAttribute('target', $configuration);
|
||||
return $dispatcher->dispatch($request, $response);
|
||||
}
|
||||
|
||||
$scriptPath = GeneralUtility::getFileAbsFileName($configuration);
|
||||
if ($scriptPath === '') {
|
||||
throw new Exception('Registered eID has invalid script path.', 1416391467);
|
||||
}
|
||||
include $scriptPath;
|
||||
return new NullResponse();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Http;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* This interface needs to be implemented by all classes that register for the hook in:
|
||||
* $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlHandlers']
|
||||
*
|
||||
* It can be used to do custom URL processing during a Frontend request.
|
||||
* @deprecated since TYPO3 v9.3, will be removed in TYPO3 v10.0 in favor of PSR-15 middlewares.
|
||||
*/
|
||||
interface UrlHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Return TRUE if this hook handles the current URL.
|
||||
* Warning! If TRUE is returned content rendering will be disabled!
|
||||
* This method will be called in the constructor of the TypoScriptFrontendController
|
||||
*
|
||||
* @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::__construct()
|
||||
* @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::initializeRedirectUrlHandlers()
|
||||
* @return bool
|
||||
*/
|
||||
public function canHandleCurrentUrl();
|
||||
|
||||
/**
|
||||
* Custom processing of the current URL.
|
||||
*
|
||||
* If canHandle() has returned TRUE this method needs to take care of redirecting the user or generating custom output.
|
||||
* This hook will be called BEFORE the user is redirected to an external URL configured in the page properties.
|
||||
*
|
||||
* @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::redirectToExternalUrl()
|
||||
*/
|
||||
public function handle();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Http;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* This interface needs to be implemented by all classes that register for the hook in:
|
||||
* $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors']
|
||||
*
|
||||
* It can be used manipulate URLs that are generated by the ContentObjectRenderer.
|
||||
*/
|
||||
interface UrlProcessorInterface
|
||||
{
|
||||
const CONTEXT_COMMON = 'common';
|
||||
const CONTEXT_FILE = 'file';
|
||||
const CONTEXT_MAIL = 'mail';
|
||||
const CONTEXT_EXTERNAL = 'external';
|
||||
|
||||
/**
|
||||
* Generates the JumpURL for the given parameters.
|
||||
*
|
||||
* @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::processUrl()
|
||||
* @param string $context The context in which the URL is generated (e.g. "typolink" or one of the constants above).
|
||||
* @param string $url The URL that should be processed.
|
||||
* @param array $configuration The link configuration.
|
||||
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer The calling content object renderer.
|
||||
* @param bool $keepProcessing If this is set to FALSE no further hooks will be processed after the current one.
|
||||
* @return string|null
|
||||
*/
|
||||
public function process($context, $url, array $configuration, \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer, &$keepProcessing);
|
||||
}
|
||||
@@ -0,0 +1,824 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Imaging;
|
||||
|
||||
/*
|
||||
* 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\Imaging\GraphicalFunctions;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\File\BasicFileUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
|
||||
|
||||
/**
|
||||
* GIFBUILDER
|
||||
*
|
||||
* Generating gif/png-files from TypoScript
|
||||
* Used by the menu-objects and imgResource in TypoScript.
|
||||
*
|
||||
* This class allows for advanced rendering of images with various layers of images, text and graphical primitives.
|
||||
* The concept is known from TypoScript as "GIFBUILDER" where you can define a "numerical array" (TypoScript term as well) of "GIFBUILDER OBJECTS" (like "TEXT", "IMAGE", etc.) and they will be rendered onto an image one by one.
|
||||
* The name "GIFBUILDER" comes from the time where GIF was the only file format supported. PNG is just as well to create today (configured with TYPO3_CONF_VARS[GFX])
|
||||
* Not all instances of this class is truly building gif/png files by layers; You may also see the class instantiated for the purpose of using the scaling functions in the parent class.
|
||||
*
|
||||
* Here is an example of how to use this class (from tslib_content.php, function getImgResource):
|
||||
*
|
||||
* $gifCreator = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Imaging\GifBuilder::class);
|
||||
* $gifCreator->init();
|
||||
* $theImage='';
|
||||
* if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
|
||||
* $gifCreator->start($fileArray, $this->data);
|
||||
* $theImage = $gifCreator->gifBuild();
|
||||
* }
|
||||
* return $gifCreator->getImageDimensions($theImage);
|
||||
*/
|
||||
class GifBuilder extends GraphicalFunctions
|
||||
{
|
||||
/**
|
||||
* Contains all text strings used on this image
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $combinedTextStrings = [];
|
||||
|
||||
/**
|
||||
* Contains all filenames (basename without extension) used on this image
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $combinedFileNames = [];
|
||||
|
||||
/**
|
||||
* This is the array from which data->field: [key] is fetched. So this is the current record!
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $objBB = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $myClassName = 'gifbuilder';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $charRangeMap = [];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
public $XY = [];
|
||||
|
||||
/**
|
||||
* @var ContentObjectRenderer
|
||||
*/
|
||||
public $cObj;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $defaultWorkArea = [];
|
||||
|
||||
/**
|
||||
* Initialization of the GIFBUILDER objects, in particular TEXT and IMAGE. This includes finding the bounding box, setting dimensions and offset values before the actual rendering is started.
|
||||
* Modifies the ->setup, ->objBB internal arrays
|
||||
* Should be called after the ->init() function which initializes the parent class functions/variables in general.
|
||||
* The class \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject also uses gifbuilder and here there is an interesting use since the function findLargestDims() from that class calls the init() and start() functions to find the total dimensions before starting the rendering of the images.
|
||||
*
|
||||
* @param array $conf TypoScript properties for the GIFBUILDER session. Stored internally in the variable ->setup
|
||||
* @param array $data The current data record from \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer. Stored internally in the variable ->data
|
||||
* @see ContentObjectRenderer::getImgResource(), \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject::makeGifs(), \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject::findLargestDims()
|
||||
*/
|
||||
public function start($conf, $data)
|
||||
{
|
||||
if (is_array($conf)) {
|
||||
$this->setup = $conf;
|
||||
$this->data = $data;
|
||||
$this->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$this->cObj->start($this->data);
|
||||
// Hook preprocess gifbuilder conf
|
||||
// Added by Julle for 3.8.0
|
||||
//
|
||||
// Let's you pre-process the gifbuilder configuration. for
|
||||
// example you can split a string up into lines and render each
|
||||
// line as TEXT obj, see extension julle_gifbconf
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_gifbuilder.php']['gifbuilder-ConfPreProcess'] ?? [] as $_funcRef) {
|
||||
$_params = $this->setup;
|
||||
$this->setup = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
|
||||
}
|
||||
// Initializing global Char Range Map
|
||||
$this->charRangeMap = [];
|
||||
if (is_array($GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'])) {
|
||||
foreach ($GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'] as $cRMcfgkey => $cRMcfg) {
|
||||
if (is_array($cRMcfg)) {
|
||||
// Initializing:
|
||||
$cRMkey = $GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'][substr($cRMcfgkey, 0, -1)];
|
||||
$this->charRangeMap[$cRMkey] = [];
|
||||
$this->charRangeMap[$cRMkey]['charMapConfig'] = $cRMcfg['charMapConfig.'];
|
||||
$this->charRangeMap[$cRMkey]['cfgKey'] = substr($cRMcfgkey, 0, -1);
|
||||
$this->charRangeMap[$cRMkey]['multiplicator'] = (double)$cRMcfg['fontSizeMultiplicator'];
|
||||
$this->charRangeMap[$cRMkey]['pixelSpace'] = (int)$cRMcfg['pixelSpaceFontSizeRef'];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Getting sorted list of TypoScript keys from setup.
|
||||
$sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
|
||||
// Setting the background color, passing it through stdWrap
|
||||
if ($conf['backColor.'] || $conf['backColor']) {
|
||||
$this->setup['backColor'] = isset($this->setup['backColor.']) ? trim($this->cObj->stdWrap($this->setup['backColor'], $this->setup['backColor.'])) : $this->setup['backColor'];
|
||||
}
|
||||
if (!$this->setup['backColor']) {
|
||||
$this->setup['backColor'] = 'white';
|
||||
}
|
||||
if ($conf['transparentColor.'] || $conf['transparentColor']) {
|
||||
$this->setup['transparentColor_array'] = isset($this->setup['transparentColor.']) ? explode('|', trim($this->cObj->stdWrap($this->setup['transparentColor'], $this->setup['transparentColor.']))) : explode('|', trim($this->setup['transparentColor']));
|
||||
}
|
||||
if (isset($this->setup['transparentBackground.'])) {
|
||||
$this->setup['transparentBackground'] = $this->cObj->stdWrap($this->setup['transparentBackground'], $this->setup['transparentBackground.']);
|
||||
}
|
||||
if (isset($this->setup['reduceColors.'])) {
|
||||
$this->setup['reduceColors'] = $this->cObj->stdWrap($this->setup['reduceColors'], $this->setup['reduceColors.']);
|
||||
}
|
||||
// Set default dimensions
|
||||
if (isset($this->setup['XY.'])) {
|
||||
$this->setup['XY'] = $this->cObj->stdWrap($this->setup['XY'], $this->setup['XY.']);
|
||||
}
|
||||
if (!$this->setup['XY']) {
|
||||
$this->setup['XY'] = '120,50';
|
||||
}
|
||||
// Checking TEXT and IMAGE objects for files. If any errors the objects are cleared.
|
||||
// The Bounding Box for the objects is stored in an array
|
||||
foreach ($sKeyArray as $theKey) {
|
||||
$theValue = $this->setup[$theKey];
|
||||
if ((int)$theKey && ($conf = $this->setup[$theKey . '.'])) {
|
||||
// Swipes through TEXT and IMAGE-objects
|
||||
switch ($theValue) {
|
||||
case 'TEXT':
|
||||
if ($this->setup[$theKey . '.'] = $this->checkTextObj($conf)) {
|
||||
// Adjust font width if max size is set:
|
||||
$maxWidth = isset($this->setup[$theKey . '.']['maxWidth.']) ? $this->cObj->stdWrap($this->setup[$theKey . '.']['maxWidth'], $this->setup[$theKey . '.']['maxWidth.']) : $this->setup[$theKey . '.']['maxWidth'];
|
||||
if ($maxWidth) {
|
||||
$this->setup[$theKey . '.']['fontSize'] = $this->fontResize($this->setup[$theKey . '.']);
|
||||
}
|
||||
// Calculate bounding box:
|
||||
$txtInfo = $this->calcBBox($this->setup[$theKey . '.']);
|
||||
$this->setup[$theKey . '.']['BBOX'] = $txtInfo;
|
||||
$this->objBB[$theKey] = $txtInfo;
|
||||
$this->setup[$theKey . '.']['imgMap'] = 0;
|
||||
}
|
||||
break;
|
||||
case 'IMAGE':
|
||||
$fileInfo = $this->getResource($conf['file'], $conf['file.']);
|
||||
if ($fileInfo) {
|
||||
$this->combinedFileNames[] = preg_replace('/\\.[[:alnum:]]+$/', '', PathUtility::basename($fileInfo[3]));
|
||||
if ($fileInfo['processedFile'] instanceof ProcessedFile) {
|
||||
// Use processed file, if a FAL file has been processed by GIFBUILDER (e.g. scaled/cropped)
|
||||
$this->setup[$theKey . '.']['file'] = $fileInfo['processedFile']->getForLocalProcessing(false);
|
||||
} elseif (!isset($fileInfo['origFile']) && $fileInfo['originalFile'] instanceof File) {
|
||||
// Use FAL file with getForLocalProcessing to circumvent problems with umlauts, if it is a FAL file (origFile not set)
|
||||
/** @var File $originalFile */
|
||||
$originalFile = $fileInfo['originalFile'];
|
||||
$this->setup[$theKey . '.']['file'] = $originalFile->getForLocalProcessing(false);
|
||||
} else {
|
||||
// Use normal path from fileInfo if it is a non-FAL file (even non-FAL files have originalFile set, but only non-FAL files have origFile set)
|
||||
$this->setup[$theKey . '.']['file'] = $fileInfo[3];
|
||||
}
|
||||
|
||||
// only pass necessary parts of fileInfo further down, to not incorporate facts as
|
||||
// CropScaleMask runs in this request, that may not occur in subsequent calls and change
|
||||
// the md5 of the generated file name
|
||||
$essentialFileInfo = $fileInfo;
|
||||
unset($essentialFileInfo['originalFile'], $essentialFileInfo['processedFile']);
|
||||
|
||||
$this->setup[$theKey . '.']['BBOX'] = $essentialFileInfo;
|
||||
$this->objBB[$theKey] = $essentialFileInfo;
|
||||
if ($conf['mask']) {
|
||||
$maskInfo = $this->getResource($conf['mask'], $conf['mask.']);
|
||||
if ($maskInfo) {
|
||||
// the same selection criteria as regarding fileInfo above apply here
|
||||
if ($maskInfo['processedFile'] instanceof ProcessedFile) {
|
||||
$this->setup[$theKey . '.']['mask'] = $maskInfo['processedFile']->getForLocalProcessing(false);
|
||||
} elseif (!isset($maskInfo['origFile']) && $maskInfo['originalFile'] instanceof File) {
|
||||
/** @var File $originalFile */
|
||||
$originalFile = $maskInfo['originalFile'];
|
||||
$this->setup[$theKey . '.']['mask'] = $originalFile->getForLocalProcessing(false);
|
||||
} else {
|
||||
$this->setup[$theKey . '.']['mask'] = $maskInfo[3];
|
||||
}
|
||||
} else {
|
||||
$this->setup[$theKey . '.']['mask'] = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unset($this->setup[$theKey . '.']);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Checks if disabled is set... (this is also done in menu.php / imgmenu!!)
|
||||
if ($conf['if.']) {
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$cObj->start($this->data);
|
||||
if (!$cObj->checkIf($conf['if.'])) {
|
||||
unset($this->setup[$theKey]);
|
||||
unset($this->setup[$theKey . '.']);
|
||||
unset($this->objBB[$theKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Calculate offsets on elements
|
||||
$this->setup['XY'] = $this->calcOffset($this->setup['XY']);
|
||||
if (isset($this->setup['offset.'])) {
|
||||
$this->setup['offset'] = $this->cObj->stdWrap($this->setup['offset'], $this->setup['offset.']);
|
||||
}
|
||||
$this->setup['offset'] = $this->calcOffset($this->setup['offset']);
|
||||
if (isset($this->setup['workArea.'])) {
|
||||
$this->setup['workArea'] = $this->cObj->stdWrap($this->setup['workArea'], $this->setup['workArea.']);
|
||||
}
|
||||
$this->setup['workArea'] = $this->calcOffset($this->setup['workArea']);
|
||||
foreach ($sKeyArray as $theKey) {
|
||||
$theValue = $this->setup[$theKey];
|
||||
if ((int)$theKey && $this->setup[$theKey . '.']) {
|
||||
switch ($theValue) {
|
||||
case 'TEXT':
|
||||
|
||||
case 'IMAGE':
|
||||
if (isset($this->setup[$theKey . '.']['offset.'])) {
|
||||
$this->setup[$theKey . '.']['offset'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['offset'], $this->setup[$theKey . '.']['offset.']);
|
||||
unset($this->setup[$theKey . '.']['offset.']);
|
||||
}
|
||||
if ($this->setup[$theKey . '.']['offset']) {
|
||||
$this->setup[$theKey . '.']['offset'] = $this->calcOffset($this->setup[$theKey . '.']['offset']);
|
||||
}
|
||||
break;
|
||||
case 'BOX':
|
||||
|
||||
case 'ELLIPSE':
|
||||
if (isset($this->setup[$theKey . '.']['dimensions.'])) {
|
||||
$this->setup[$theKey . '.']['dimensions'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['dimensions'], $this->setup[$theKey . '.']['dimensions.']);
|
||||
unset($this->setup[$theKey . '.']['dimensions.']);
|
||||
}
|
||||
if ($this->setup[$theKey . '.']['dimensions']) {
|
||||
$this->setup[$theKey . '.']['dimensions'] = $this->calcOffset($this->setup[$theKey . '.']['dimensions']);
|
||||
}
|
||||
break;
|
||||
case 'WORKAREA':
|
||||
if (isset($this->setup[$theKey . '.']['set.'])) {
|
||||
$this->setup[$theKey . '.']['set'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['set'], $this->setup[$theKey . '.']['set.']);
|
||||
unset($this->setup[$theKey . '.']['set.']);
|
||||
}
|
||||
if ($this->setup[$theKey . '.']['set']) {
|
||||
$this->setup[$theKey . '.']['set'] = $this->calcOffset($this->setup[$theKey . '.']['set']);
|
||||
}
|
||||
break;
|
||||
case 'CROP':
|
||||
if (isset($this->setup[$theKey . '.']['crop.'])) {
|
||||
$this->setup[$theKey . '.']['crop'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['crop'], $this->setup[$theKey . '.']['crop.']);
|
||||
unset($this->setup[$theKey . '.']['crop.']);
|
||||
}
|
||||
if ($this->setup[$theKey . '.']['crop']) {
|
||||
$this->setup[$theKey . '.']['crop'] = $this->calcOffset($this->setup[$theKey . '.']['crop']);
|
||||
}
|
||||
break;
|
||||
case 'SCALE':
|
||||
if (isset($this->setup[$theKey . '.']['width.'])) {
|
||||
$this->setup[$theKey . '.']['width'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['width'], $this->setup[$theKey . '.']['width.']);
|
||||
unset($this->setup[$theKey . '.']['width.']);
|
||||
}
|
||||
if ($this->setup[$theKey . '.']['width']) {
|
||||
$this->setup[$theKey . '.']['width'] = $this->calcOffset($this->setup[$theKey . '.']['width']);
|
||||
}
|
||||
if (isset($this->setup[$theKey . '.']['height.'])) {
|
||||
$this->setup[$theKey . '.']['height'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['height'], $this->setup[$theKey . '.']['height.']);
|
||||
unset($this->setup[$theKey . '.']['height.']);
|
||||
}
|
||||
if ($this->setup[$theKey . '.']['height']) {
|
||||
$this->setup[$theKey . '.']['height'] = $this->calcOffset($this->setup[$theKey . '.']['height']);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get trivial data
|
||||
$XY = GeneralUtility::intExplode(',', $this->setup['XY']);
|
||||
$maxWidth = isset($this->setup['maxWidth.']) ? (int)$this->cObj->stdWrap($this->setup['maxWidth'], $this->setup['maxWidth.']) : (int)$this->setup['maxWidth'];
|
||||
$maxHeight = isset($this->setup['maxHeight.']) ? (int)$this->cObj->stdWrap($this->setup['maxHeight'], $this->setup['maxHeight.']) : (int)$this->setup['maxHeight'];
|
||||
$XY[0] = MathUtility::forceIntegerInRange($XY[0], 1, $maxWidth ?: 2000);
|
||||
$XY[1] = MathUtility::forceIntegerInRange($XY[1], 1, $maxHeight ?: 2000);
|
||||
$this->XY = $XY;
|
||||
$this->w = $XY[0];
|
||||
$this->h = $XY[1];
|
||||
$this->OFFSET = GeneralUtility::intExplode(',', $this->setup['offset']);
|
||||
// this sets the workArea
|
||||
$this->setWorkArea($this->setup['workArea']);
|
||||
// this sets the default to the current;
|
||||
$this->defaultWorkArea = $this->workArea;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the image file generation if ->setup is TRUE and if the file did not exist already.
|
||||
* Gets filename from fileName() and if file exists in typo3temp/assets/images/ dir it will - of course - not be rendered again.
|
||||
* Otherwise rendering means calling ->make(), then ->output(), then ->destroy()
|
||||
*
|
||||
* @return string The filename for the created GIF/PNG file. The filename will be prefixed "GB_
|
||||
* @see make(), fileName()
|
||||
*/
|
||||
public function gifBuild()
|
||||
{
|
||||
if ($this->setup) {
|
||||
// Relative to Environment::getPublicPath()
|
||||
$gifFileName = $this->fileName('assets/images/');
|
||||
// File exists
|
||||
if (!file_exists($gifFileName)) {
|
||||
// Create temporary directory if not done:
|
||||
GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/images/');
|
||||
// Create file:
|
||||
$this->make();
|
||||
$this->output($gifFileName);
|
||||
$this->destroy();
|
||||
}
|
||||
return $gifFileName;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual rendering of the image file.
|
||||
* Basically sets the dimensions, the background color, the traverses the array of GIFBUILDER objects and finally setting the transparent color if defined.
|
||||
* Creates a GDlib resource in $this->im and works on that
|
||||
* Called by gifBuild()
|
||||
*
|
||||
* @internal
|
||||
* @see gifBuild()
|
||||
*/
|
||||
public function make()
|
||||
{
|
||||
// Get trivial data
|
||||
$XY = $this->XY;
|
||||
// Reset internal properties
|
||||
$this->saveAlphaLayer = false;
|
||||
// Gif-start
|
||||
$this->im = imagecreatetruecolor($XY[0], $XY[1]);
|
||||
$this->w = $XY[0];
|
||||
$this->h = $XY[1];
|
||||
// Transparent layer as background if set and requirements are met
|
||||
if (!empty($this->setup['backColor']) && $this->setup['backColor'] === 'transparent' && !$this->setup['reduceColors'] && (empty($this->setup['format']) || $this->setup['format'] === 'png')) {
|
||||
// Set transparency properties
|
||||
imagesavealpha($this->im, true);
|
||||
// Fill with a transparent background
|
||||
$transparentColor = imagecolorallocatealpha($this->im, 0, 0, 0, 127);
|
||||
imagefill($this->im, 0, 0, $transparentColor);
|
||||
// Set internal properties to keep the transparency over the rendering process
|
||||
$this->saveAlphaLayer = true;
|
||||
// Force PNG in case no format is set
|
||||
$this->setup['format'] = 'png';
|
||||
$BGcols = [];
|
||||
} else {
|
||||
// Fill the background with the given color
|
||||
$BGcols = $this->convertColor($this->setup['backColor']);
|
||||
$Bcolor = imagecolorallocate($this->im, $BGcols[0], $BGcols[1], $BGcols[2]);
|
||||
imagefilledrectangle($this->im, 0, 0, $XY[0], $XY[1], $Bcolor);
|
||||
}
|
||||
// Traverse the GIFBUILDER objects an render each one:
|
||||
if (is_array($this->setup)) {
|
||||
$sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
|
||||
foreach ($sKeyArray as $theKey) {
|
||||
$theValue = $this->setup[$theKey];
|
||||
if ((int)$theKey && ($conf = $this->setup[$theKey . '.'])) {
|
||||
// apply stdWrap to all properties, except for TEXT objects
|
||||
// all properties of the TEXT sub-object have already been stdWrap-ped
|
||||
// before in ->checkTextObj()
|
||||
if ($theValue !== 'TEXT') {
|
||||
$isStdWrapped = [];
|
||||
foreach ($conf as $key => $value) {
|
||||
$parameter = rtrim($key, '.');
|
||||
if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
|
||||
$conf[$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
|
||||
$isStdWrapped[$parameter] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch ($theValue) {
|
||||
case 'IMAGE':
|
||||
if ($conf['mask']) {
|
||||
$this->maskImageOntoImage($this->im, $conf, $this->workArea);
|
||||
} else {
|
||||
$this->copyImageOntoImage($this->im, $conf, $this->workArea);
|
||||
}
|
||||
break;
|
||||
case 'TEXT':
|
||||
if (!$conf['hide']) {
|
||||
if (is_array($conf['shadow.'])) {
|
||||
$isStdWrapped = [];
|
||||
foreach ($conf['shadow.'] as $key => $value) {
|
||||
$parameter = rtrim($key, '.');
|
||||
if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
|
||||
$conf['shadow.'][$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
|
||||
$isStdWrapped[$parameter] = 1;
|
||||
}
|
||||
}
|
||||
$this->makeShadow($this->im, $conf['shadow.'], $this->workArea, $conf);
|
||||
}
|
||||
if (is_array($conf['emboss.'])) {
|
||||
$isStdWrapped = [];
|
||||
foreach ($conf['emboss.'] as $key => $value) {
|
||||
$parameter = rtrim($key, '.');
|
||||
if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
|
||||
$conf['emboss.'][$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
|
||||
$isStdWrapped[$parameter] = 1;
|
||||
}
|
||||
}
|
||||
$this->makeEmboss($this->im, $conf['emboss.'], $this->workArea, $conf);
|
||||
}
|
||||
if (is_array($conf['outline.'])) {
|
||||
$isStdWrapped = [];
|
||||
foreach ($conf['outline.'] as $key => $value) {
|
||||
$parameter = rtrim($key, '.');
|
||||
if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
|
||||
$conf['outline.'][$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
|
||||
$isStdWrapped[$parameter] = 1;
|
||||
}
|
||||
}
|
||||
$this->makeOutline($this->im, $conf['outline.'], $this->workArea, $conf);
|
||||
}
|
||||
$conf['imgMap'] = 1;
|
||||
$this->makeText($this->im, $conf, $this->workArea);
|
||||
}
|
||||
break;
|
||||
case 'OUTLINE':
|
||||
if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
|
||||
$this->makeOutline($this->im, $conf, $this->workArea, $txtConf);
|
||||
}
|
||||
break;
|
||||
case 'EMBOSS':
|
||||
if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
|
||||
$this->makeEmboss($this->im, $conf, $this->workArea, $txtConf);
|
||||
}
|
||||
break;
|
||||
case 'SHADOW':
|
||||
if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
|
||||
$this->makeShadow($this->im, $conf, $this->workArea, $txtConf);
|
||||
}
|
||||
break;
|
||||
case 'BOX':
|
||||
$this->makeBox($this->im, $conf, $this->workArea);
|
||||
break;
|
||||
case 'EFFECT':
|
||||
$this->makeEffect($this->im, $conf);
|
||||
break;
|
||||
case 'ADJUST':
|
||||
$this->adjust($this->im, $conf);
|
||||
break;
|
||||
case 'CROP':
|
||||
$this->crop($this->im, $conf);
|
||||
break;
|
||||
case 'SCALE':
|
||||
$this->scale($this->im, $conf);
|
||||
break;
|
||||
case 'WORKAREA':
|
||||
if ($conf['set']) {
|
||||
// this sets the workArea
|
||||
$this->setWorkArea($conf['set']);
|
||||
}
|
||||
if (isset($conf['clear'])) {
|
||||
// This sets the current to the default;
|
||||
$this->workArea = $this->defaultWorkArea;
|
||||
}
|
||||
break;
|
||||
case 'ELLIPSE':
|
||||
$this->makeEllipse($this->im, $conf, $this->workArea);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Preserve alpha transparency
|
||||
if (!$this->saveAlphaLayer) {
|
||||
if ($this->setup['transparentBackground']) {
|
||||
// Auto transparent background is set
|
||||
$Bcolor = imagecolorclosest($this->im, $BGcols[0], $BGcols[1], $BGcols[2]);
|
||||
imagecolortransparent($this->im, $Bcolor);
|
||||
} elseif (is_array($this->setup['transparentColor_array'])) {
|
||||
// Multiple transparent colors are set. This is done via the trick that all transparent colors get
|
||||
// converted to one color and then this one gets set as transparent as png/gif can just have one
|
||||
// transparent color.
|
||||
$Tcolor = $this->unifyColors($this->im, $this->setup['transparentColor_array'], (int)$this->setup['transparentColor.']['closest']);
|
||||
if ($Tcolor >= 0) {
|
||||
imagecolortransparent($this->im, $Tcolor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************
|
||||
*
|
||||
* Various helper functions
|
||||
*
|
||||
********************************************/
|
||||
/**
|
||||
* Initializing/Cleaning of TypoScript properties for TEXT GIFBUILDER objects
|
||||
*
|
||||
* 'cleans' TEXT-object; Checks fontfile and other vital setup
|
||||
* Finds the title if its a 'variable' (instantiates a cObj and loads it with the ->data record)
|
||||
* Performs caseshift if any.
|
||||
*
|
||||
* @param array $conf GIFBUILDER object TypoScript properties
|
||||
* @return array Modified $conf array IF the "text" property is not blank
|
||||
* @internal
|
||||
*/
|
||||
public function checkTextObj($conf)
|
||||
{
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$cObj->start($this->data);
|
||||
$isStdWrapped = [];
|
||||
foreach ($conf as $key => $value) {
|
||||
$parameter = rtrim($key, '.');
|
||||
if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
|
||||
$conf[$parameter] = $cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
|
||||
$isStdWrapped[$parameter] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($conf['fontFile'])) {
|
||||
$conf['fontFile'] = $this->checkFile($conf['fontFile']);
|
||||
}
|
||||
if (!$conf['fontFile']) {
|
||||
$conf['fontFile'] = $this->checkFile('EXT:core/Resources/Private/Font/nimbus.ttf');
|
||||
}
|
||||
if (!$conf['iterations']) {
|
||||
$conf['iterations'] = 1;
|
||||
}
|
||||
if (!$conf['fontSize']) {
|
||||
$conf['fontSize'] = 12;
|
||||
}
|
||||
// If any kind of spacing applys, we cannot use angles!!
|
||||
if ($conf['spacing'] || $conf['wordSpacing']) {
|
||||
$conf['angle'] = 0;
|
||||
}
|
||||
if (!isset($conf['antiAlias'])) {
|
||||
$conf['antiAlias'] = 1;
|
||||
}
|
||||
$conf['fontColor'] = trim($conf['fontColor']);
|
||||
// Strip HTML
|
||||
if (!$conf['doNotStripHTML']) {
|
||||
$conf['text'] = strip_tags($conf['text']);
|
||||
}
|
||||
$this->combinedTextStrings[] = strip_tags($conf['text']);
|
||||
// Max length = 100 if automatic line braks are not defined:
|
||||
if (!isset($conf['breakWidth']) || !$conf['breakWidth']) {
|
||||
$tlen = (int)$conf['textMaxLength'] ?: 100;
|
||||
$conf['text'] = mb_substr($conf['text'], 0, $tlen, 'utf-8');
|
||||
}
|
||||
if ((string)$conf['text'] != '') {
|
||||
// Char range map thingie:
|
||||
$fontBaseName = PathUtility::basename($conf['fontFile']);
|
||||
if (is_array($this->charRangeMap[$fontBaseName])) {
|
||||
// Initialize splitRendering array:
|
||||
if (!is_array($conf['splitRendering.'])) {
|
||||
$conf['splitRendering.'] = [];
|
||||
}
|
||||
$cfgK = $this->charRangeMap[$fontBaseName]['cfgKey'];
|
||||
// Do not impose settings if a splitRendering object already exists:
|
||||
if (!isset($conf['splitRendering.'][$cfgK])) {
|
||||
// Set configuration:
|
||||
$conf['splitRendering.'][$cfgK] = 'charRange';
|
||||
$conf['splitRendering.'][$cfgK . '.'] = $this->charRangeMap[$fontBaseName]['charMapConfig'];
|
||||
// Multiplicator of fontsize:
|
||||
if ($this->charRangeMap[$fontBaseName]['multiplicator']) {
|
||||
$conf['splitRendering.'][$cfgK . '.']['fontSize'] = round($conf['fontSize'] * $this->charRangeMap[$fontBaseName]['multiplicator']);
|
||||
}
|
||||
// Multiplicator of pixelSpace:
|
||||
if ($this->charRangeMap[$fontBaseName]['pixelSpace']) {
|
||||
$travKeys = ['xSpaceBefore', 'xSpaceAfter', 'ySpaceBefore', 'ySpaceAfter'];
|
||||
foreach ($travKeys as $pxKey) {
|
||||
if (isset($conf['splitRendering.'][$cfgK . '.'][$pxKey])) {
|
||||
$conf['splitRendering.'][$cfgK . '.'][$pxKey] = round($conf['splitRendering.'][$cfgK . '.'][$pxKey] * ($conf['fontSize'] / $this->charRangeMap[$fontBaseName]['pixelSpace']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_array($conf['splitRendering.'])) {
|
||||
foreach ($conf['splitRendering.'] as $key => $value) {
|
||||
if (is_array($conf['splitRendering.'][$key])) {
|
||||
if (isset($conf['splitRendering.'][$key]['fontFile'])) {
|
||||
$conf['splitRendering.'][$key]['fontFile'] = $this->checkFile($conf['splitRendering.'][$key]['fontFile']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $conf;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculation of offset using "splitCalc" and insertion of dimensions from other GIFBUILDER objects.
|
||||
*
|
||||
* Example:
|
||||
* Input: 2+2, 2*3, 123, [10.w]
|
||||
* Output: 4,6,123,45 (provided that the width of object in position 10 was 45 pixels wide)
|
||||
*
|
||||
* @param string $string The string to resolve/calculate the result of. The string is divided by a comma first and each resulting part is calculated into an integer.
|
||||
* @return string The resolved string with each part (separated by comma) returned separated by comma
|
||||
* @internal
|
||||
*/
|
||||
public function calcOffset($string)
|
||||
{
|
||||
$value = [];
|
||||
$numbers = GeneralUtility::trimExplode(',', $this->calculateFunctions($string));
|
||||
foreach ($numbers as $key => $val) {
|
||||
if ((string)$val == (string)(int)$val) {
|
||||
$value[$key] = (int)$val;
|
||||
} else {
|
||||
$value[$key] = $this->calculateValue($val);
|
||||
}
|
||||
}
|
||||
$string = implode(',', $value);
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an "imgResource" creating an instance of the ContentObjectRenderer class and calling ContentObjectRenderer::getImgResource
|
||||
*
|
||||
* @param string $file Filename value OR the string "GIFBUILDER", see documentation in TSref for the "datatype" called "imgResource
|
||||
* @param array $fileArray TypoScript properties passed to the function. Either GIFBUILDER properties or imgResource properties, depending on the value of $file (whether that is "GIFBUILDER" or a file reference)
|
||||
* @return array|null Returns an array with file information from ContentObjectRenderer::getImgResource()
|
||||
* @internal
|
||||
* @see ContentObjectRenderer::getImgResource()
|
||||
*/
|
||||
public function getResource($file, $fileArray)
|
||||
{
|
||||
if (!in_array($fileArray['ext'], $this->imageFileExt, true)) {
|
||||
$fileArray['ext'] = $this->gifExtension;
|
||||
}
|
||||
/** @var ContentObjectRenderer $cObj */
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$cObj->start($this->data);
|
||||
return $cObj->getImgResource($file, $fileArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reference to a "resource" in TypoScript.
|
||||
*
|
||||
* @param string $file The resource value.
|
||||
* @return string|null Returns the relative filepath or null if it's invalid
|
||||
* @internal
|
||||
* @see TemplateService::getFileName()
|
||||
*/
|
||||
public function checkFile($file)
|
||||
{
|
||||
try {
|
||||
return GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($file);
|
||||
} catch (\TYPO3\CMS\Core\Resource\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the GIFBUILDER output filename/path based on a serialized, hashed value of this->setup
|
||||
* and prefixes the original filename
|
||||
* also, the filename gets an additional prefix (max 100 characters),
|
||||
* something like "GB_MD5HASH_myfilename_is_very_long_and_such.jpg"
|
||||
*
|
||||
* @param string $pre Filename prefix, eg. "GB_
|
||||
* @return string The filepath, relative to Environment::getPublicPath()
|
||||
* @internal
|
||||
*/
|
||||
public function fileName($pre)
|
||||
{
|
||||
/** @var \TYPO3\CMS\Core\Utility\File\BasicFileUtility $basicFileFunctions */
|
||||
$basicFileFunctions = GeneralUtility::makeInstance(BasicFileUtility::class);
|
||||
$filePrefix = implode('_', array_merge($this->combinedTextStrings, $this->combinedFileNames));
|
||||
$filePrefix = $basicFileFunctions->cleanFileName(ltrim($filePrefix, '.'));
|
||||
|
||||
// shorten prefix to avoid overly long file names
|
||||
$filePrefix = substr($filePrefix, 0, 100);
|
||||
|
||||
return 'typo3temp/' . $pre . $filePrefix . '_' . GeneralUtility::shortMD5(serialize($this->setup)) . '.' . $this->extension();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file extension used in the filename
|
||||
*
|
||||
* @return string Extension; "jpg" or "gif"/"png
|
||||
* @internal
|
||||
*/
|
||||
public function extension()
|
||||
{
|
||||
switch (strtolower($this->setup['format'])) {
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
return 'jpg';
|
||||
case 'png':
|
||||
return 'png';
|
||||
case 'gif':
|
||||
return 'gif';
|
||||
default:
|
||||
return $this->gifExtension;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the value concerning the dimensions of objects.
|
||||
*
|
||||
* @param string $string The string to be calculated (e.g. "[20.h]+13")
|
||||
* @return int The calculated value (e.g. "23")
|
||||
* @see calcOffset()
|
||||
*/
|
||||
protected function calculateValue($string)
|
||||
{
|
||||
$calculatedValue = 0;
|
||||
$parts = GeneralUtility::splitCalc($string, '+-*/%');
|
||||
foreach ($parts as $part) {
|
||||
$theVal = $part[1];
|
||||
$sign = $part[0];
|
||||
if (((string)(int)$theVal) == ((string)$theVal)) {
|
||||
$theVal = (int)$theVal;
|
||||
} elseif ('[' . substr($theVal, 1, -1) . ']' == $theVal) {
|
||||
$objParts = explode('.', substr($theVal, 1, -1));
|
||||
$theVal = 0;
|
||||
if (isset($this->objBB[$objParts[0]])) {
|
||||
if ($objParts[1] === 'w') {
|
||||
$theVal = $this->objBB[$objParts[0]][0];
|
||||
} elseif ($objParts[1] === 'h') {
|
||||
$theVal = $this->objBB[$objParts[0]][1];
|
||||
} elseif ($objParts[1] === 'lineHeight') {
|
||||
$theVal = $this->objBB[$objParts[0]][2]['lineHeight'];
|
||||
}
|
||||
$theVal = (int)$theVal;
|
||||
}
|
||||
} elseif ((float)$theVal) {
|
||||
$theVal = (float)$theVal;
|
||||
} else {
|
||||
$theVal = 0;
|
||||
}
|
||||
if ($sign === '-') {
|
||||
$calculatedValue -= $theVal;
|
||||
} elseif ($sign === '+') {
|
||||
$calculatedValue += $theVal;
|
||||
} elseif ($sign === '/' && $theVal) {
|
||||
$calculatedValue = $calculatedValue / $theVal;
|
||||
} elseif ($sign === '*') {
|
||||
$calculatedValue = $calculatedValue * $theVal;
|
||||
} elseif ($sign === '%' && $theVal) {
|
||||
$calculatedValue %= $theVal;
|
||||
}
|
||||
}
|
||||
return round($calculatedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates special functions:
|
||||
* + max([10.h], [20.h]) -> gets the maximum of the given values
|
||||
*
|
||||
* @param string $string The raw string with functions to be calculated
|
||||
* @return string The calculated values
|
||||
*/
|
||||
protected function calculateFunctions($string)
|
||||
{
|
||||
if (preg_match_all('#max\\(([^)]+)\\)#', $string, $matches)) {
|
||||
foreach ($matches[1] as $index => $maxExpression) {
|
||||
$string = str_replace($matches[0][$index], $this->calculateMaximum($maxExpression), $string);
|
||||
}
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the maximum of a set of values defined like "[10.h],[20.h],1000"
|
||||
*
|
||||
* @param string $string The string to be used to calculate the maximum (e.g. "[10.h],[20.h],1000")
|
||||
* @return int The maxium value of the given comma separated and calculated values
|
||||
*/
|
||||
protected function calculateMaximum($string)
|
||||
{
|
||||
$parts = GeneralUtility::trimExplode(',', $this->calcOffset($string), true);
|
||||
$maximum = !empty($parts) ? max($parts) : 0;
|
||||
return $maximum;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Context\UserAspect;
|
||||
use TYPO3\CMS\Core\Context\WorkspaceAspect;
|
||||
use TYPO3\CMS\Core\Core\Bootstrap;
|
||||
use TYPO3\CMS\Core\Http\NormalizedParams;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This middleware authenticates a Backend User (be_user) (pre)-viewing a frontend page.
|
||||
*
|
||||
* This middleware also ensures that $GLOBALS['LANG'] is available, however it is possible that
|
||||
* a different middleware later-on might unset the BE_USER as he/she is not allowed to preview a certain
|
||||
* page due to rights management. As this can only happen once the page ID is resolved, this will happen
|
||||
* after the routing middleware.
|
||||
*
|
||||
* Currently, this middleware depends on the availability of $GLOBALS['TSFE'], however, this is solely
|
||||
* due to backwards-compatibility and will be disabled in the future.
|
||||
*/
|
||||
class BackendUserAuthenticator implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Creates a frontend user authentication object, tries to authenticate a user
|
||||
* and stores the object in $GLOBALS['TSFE']->fe_user.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
// PRE BE_USER HOOK
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preBeUser'])) {
|
||||
trigger_error('The "preBeUser" hook will be removed in TYPO3 v10.0 in favor of PSR-15. Use a middleware instead.', E_USER_DEPRECATED);
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preBeUser'] as $_funcRef) {
|
||||
$_params = [];
|
||||
GeneralUtility::callUserFunction($_funcRef, $_params, $GLOBALS['TSFE']);
|
||||
}
|
||||
}
|
||||
|
||||
// Initializing a possible logged-in Backend User
|
||||
// If the backend cookie is set,
|
||||
// we proceed and check if a backend user is logged in.
|
||||
$backendUserObject = null;
|
||||
if (isset($request->getCookieParams()[BackendUserAuthentication::getCookieName()])) {
|
||||
$backendUserObject = $this->initializeBackendUser($request);
|
||||
}
|
||||
|
||||
$GLOBALS['BE_USER'] = $backendUserObject;
|
||||
|
||||
// POST BE_USER HOOK
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['postBeUser'])) {
|
||||
$_params = [
|
||||
'BE_USER' => &$GLOBALS['BE_USER']
|
||||
];
|
||||
trigger_error('The "postBeUser" hook will be removed in TYPO3 v10.0 in favor of PSR-15. Use a middleware instead.', E_USER_DEPRECATED);
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['postBeUser'] as $_funcRef) {
|
||||
GeneralUtility::callUserFunction($_funcRef, $_params, $GLOBALS['TSFE']);
|
||||
}
|
||||
}
|
||||
|
||||
// Load specific dependencies which are necessary for a valid Backend User
|
||||
// like $GLOBALS['LANG'] for labels in the language of the BE User, the router, and ext_tables.php for all modules
|
||||
// So things like Frontend Editing and Admin Panel can use this for generating links to the TYPO3 Backend.
|
||||
if ($GLOBALS['BE_USER'] instanceof FrontendBackendUserAuthentication) {
|
||||
Bootstrap::initializeLanguageObject();
|
||||
Bootstrap::initializeBackendRouter();
|
||||
Bootstrap::loadExtTables();
|
||||
$this->setBackendUserAspect(GeneralUtility::makeInstance(Context::class), $GLOBALS['BE_USER']);
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the backend user object and returns it.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return FrontendBackendUserAuthentication|null the backend user object or null if there was no valid user found
|
||||
* @throws \TYPO3\CMS\Core\Exception
|
||||
*/
|
||||
protected function initializeBackendUser(ServerRequestInterface $request)
|
||||
{
|
||||
// New backend user object
|
||||
$backendUserObject = GeneralUtility::makeInstance(FrontendBackendUserAuthentication::class);
|
||||
$backendUserObject->start();
|
||||
$backendUserObject->unpack_uc();
|
||||
if (!empty($backendUserObject->user['uid'])) {
|
||||
$backendUserObject->fetchGroupData();
|
||||
}
|
||||
// Unset the user initialization if any setting / restriction applies
|
||||
if (!$this->isAuthenticated($backendUserObject, $request->getAttribute('normalizedParams'))) {
|
||||
$backendUserObject = null;
|
||||
}
|
||||
return $backendUserObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementing the access checks that the TYPO3 CMS bootstrap script does before a user is ever logged in.
|
||||
*
|
||||
* @param FrontendBackendUserAuthentication $user
|
||||
* @param NormalizedParams $normalizedParams
|
||||
* @return bool Returns TRUE if access is OK
|
||||
*/
|
||||
protected function isAuthenticated(FrontendBackendUserAuthentication $user, NormalizedParams $normalizedParams)
|
||||
{
|
||||
// Check IP
|
||||
$ipMask = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['IPmaskList'] ?? '');
|
||||
if ($ipMask && !GeneralUtility::cmpIP($normalizedParams->getRemoteAddress(), $ipMask)) {
|
||||
return false;
|
||||
}
|
||||
// Check SSL (https)
|
||||
if ((bool)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSL'] && !$normalizedParams->isHttps()) {
|
||||
return false;
|
||||
}
|
||||
return $user->backendCheckLogin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the backend user as aspect
|
||||
*
|
||||
* @param Context $context
|
||||
* @param BackendUserAuthentication|null $user
|
||||
*/
|
||||
protected function setBackendUserAspect(Context $context, BackendUserAuthentication $user)
|
||||
{
|
||||
$context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
|
||||
$context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user->workspace));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Http\NullResponse;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* Send content-length header.
|
||||
* Notice that all HTML content outside the length of the content-length header will be cut off!
|
||||
* Therefore content of unknown length from included PHP-scripts and if admin users are logged
|
||||
* in (admin panel might show...) or if debug mode is turned on, we disable it!
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ContentLengthResponseHeader implements MiddlewareInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Adds the content length
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$response = $handler->handle($request);
|
||||
if (
|
||||
!($response instanceof NullResponse)
|
||||
&& $GLOBALS['TSFE'] instanceof TypoScriptFrontendController
|
||||
&& $GLOBALS['TSFE']->isOutputting()) {
|
||||
if (
|
||||
(!isset($GLOBALS['TSFE']->config['config']['enableContentLengthHeader']) || $GLOBALS['TSFE']->config['config']['enableContentLengthHeader'])
|
||||
&& !$GLOBALS['TSFE']->isBackendUserLoggedIn() && !$GLOBALS['TYPO3_CONF_VARS']['FE']['debug']
|
||||
&& !$GLOBALS['TSFE']->config['config']['debug'] && !$GLOBALS['TSFE']->doWorkspacePreview()
|
||||
) {
|
||||
$response = $response->withHeader('Content-Length', (string)$response->getBody()->getSize());
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
use TYPO3\CMS\Core\Http\Dispatcher;
|
||||
use TYPO3\CMS\Core\Http\NullResponse;
|
||||
use TYPO3\CMS\Core\Http\Response;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Lightweight alternative to regular frontend requests; used when $_GET[eID] is set.
|
||||
* In the future, logic from the EidUtility will be moved to this class, however in most cases
|
||||
* a custom PSR-15 middleware will be better suited for whatever job the eID functionality does currently.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class EidHandler implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Dispatches the request to the corresponding eID class or eID script
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$eID = $request->getParsedBody()['eID'] ?? $request->getQueryParams()['eID'] ?? null;
|
||||
|
||||
if ($eID === null) {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
// Remove any output produced until now
|
||||
ob_clean();
|
||||
|
||||
/** @var Response $response */
|
||||
$response = GeneralUtility::makeInstance(Response::class);
|
||||
|
||||
if (empty($eID) || !isset($GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include'][$eID])) {
|
||||
return $response->withStatus(404, 'eID not registered');
|
||||
}
|
||||
|
||||
$configuration = $GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include'][$eID];
|
||||
|
||||
// Simple check to make sure that it's not an absolute file (to use the fallback)
|
||||
if (strpos($configuration, '::') !== false || is_callable($configuration)) {
|
||||
/** @var Dispatcher $dispatcher */
|
||||
$dispatcher = GeneralUtility::makeInstance(Dispatcher::class);
|
||||
$request = $request->withAttribute('target', $configuration);
|
||||
return $dispatcher->dispatch($request, $response) ?? new NullResponse();
|
||||
}
|
||||
trigger_error(
|
||||
'eID "' . $eID . '" is registered with a script to a file. This behaviour will be removed in TYPO3 v10.0.'
|
||||
. ' Register eID with a class::method syntax like "\MyVendor\MyExtension\Controller\MyEidController::myMethod" instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$scriptPath = GeneralUtility::getFileAbsFileName($configuration);
|
||||
if ($scriptPath === '') {
|
||||
throw new Exception('Registered eID has invalid script path.', 1518042216);
|
||||
}
|
||||
include $scriptPath;
|
||||
return new NullResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Context\UserAspect;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
|
||||
|
||||
/**
|
||||
* This middleware authenticates a Frontend User (fe_users).
|
||||
* A valid $GLOBALS['TSFE'] object is needed for the time being, being fully backwards-compatible.
|
||||
*/
|
||||
class FrontendUserAuthenticator implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Creates a frontend user authentication object, tries to authenticate a user
|
||||
* and stores the object in $GLOBALS['TSFE']->fe_user.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$frontendUser = GeneralUtility::makeInstance(FrontendUserAuthentication::class);
|
||||
|
||||
// List of page IDs where to look for frontend user records
|
||||
$pid = $request->getParsedBody()['pid'] ?? $request->getQueryParams()['pid'] ?? 0;
|
||||
if ($pid) {
|
||||
$frontendUser->checkPid_value = implode(',', GeneralUtility::intExplode(',', $pid));
|
||||
}
|
||||
|
||||
// Check if a session is transferred, and update the cookie parameters
|
||||
$frontendSessionKey = $request->getParsedBody()['FE_SESSION_KEY'] ?? $request->getQueryParams()['FE_SESSION_KEY'] ?? '';
|
||||
if ($frontendSessionKey) {
|
||||
$request = $this->transferFrontendUserSession($frontendUser, $request, $frontendSessionKey);
|
||||
}
|
||||
|
||||
// Authenticate now
|
||||
$frontendUser->start();
|
||||
$frontendUser->unpack_uc();
|
||||
|
||||
// Keep the backwards-compatibility for TYPO3 v9, to have the fe_user within the global TSFE object
|
||||
$GLOBALS['TSFE']->fe_user = $frontendUser;
|
||||
|
||||
// Call hook for possible manipulation of frontend user object
|
||||
// This hook is kept for compatibility reasons, however, it should be fairly simple to add a custom middleware
|
||||
// for this purpose
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['initFEuser'])) {
|
||||
trigger_error('The "initFEuser" hook will be removed in TYPO3 v10.0 in favor of PSR-15. Use a middleware instead.', E_USER_DEPRECATED);
|
||||
$_params = ['pObj' => &$GLOBALS['TSFE']];
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['initFEuser'] as $_funcRef) {
|
||||
GeneralUtility::callUserFunction($_funcRef, $_params, $GLOBALS['TSFE']);
|
||||
}
|
||||
}
|
||||
|
||||
// Register the frontend user as aspect
|
||||
$this->setFrontendUserAspect(GeneralUtility::makeInstance(Context::class), $frontendUser);
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* It's possible to transfer a frontend user session via a GET/POST parameter 'FE_SESSION_KEY'.
|
||||
* In the future, this logic should be moved into the FrontendUserAuthentication object directly,
|
||||
* but only if FrontendUserAuthentication does not request superglobals (like $_COOKIE) anymore.
|
||||
*
|
||||
* @param FrontendUserAuthentication $frontendUser
|
||||
* @param ServerRequestInterface $request
|
||||
* @param string $frontendSessionKey
|
||||
* @return ServerRequestInterface
|
||||
*/
|
||||
protected function transferFrontendUserSession(
|
||||
FrontendUserAuthentication $frontendUser,
|
||||
ServerRequestInterface $request,
|
||||
string $frontendSessionKey
|
||||
): ServerRequestInterface {
|
||||
list($sessionId, $hash) = explode('-', $frontendSessionKey);
|
||||
// If the session key hash check is OK, set the cookie
|
||||
if (hash_equals(md5($sessionId . '/' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']), (string)$hash)) {
|
||||
$cookieName = FrontendUserAuthentication::getCookieName();
|
||||
|
||||
// keep the global cookie overwriting for now, as long as FrontendUserAuthentication does not
|
||||
// use the request object for fetching the cookie information.
|
||||
$_COOKIE[$cookieName] = $sessionId;
|
||||
if (isset($_SERVER['HTTP_COOKIE'])) {
|
||||
// See http://forge.typo3.org/issues/27740
|
||||
$_SERVER['HTTP_COOKIE'] .= ';' . $cookieName . '=' . $sessionId;
|
||||
}
|
||||
// Add the cookie to the Server Request object
|
||||
$cookieParams = $request->getCookieParams();
|
||||
$cookieParams[$cookieName] = $sessionId;
|
||||
$request = $request->withCookieParams($cookieParams);
|
||||
// @deprecated: we override the current request because it was enriched by cookie information here.
|
||||
$GLOBALS['TYPO3_REQUEST'] = $request;
|
||||
$frontendUser->forceSetCookie = true;
|
||||
$frontendUser->dontSetCookie = false;
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the frontend user as aspect
|
||||
*
|
||||
* @param Context $context
|
||||
* @param AbstractUserAuthentication $user
|
||||
*/
|
||||
protected function setFrontendUserAspect(Context $context, AbstractUserAuthentication $user)
|
||||
{
|
||||
$context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\ErrorController;
|
||||
|
||||
/**
|
||||
* Checks whether the whole Frontend should be put into "Page unavailable mode"
|
||||
* unless the devIPMask matches the current visitor's IP.
|
||||
*
|
||||
* The global setting $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_force']
|
||||
* is used for turning on the maintenance mode.
|
||||
*/
|
||||
class MaintenanceMode implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Calls the "unavailableAction" of the error controller if the system is in maintenance mode.
|
||||
* This only applies if the REMOTE_ADDR does not match the devIpMask
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_force']
|
||||
&& !GeneralUtility::cmpIP(
|
||||
$request->getAttribute('normalizedParams')->getRemoteAddress(),
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
|
||||
)
|
||||
) {
|
||||
return GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($request, 'This page is temporarily unavailable.');
|
||||
}
|
||||
// Continue the regular stack if no maintenance mode is active
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Frontend\Utility\CompressionUtility;
|
||||
|
||||
/**
|
||||
* Sets up output compression
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class OutputCompression implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Clears all output and checks if a compression level is set
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
// Throw away all output that may have happened during bootstrapping by weird extensions
|
||||
ob_clean();
|
||||
// Initialize output compression if configured
|
||||
$this->initializeOutputCompression();
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize output compression if configured
|
||||
*/
|
||||
protected function initializeOutputCompression()
|
||||
{
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'] && extension_loaded('zlib')) {
|
||||
if (MathUtility::canBeInterpretedAsInteger($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'])) {
|
||||
@ini_set('zlib.output_compression_level', (string)$GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']);
|
||||
}
|
||||
ob_start([GeneralUtility::makeInstance(CompressionUtility::class), 'compressionOutputHandler']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Routing\PageArguments;
|
||||
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\ErrorController;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
|
||||
use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
|
||||
|
||||
/**
|
||||
* This middleware validates given request parameters against the common "cHash" functionality.
|
||||
*/
|
||||
class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* The cHash Service class used for cHash related functionality
|
||||
*
|
||||
* @var CacheHashCalculator
|
||||
*/
|
||||
protected $cacheHashCalculator;
|
||||
|
||||
/**
|
||||
* @var TypoScriptFrontendController
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* @param TypoScriptFrontendController|null $controller
|
||||
*/
|
||||
public function __construct(TypoScriptFrontendController $controller = null)
|
||||
{
|
||||
$this->controller = $controller ?? $GLOBALS['TSFE'];
|
||||
$this->cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the &cHash parameter against the other $queryParameters / GET parameters
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$pageNotFoundOnValidationError = (bool)($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError'] ?? true);
|
||||
$pageArguments = $request->getAttribute('routing', null);
|
||||
if ($this->controller->no_cache && !$pageNotFoundOnValidationError) {
|
||||
// No need to test anything if caching was already disabled.
|
||||
} else {
|
||||
// Evaluate the cache hash parameter or dynamic arguments when coming from a Site-based routing
|
||||
if ($pageArguments instanceof PageArguments) {
|
||||
$queryParams = $pageArguments->getDynamicArguments();
|
||||
} else {
|
||||
$queryParams = $request->getQueryParams();
|
||||
}
|
||||
if (!empty($queryParams) && !$this->evaluateCacheHashParameter($queryParams, $pageNotFoundOnValidationError)) {
|
||||
// cHash was given, but nothing to be calculated, so let's do a redirect to the current page
|
||||
// but without the cHash
|
||||
if ($this->controller->cHash && empty($this->controller->cHash_array)) {
|
||||
$uri = $request->getUri();
|
||||
unset($queryParams['cHash']);
|
||||
$uri = $uri->withQuery(HttpUtility::buildQueryString($queryParams));
|
||||
return new RedirectResponse($uri, 308);
|
||||
}
|
||||
return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
||||
$request,
|
||||
'Request parameters could not be validated (&cHash comparison failed)',
|
||||
['code' => PageAccessFailureReasons::CACHEHASH_COMPARISON_FAILED]
|
||||
);
|
||||
}
|
||||
}
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a hash string based on additional parameters in the url.
|
||||
*
|
||||
* Calculated hash is stored in $this->controller->cHash_array.
|
||||
* This is used to cache pages with more parameters than just id and type.
|
||||
*
|
||||
* @see TypoScriptFrontendController::reqCHash()
|
||||
* @param array<string, string> $queryParams GET parameters
|
||||
* @param bool $pageNotFoundOnCacheHashError see $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']
|
||||
* @return bool if false, then a PageNotFound response is triggered
|
||||
*/
|
||||
protected function evaluateCacheHashParameter(array $queryParams, bool $pageNotFoundOnCacheHashError): bool
|
||||
{
|
||||
if ($this->controller->cHash !== '') {
|
||||
// Make sure we use the page uid and not the page alias
|
||||
$queryParams['id'] = $this->controller->id;
|
||||
$relevantParameters = $this->cacheHashCalculator->getRelevantParameters(HttpUtility::buildQueryString($queryParams));
|
||||
$this->controller->cHash_array = $relevantParameters;
|
||||
// cHash was given, but nothing to be calculated, so cHash is unset and all is good.
|
||||
if (empty($relevantParameters)) {
|
||||
$this->logger->notice('The incoming cHash "' . $this->controller->cHash . '" is given but not needed. cHash is unset');
|
||||
return false;
|
||||
}
|
||||
$calculatedCacheHash = $this->cacheHashCalculator->calculateCacheHash($relevantParameters);
|
||||
if (!hash_equals($calculatedCacheHash, $this->controller->cHash)) {
|
||||
// Early return to trigger the error controller
|
||||
if ($pageNotFoundOnCacheHashError) {
|
||||
return false;
|
||||
}
|
||||
$this->controller->no_cache = true;
|
||||
$this->getTimeTracker()->setTSlogMessage('The incoming cHash "' . $this->controller->cHash . '" and calculated cHash "' . $calculatedCacheHash . '" did not match, so caching was disabled. The fieldlist used was "' . implode(',', array_keys($this->controller->cHash_array)) . '"', 2);
|
||||
}
|
||||
// No cHash is set, check if that is correct
|
||||
} elseif ($this->cacheHashCalculator->doParametersRequireCacheHash(HttpUtility::buildQueryString($queryParams))) {
|
||||
// Will disable caching
|
||||
$this->controller->reqCHash();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TimeTracker
|
||||
*/
|
||||
protected function getTimeTracker(): TimeTracker
|
||||
{
|
||||
return GeneralUtility::makeInstance(TimeTracker::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Context\UserAspect;
|
||||
use TYPO3\CMS\Core\Context\WorkspaceAspect;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendWorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Routing\PageArguments;
|
||||
use TYPO3\CMS\Core\Routing\RouteNotFoundException;
|
||||
use TYPO3\CMS\Core\Routing\SiteRouteResult;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\ErrorController;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
|
||||
|
||||
/**
|
||||
* Process the ID, type and other parameters.
|
||||
* After this point we have an array, TSFE->page, which is the page-record of the current page, $TSFE->id.
|
||||
*
|
||||
* Now, if there is a backend user logged in and he has NO access to this page,
|
||||
* then re-evaluate the id shown!
|
||||
*/
|
||||
class PageResolver implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var TypoScriptFrontendController
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
public function __construct(TypoScriptFrontendController $controller = null)
|
||||
{
|
||||
$this->controller = $controller ?? $GLOBALS['TSFE'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the page ID
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
// First, resolve the root page of the site, the Page ID of the current domain
|
||||
if (($site = $request->getAttribute('site', null)) instanceof SiteInterface) {
|
||||
$this->controller->domainStartPage = $site->getRootPageId();
|
||||
}
|
||||
$language = $request->getAttribute('language', null);
|
||||
|
||||
$hasSiteConfiguration = $language instanceof SiteLanguage && $site instanceof Site;
|
||||
|
||||
// Resolve the page ID based on TYPO3's native routing functionality
|
||||
if ($hasSiteConfiguration) {
|
||||
/** @var SiteRouteResult $previousResult */
|
||||
$previousResult = $request->getAttribute('routing', null);
|
||||
if (!$previousResult) {
|
||||
return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
||||
$request,
|
||||
'The requested page does not exist',
|
||||
['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
|
||||
);
|
||||
}
|
||||
|
||||
$requestId = (string)($request->getQueryParams()['id'] ?? '');
|
||||
if (!empty($requestId)) {
|
||||
$page = $this->resolvePageId($requestId);
|
||||
if ($page === null) {
|
||||
return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
||||
$request,
|
||||
'The requested page does not exist',
|
||||
['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
|
||||
);
|
||||
}
|
||||
// Legacy URIs (?id=12345) takes precedence, not matter if a route is given
|
||||
$pageArguments = new PageArguments(
|
||||
(int)($page['l10n_parent'] ?: $page['uid']),
|
||||
(string)($request->getQueryParams()['type'] ?? '0'),
|
||||
[],
|
||||
[],
|
||||
$request->getQueryParams()
|
||||
);
|
||||
} else {
|
||||
// Check for the route
|
||||
try {
|
||||
$pageArguments = $site->getRouter()->matchRequest($request, $previousResult);
|
||||
} catch (RouteNotFoundException $e) {
|
||||
return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
||||
$request,
|
||||
'The requested page does not exist',
|
||||
['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!$pageArguments->getPageId()) {
|
||||
return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
||||
$request,
|
||||
'The requested page does not exist',
|
||||
['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
|
||||
);
|
||||
}
|
||||
|
||||
$this->controller->id = $pageArguments->getPageId();
|
||||
$this->controller->type = $pageArguments->getPageType() ?? $this->controller->type;
|
||||
$this->controller->MP = $pageArguments->getArguments()['MP'] ?? $this->controller->MP;
|
||||
$request = $request->withAttribute('routing', $pageArguments);
|
||||
// stop in case arguments are dirty (=defined twice in route and GET query parameters)
|
||||
if ($pageArguments->areDirty()) {
|
||||
return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
||||
$request,
|
||||
'The requested URL is not distinct',
|
||||
['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
|
||||
);
|
||||
}
|
||||
|
||||
// merge the PageArguments with the request query parameters
|
||||
$queryParams = array_replace_recursive($request->getQueryParams(), $pageArguments->getArguments());
|
||||
$request = $request->withQueryParams($queryParams);
|
||||
$this->controller->setPageArguments($pageArguments);
|
||||
|
||||
// At this point, we later get further route modifiers
|
||||
// for bw-compat we update $GLOBALS[TYPO3_REQUEST] to be used later in TSFE.
|
||||
$GLOBALS['TYPO3_REQUEST'] = $request;
|
||||
} else {
|
||||
// old-school page resolving for realurl, cooluri etc.
|
||||
$this->controller->siteScript = $request->getAttribute('normalizedParams')->getSiteScript();
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'])) {
|
||||
trigger_error('The "checkAlternativeIdMethods-PostProc" hook will be removed in TYPO3 v10.0 in favor of PSR-15. Use a middleware instead.', E_USER_DEPRECATED);
|
||||
$this->checkAlternativeIdMethods($this->controller);
|
||||
}
|
||||
}
|
||||
|
||||
$this->controller->determineId();
|
||||
|
||||
// No access? Then remove user & Re-evaluate the page-id
|
||||
if ($this->controller->isBackendUserLoggedIn() && !$GLOBALS['BE_USER']->doesUserHaveAccess($this->controller->page, Permission::PAGE_SHOW)) {
|
||||
unset($GLOBALS['BE_USER']);
|
||||
// Register an empty backend user as aspect
|
||||
$this->setBackendUserAspect(GeneralUtility::makeInstance(Context::class), null);
|
||||
if (!$hasSiteConfiguration) {
|
||||
$this->checkAlternativeIdMethods($this->controller);
|
||||
}
|
||||
$this->controller->determineId();
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides ways to bypass the '?id=[xxx]&type=[xx]' format, using either PATH_INFO or Server Rewrites
|
||||
*
|
||||
* Two options:
|
||||
* 1) Use PATH_INFO (also Apache) to extract id and type from that var. Does not require any special modules compiled with apache. (less typical)
|
||||
* 2) Using hook which enables features like those provided from "realurl" extension (AKA "Speaking URLs")
|
||||
*
|
||||
* @param TypoScriptFrontendController $tsfe
|
||||
*/
|
||||
protected function checkAlternativeIdMethods(TypoScriptFrontendController $tsfe)
|
||||
{
|
||||
// Call post processing function for custom URL methods.
|
||||
$_params = ['pObj' => &$tsfe];
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'] ?? [] as $_funcRef) {
|
||||
GeneralUtility::callUserFunction($_funcRef, $_params, $tsfe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pageId
|
||||
* @return array|null
|
||||
*/
|
||||
protected function resolvePageId(string $pageId): ?array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('pages');
|
||||
$queryBuilder
|
||||
->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(FrontendWorkspaceRestriction::class));
|
||||
|
||||
if (MathUtility::canBeInterpretedAsInteger($pageId)) {
|
||||
$constraint = $queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
|
||||
);
|
||||
} else {
|
||||
$constraint = $queryBuilder->expr()->eq(
|
||||
'alias',
|
||||
$queryBuilder->createNamedParameter($pageId, \PDO::PARAM_STR)
|
||||
);
|
||||
}
|
||||
|
||||
$statement = $queryBuilder
|
||||
->select('uid', 'l10n_parent', 'pid')
|
||||
->from('pages')
|
||||
->where($constraint)
|
||||
->execute();
|
||||
|
||||
$page = $statement->fetch();
|
||||
if (empty($page)) {
|
||||
return null;
|
||||
}
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the backend user as aspect
|
||||
*
|
||||
* @param Context $context
|
||||
* @param BackendUserAuthentication $user
|
||||
*/
|
||||
protected function setBackendUserAspect(Context $context, BackendUserAuthentication $user = null)
|
||||
{
|
||||
$context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
|
||||
$context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user ? $user->workspace : 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Routing\PageArguments;
|
||||
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* Initialization of TypoScriptFrontendController
|
||||
*
|
||||
* Do all necessary preparation steps for rendering
|
||||
*
|
||||
* @internal this middleware might get removed in TYPO3 v10.0.
|
||||
*/
|
||||
class PrepareTypoScriptFrontendRendering implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var TypoScriptFrontendController
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* @var TimeTracker
|
||||
*/
|
||||
protected $timeTracker;
|
||||
|
||||
public function __construct(TypoScriptFrontendController $controller = null, TimeTracker $timeTracker = null)
|
||||
{
|
||||
$this->controller = $controller ?: $GLOBALS['TSFE'];
|
||||
$this->timeTracker = $timeTracker ?: GeneralUtility::makeInstance(TimeTracker::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize TypoScriptFrontendController to the point right before rendering of the page is triggered
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param PsrRequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, PsrRequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
// Get from cache
|
||||
$this->timeTracker->push('Get Page from cache');
|
||||
// Locks may be acquired here
|
||||
$this->controller->getFromCache();
|
||||
$this->timeTracker->pull();
|
||||
// Get config if not already gotten
|
||||
// After this, we should have a valid config-array ready
|
||||
$this->controller->getConfigArray();
|
||||
|
||||
// Merge Query Parameters with config.defaultGetVars
|
||||
// This is done in getConfigArray as well, but does not override the current middleware request object
|
||||
// Since we want to stay in sync with this, the option needs to be set as well.
|
||||
$pageArguments = $request->getAttribute('routing');
|
||||
if (!empty($this->controller->config['config']['defaultGetVars.'] ?? null)) {
|
||||
$modifiedGetVars = GeneralUtility::removeDotsFromTS($this->controller->config['config']['defaultGetVars.']);
|
||||
if ($pageArguments instanceof PageArguments) {
|
||||
$pageArguments = $pageArguments->withQueryArguments($modifiedGetVars);
|
||||
$this->controller->setPageArguments($pageArguments);
|
||||
$request = $request->withAttribute('routing', $pageArguments);
|
||||
}
|
||||
if (!empty($request->getQueryParams())) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($modifiedGetVars, $request->getQueryParams());
|
||||
}
|
||||
$request = $request->withQueryParams($modifiedGetVars);
|
||||
$GLOBALS['TYPO3_REQUEST'] = $request;
|
||||
}
|
||||
|
||||
// Setting language and locale
|
||||
$this->timeTracker->push('Setting language and locale');
|
||||
$this->controller->settingLanguage();
|
||||
$this->controller->settingLocale();
|
||||
$this->timeTracker->pull();
|
||||
|
||||
// Convert POST data to utf-8 for internal processing if metaCharset is different
|
||||
if ($this->controller->metaCharset !== 'utf-8' && is_array($_POST) && !empty($_POST)) {
|
||||
$this->convertCharsetRecursivelyToUtf8($_POST, $this->controller->metaCharset);
|
||||
$GLOBALS['HTTP_POST_VARS'] = $_POST;
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$this->convertCharsetRecursivelyToUtf8($parsedBody, $this->controller->metaCharset);
|
||||
$request = $request->withParsedBody($parsedBody);
|
||||
$GLOBALS['TYPO3_REQUEST'] = $request;
|
||||
}
|
||||
|
||||
// @deprecated since TYPO3 v9.3, will be removed in TYPO3 v10.0
|
||||
$this->controller->initializeRedirectUrlHandlers(true);
|
||||
|
||||
// Hook for processing data submission to extensions
|
||||
// This is done at this point, because we need the config values
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkDataSubmission'])) {
|
||||
trigger_error('The "checkDataSubmission" hook will be removed in TYPO3 v10.0 in favor of PSR-15. Use a middleware instead.', E_USER_DEPRECATED);
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkDataSubmission'] as $className) {
|
||||
GeneralUtility::makeInstance($className)->checkDataSubmission($this->controller);
|
||||
}
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Small helper function to convert charsets for arrays to UTF-8
|
||||
*
|
||||
* @param mixed $data given by reference (string/array usually)
|
||||
* @param string $fromCharset convert FROM this charset
|
||||
*/
|
||||
protected function convertCharsetRecursivelyToUtf8(&$data, string $fromCharset)
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
if (is_array($data[$key])) {
|
||||
$this->convertCharsetRecursivelyToUtf8($data[$key], $fromCharset);
|
||||
} elseif (is_string($data[$key])) {
|
||||
$data[$key] = mb_convert_encoding($data[$key], 'utf-8', $fromCharset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Calls a hook before processing a request for the TYPO3 Frontend.
|
||||
*
|
||||
* @internal
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
class PreprocessRequestHook implements MiddlewareInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Hook to preprocess the current request
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
// Legacy functionality to check if any hook modified global GET/POST
|
||||
// This is a safety net, see RequestHandler for how this is validated.
|
||||
// This information is just a compat layer which will be removed in TYPO3 v10.0.
|
||||
$request = $request->withAttribute('_originalGetParameters', $_GET);
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$request = $request->withAttribute('_originalPostParameters', $_POST);
|
||||
}
|
||||
// Set original parameters
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preprocessRequest'])) {
|
||||
trigger_error('The "preprocessRequest" hook will be removed in TYPO3 v10.0 in favor of PSR-15. Use a middleware instead.', E_USER_DEPRECATED);
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preprocessRequest'] as $hookFunction) {
|
||||
$hookParameters = [];
|
||||
GeneralUtility::callUserFunction($hookFunction, $hookParameters, $hookParameters);
|
||||
}
|
||||
}
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Context\DateTimeAspect;
|
||||
use TYPO3\CMS\Core\Context\UserAspect;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Middleware for handling preview settings
|
||||
* used when simulating / previewing pages or content through query params when
|
||||
* previewing access or time restricted content via for example backend preview links
|
||||
*/
|
||||
class PreviewSimulator implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Context\Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->context = GeneralUtility::makeInstance(Context::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates preview settings if a backend user is logged in
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
if ((bool)$this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false)) {
|
||||
$simulatingDate = $this->simulateDate($request);
|
||||
$simulatingGroup = $this->simulateUserGroup($request);
|
||||
$showHiddenRecords = ($this->context->hasAspect('visibility') ? $this->context->getAspect('visibility')->includeHidden() : false);
|
||||
$GLOBALS['TSFE']->fePreview = ($simulatingDate || $simulatingGroup || $showHiddenRecords);
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate dates for preview functionality
|
||||
* When previewing a time restricted page from the backend, the parameter ADMCMD_simTime it added containing
|
||||
* a timestamp with the time to preview. The globals 'SIM_EXEC_TIME' and 'SIM_ACCESS_TIME' and the 'DateTimeAspect'
|
||||
* are used to simulate rendering at that point in time.
|
||||
* Ideally the global access is removed in future versions.
|
||||
* This functionality needs to be loaded after BackendAuthenticator as it is only relevant for
|
||||
* logged in backend users and needs to be done before any page resolving starts.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function simulateDate(ServerRequestInterface $request): bool
|
||||
{
|
||||
$simulatedDate = null;
|
||||
$queryTime = $request->getQueryParams()['ADMCMD_simTime'] ?? false;
|
||||
if (!$queryTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$simulatedDate = new \DateTimeImmutable('@' . $queryTime);
|
||||
if (!$simulatedDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$GLOBALS['SIM_EXEC_TIME'] = $queryTime;
|
||||
$GLOBALS['SIM_ACCESS_TIME'] = $queryTime - $queryTime % 60;
|
||||
$this->context->setAspect(
|
||||
'date',
|
||||
GeneralUtility::makeInstance(
|
||||
DateTimeAspect::class,
|
||||
$simulatedDate
|
||||
)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate user group for preview functionality
|
||||
* When previewing a page with a usergroup restriction, the parameter ADMCMD_simUser = <groupId> will be added
|
||||
* to the preview url. Simulation happens.
|
||||
* legacy: via TSFE member variables (->fe_user->user[<groupColumn>])
|
||||
* new: via Context::UserAspect
|
||||
* This functionality needs to be loaded after BackendAuthenticator as it is only relevant for
|
||||
* logged in backend users and needs to be done before any page resolving starts.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function simulateUserGroup(ServerRequestInterface $request): bool
|
||||
{
|
||||
$simulateUserGroup = (int)($request->getQueryParams()['ADMCMD_simUser'] ?? 0);
|
||||
if (!$simulateUserGroup) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$frontendUser = $GLOBALS['TSFE']->fe_user;
|
||||
$frontendUser->user[$frontendUser->usergroup_column] = $simulateUserGroup;
|
||||
// let's fake having a user with that group, too
|
||||
$frontendUser->user['uid'] = PHP_INT_MAX;
|
||||
$this->context->setAspect(
|
||||
'frontend.user',
|
||||
GeneralUtility::makeInstance(
|
||||
UserAspect::class,
|
||||
$frontendUser,
|
||||
[$simulateUserGroup]
|
||||
)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
use TYPO3\CMS\Frontend\Page\PageRepository;
|
||||
|
||||
/**
|
||||
* Checks mount points, shortcuts and redirects to the target.
|
||||
* Alternatively, checks if the current page is an redirect to an external page
|
||||
*
|
||||
* @internal this middleware might get removed in TYPO3 v10.0.
|
||||
*/
|
||||
class ShortcutAndMountPointRedirect implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var TypoScriptFrontendController
|
||||
*/
|
||||
private $controller;
|
||||
|
||||
public function __construct(TypoScriptFrontendController $controller = null)
|
||||
{
|
||||
$this->controller = $controller ?: $GLOBALS['TSFE'];
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
// Check for shortcut page and mount point redirect
|
||||
$redirectToUri = $this->getRedirectUri($request);
|
||||
if ($redirectToUri !== null && $redirectToUri !== (string)$request->getUri()) {
|
||||
return new RedirectResponse($redirectToUri, 307);
|
||||
}
|
||||
|
||||
// See if the current page is of doktype "External URL", if so, do a redirect as well.
|
||||
if (empty($this->controller->config['config']['disablePageExternalUrl'] ?? null)
|
||||
&& PageRepository::DOKTYPE_LINK === (int)$this->controller->page['doktype']) {
|
||||
$externalUrl = $this->prefixExternalPageUrl(
|
||||
$this->controller->page['url'],
|
||||
$request->getAttribute('normalizedParams')->getSiteUrl()
|
||||
);
|
||||
if (!empty($externalUrl)) {
|
||||
return new RedirectResponse($externalUrl, 303);
|
||||
}
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
protected function getRedirectUri(ServerRequestInterface $request): ?string
|
||||
{
|
||||
$redirectToUri = $this->controller->getRedirectUriForShortcut($request);
|
||||
if ($redirectToUri !== null) {
|
||||
return $redirectToUri;
|
||||
}
|
||||
return $this->controller->getRedirectUriForMountPoint($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the redirect URL for the input page row IF the doktype is set to 3.
|
||||
*
|
||||
* @param string $redirectTo The page row to return URL type for
|
||||
* @param string $sitePrefix if no protocol or relative path given, the site prefix is added
|
||||
* @return string The URL from based on the external page URL given with a prefix.
|
||||
*/
|
||||
protected function prefixExternalPageUrl(string $redirectTo, string $sitePrefix): string
|
||||
{
|
||||
$uI = parse_url($redirectTo);
|
||||
// If relative path, prefix Site URL
|
||||
// If it's a valid email without protocol, add "mailto:"
|
||||
if (!($uI['scheme'] ?? false)) {
|
||||
if (GeneralUtility::validEmail($redirectTo)) {
|
||||
$redirectTo = 'mailto:' . $redirectTo;
|
||||
} elseif ($redirectTo[0] !== '/') {
|
||||
$redirectTo = $sitePrefix . $redirectTo;
|
||||
}
|
||||
}
|
||||
return $redirectTo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Routing\SiteRouteResult;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\ErrorController;
|
||||
use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
|
||||
|
||||
/**
|
||||
* Resolves redirects of site if base is not /
|
||||
* Can be replaced or extended by extensions if GeoIP-based or user-agent based language redirects need to happen.
|
||||
*
|
||||
* Please note that the redirect usually does not contain the Query Parameters, as special query parameters
|
||||
* like "id", "L" and "cHash" could then result in an error loop.
|
||||
* One special case (adding a "/") is keeping the query parameters though.
|
||||
*/
|
||||
class SiteBaseRedirectResolver implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Redirect to default language if required
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$site = $request->getAttribute('site', null);
|
||||
$language = $request->getAttribute('language', null);
|
||||
$routeResult = $request->getAttribute('routing', null);
|
||||
|
||||
// Usually called when "https://www.example.com" was entered, but all sites have "https://www.example.com/lang-key/"
|
||||
// So a redirect to the first possible language is done.
|
||||
if ($site instanceof Site && !($language instanceof SiteLanguage)) {
|
||||
if ($routeResult instanceof SiteRouteResult && $routeResult->getTail() === '') {
|
||||
$language = $site->getDefaultLanguage();
|
||||
return new RedirectResponse($language->getBase(), 307);
|
||||
}
|
||||
return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
||||
$request,
|
||||
'The requested page does not exist',
|
||||
['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
|
||||
);
|
||||
}
|
||||
|
||||
// Language is found, and hidden but also not visible to the BE user, this needs to fail
|
||||
if ($language instanceof SiteLanguage && !$this->isLanguageEnabled($language, $GLOBALS['BE_USER'] ?? null)) {
|
||||
return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
||||
$request,
|
||||
'Page is not available in the requested language.',
|
||||
['code' => PageAccessFailureReasons::LANGUAGE_NOT_AVAILABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if ($language instanceof SiteLanguage && $routeResult instanceof SiteRouteResult) {
|
||||
$requestedUri = $request->getUri();
|
||||
$tail = $routeResult->getTail();
|
||||
// a URL was called via "/fr-FR/" but the page is actually called "/fr-FR", let's do a redirect
|
||||
if ($tail === '/') {
|
||||
$uri = $requestedUri->withPath(rtrim($requestedUri->getPath(), '/'));
|
||||
return new RedirectResponse($uri, 307);
|
||||
}
|
||||
}
|
||||
return $handler->handle($request);
|
||||
}
|
||||
/**
|
||||
* Checks if the language is allowed in Frontend, if not, check if there is valid BE user
|
||||
*
|
||||
* @param SiteLanguage|null $language
|
||||
* @param BackendUserAuthentication|null $user
|
||||
* @return bool
|
||||
*/
|
||||
protected function isLanguageEnabled(SiteLanguage $language, BackendUserAuthentication $user = null): bool
|
||||
{
|
||||
// language is hidden, check if a possible backend user is allowed to access the language
|
||||
if ($language->enabled() || ($user instanceof BackendUserAuthentication && $user->checkLanguageAccess($language->getLanguageId()))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Routing\SiteMatcher;
|
||||
use TYPO3\CMS\Core\Routing\SiteRouteResult;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Identifies if a site is configured for the request, based on "id" and "L" GET/POST parameters, or the requested
|
||||
* string.
|
||||
*
|
||||
* If a site is found, the request is populated with the found language+site objects. If none is found, the main magic
|
||||
* is handled by the PageResolver middleware.
|
||||
*/
|
||||
class SiteResolver implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var SiteMatcher
|
||||
*/
|
||||
protected $matcher;
|
||||
|
||||
public function __construct(SiteMatcher $matcher = null)
|
||||
{
|
||||
$this->matcher = $matcher ?? GeneralUtility::makeInstance(
|
||||
SiteMatcher::class,
|
||||
GeneralUtility::makeInstance(SiteFinder::class)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the site/language information by checking the page ID or the URL.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
/** @var SiteRouteResult $routeResult */
|
||||
$routeResult = $this->matcher->matchRequest($request);
|
||||
$request = $request->withAttribute('site', $routeResult->getSite());
|
||||
$request = $request->withAttribute('language', $routeResult->getLanguage());
|
||||
$request = $request->withAttribute('routing', $routeResult);
|
||||
|
||||
// At this point, we later get further route modifiers
|
||||
// for bw-compat we update $GLOBALS[TYPO3_REQUEST] to be used later in TSFE.
|
||||
$GLOBALS['TYPO3_REQUEST'] = $request;
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\RequestFactory;
|
||||
use TYPO3\CMS\Core\Http\Response;
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
|
||||
use TYPO3\CMS\Core\Routing\RouterInterface;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Resolves static routes - can return configured content directly or load content from file / urls
|
||||
*/
|
||||
class StaticRouteResolver implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Checks if there is a valid site with route configuration.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
if (($site = $request->getAttribute('site', null)) instanceof Site &&
|
||||
($configuration = $site->getConfiguration()['routes'] ?? null)
|
||||
) {
|
||||
$path = ltrim($request->getUri()->getPath(), '/');
|
||||
$routeConfig = $this->getApplicableStaticRoute($configuration, $site, $path);
|
||||
if (is_array($routeConfig)) {
|
||||
try {
|
||||
[$content, $contentType] = $this->resolveByType($request, $site, $routeConfig['type'], $routeConfig);
|
||||
} catch (InvalidRouteArgumentsException $e) {
|
||||
return new Response('Invalid route', 404, ['Content-Type' => 'text/plain']);
|
||||
}
|
||||
|
||||
return new HtmlResponse($content, 200, ['Content-Type' => $contentType]);
|
||||
}
|
||||
}
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the proper configuration for the static route in the static route configuration. Mainly:
|
||||
* - needs to have a valid "route" property
|
||||
* - needs to have a "type"
|
||||
*
|
||||
* @param array $staticRouteConfiguration the "routes" part of the site configuration
|
||||
* @param Site $site the current site where the configuration is based on
|
||||
* @param string $uriPath the path of the current request - used to match the "route" value of a single static route
|
||||
* @return array|null the configuration for the static route that matches, or null if no route is given
|
||||
*/
|
||||
protected function getApplicableStaticRoute(array $staticRouteConfiguration, Site $site, string $uriPath): ?array
|
||||
{
|
||||
$routeNames = array_map(function (?string $route) use ($site) {
|
||||
if ($route === null || $route === '') {
|
||||
return null;
|
||||
}
|
||||
return ltrim(trim($site->getBase()->getPath(), '/') . '/' . ltrim($route, '/'), '/');
|
||||
}, array_column($staticRouteConfiguration, 'route'));
|
||||
// Remove empty routes which would throw an error (could happen within creating a false route in the GUI)
|
||||
$routeNames = array_filter($routeNames);
|
||||
|
||||
if (in_array($uriPath, $routeNames, true)) {
|
||||
$key = array_search($uriPath, $routeNames, true);
|
||||
// Only allow routes with a type "given"
|
||||
if (isset($staticRouteConfiguration[$key]['type'])) {
|
||||
return $staticRouteConfiguration[$key];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param File $file
|
||||
* @return array
|
||||
*/
|
||||
protected function getFromFile(File $file): array
|
||||
{
|
||||
$content = $file->getContents();
|
||||
$contentType = $file->getMimeType();
|
||||
return [$content, $contentType];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @return array
|
||||
*/
|
||||
protected function getFromUri(string $uri): array
|
||||
{
|
||||
$requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
|
||||
$response = $requestFactory->request($uri);
|
||||
$contentType = 'text/plain; charset=utf-8';
|
||||
$content = '';
|
||||
if ($response->getStatusCode() === 200) {
|
||||
$content = $response->getBody()->getContents();
|
||||
$contentType = $response->getHeader('Content-Type');
|
||||
}
|
||||
|
||||
return [$content, $contentType];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @param Site $site
|
||||
* @param array $urlParams
|
||||
* @return string
|
||||
* @throws InvalidRouteArgumentsException
|
||||
*/
|
||||
protected function getPageUri(ServerRequestInterface $request, Site $site, array $urlParams): string
|
||||
{
|
||||
$parameters = [];
|
||||
// Add additional parameters, if set via TypoLink
|
||||
if (isset($urlParams['parameters'])) {
|
||||
parse_str($urlParams['parameters'], $parameters);
|
||||
}
|
||||
$parameters['type'] = $urlParams['pagetype'] ?? 0;
|
||||
$parameters['_language'] = $request->getAttribute('language', null);
|
||||
$uri = $site->getRouter()->generateUri(
|
||||
(int)$urlParams['pageuid'],
|
||||
$parameters,
|
||||
'',
|
||||
RouterInterface::ABSOLUTE_URL
|
||||
);
|
||||
return (string)$uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @param Site $site
|
||||
* @param string $type
|
||||
* @param array $routeConfig
|
||||
* @return array
|
||||
* @throws InvalidRouteArgumentsException
|
||||
*/
|
||||
protected function resolveByType(ServerRequestInterface $request, Site $site, string $type, array $routeConfig): array
|
||||
{
|
||||
switch ($type) {
|
||||
case 'staticText':
|
||||
$content = $routeConfig['content'];
|
||||
$contentType = 'text/plain; charset=utf-8';
|
||||
break;
|
||||
case 'uri':
|
||||
$linkService = GeneralUtility::makeInstance(LinkService::class);
|
||||
$urlParams = $linkService->resolve($routeConfig['source']);
|
||||
if ($urlParams['type'] === 'url' || $urlParams['type'] === 'page') {
|
||||
$uri = $urlParams['url'] ?? $this->getPageUri($request, $site, $urlParams);
|
||||
[$content, $contentType] = $this->getFromUri($uri);
|
||||
} elseif ($urlParams['type'] === 'file') {
|
||||
[$content, $contentType] = $this->getFromFile($urlParams['file']);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Can only handle URIs of type page, url or file.', 1537348076);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException(
|
||||
'Can only handle static file configurations with type uri or staticText.',
|
||||
1537348083
|
||||
);
|
||||
}
|
||||
return [$content, $contentType];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Initializes the time tracker (singleton) for the whole TYPO3 Frontend
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TimeTrackerInitialization implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Starting time tracking (by setting up a singleton object)
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['cookieName']) ?: 'be_typo_user';
|
||||
$timeTracker = GeneralUtility::makeInstance(
|
||||
TimeTracker::class,
|
||||
!empty($request->getCookieParams()[$configuredCookieName])
|
||||
);
|
||||
$timeTracker->start();
|
||||
$timeTracker->push('');
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Middleware;
|
||||
|
||||
/*
|
||||
* 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 Doctrine\DBAL\Exception\ConnectionException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Error\Http\ServiceUnavailableException;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\ErrorController;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* Creates an instance of TypoScriptFrontendController and makes this globally available
|
||||
* via $GLOBALS['TSFE'].
|
||||
*
|
||||
* For now, GeneralUtility::_GP() is used in favor of $request->getQueryParams() due to
|
||||
* hooks who could have $_GET/$_POST modified before.
|
||||
*
|
||||
* @internal this middleware might get removed in TYPO3 v10.0.
|
||||
*/
|
||||
class TypoScriptFrontendInitialization implements MiddlewareInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* Creates an instance of TSFE and sets it as a global variable,
|
||||
* also pings the database in order ensure a valid database connection.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$GLOBALS['TSFE'] = GeneralUtility::makeInstance(
|
||||
TypoScriptFrontendController::class,
|
||||
null,
|
||||
GeneralUtility::_GP('id'),
|
||||
GeneralUtility::_GP('type'),
|
||||
null,
|
||||
GeneralUtility::_GP('cHash'),
|
||||
null,
|
||||
GeneralUtility::_GP('MP')
|
||||
);
|
||||
if (GeneralUtility::_GP('no_cache')) {
|
||||
$GLOBALS['TSFE']->set_no_cache('&no_cache=1 has been supplied, so caching is disabled! URL: "' . (string)$request->getUri() . '"');
|
||||
}
|
||||
|
||||
// Set up the database connection and see if the connection can be established
|
||||
try {
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages');
|
||||
$connection->connect();
|
||||
} catch (ConnectionException | \RuntimeException $exception) {
|
||||
$message = 'Cannot connect to the configured database';
|
||||
$this->logger->emergency($message, ['exception' => $exception]);
|
||||
try {
|
||||
return GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($request, $message);
|
||||
} catch (ServiceUnavailableException $e) {
|
||||
throw new ServiceUnavailableException($message, 1526013723);
|
||||
}
|
||||
}
|
||||
// Call post processing function for DB connection:
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['connectToDB'])) {
|
||||
trigger_error('The "connectToDB" hook will be removed in TYPO3 v10.0 in favor of PSR-15. Use a middleware instead.', E_USER_DEPRECATED);
|
||||
$_params = ['pObj' => &$GLOBALS['TSFE']];
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['connectToDB'] as $_funcRef) {
|
||||
GeneralUtility::callUserFunction($_funcRef, $_params, $GLOBALS['TSFE']);
|
||||
}
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Page;
|
||||
|
||||
/*
|
||||
* 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\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Logic for cHash calculation
|
||||
*/
|
||||
class CacheHashCalculator implements SingletonInterface
|
||||
{
|
||||
/**
|
||||
* @var array Parameters that are relevant for cacheHash calculation. Optional.
|
||||
*/
|
||||
protected $cachedParametersWhiteList = [];
|
||||
|
||||
/**
|
||||
* @var array Parameters that are not relevant for cacheHash calculation.
|
||||
*/
|
||||
protected $excludedParameters = [];
|
||||
|
||||
/**
|
||||
* @var array Parameters that forces a presence of a valid cacheHash.
|
||||
*/
|
||||
protected $requireCacheHashPresenceParameters = [];
|
||||
|
||||
/**
|
||||
* @var array Parameters that need a value to be relevant for cacheHash calculation
|
||||
*/
|
||||
protected $excludedParametersIfEmpty = [];
|
||||
|
||||
/**
|
||||
* @var bool Whether to exclude all empty parameters for cacheHash calculation
|
||||
*/
|
||||
protected $excludeAllEmptyParameters = false;
|
||||
|
||||
/**
|
||||
* Initialise class properties by using the relevant TYPO3 configuration
|
||||
*
|
||||
* @param array $configuration
|
||||
*/
|
||||
public function __construct(array $configuration = null)
|
||||
{
|
||||
$configuration = $configuration ?? $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash'] ?? [];
|
||||
$this->setConfiguration($configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the cHash based on the provided parameters
|
||||
*
|
||||
* @param array $params Array of cHash key-value pairs
|
||||
* @return string Hash of all the values
|
||||
*/
|
||||
public function calculateCacheHash(array $params)
|
||||
{
|
||||
return !empty($params) ? md5(serialize($params)) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cHash based on provided query parameters and added values from internal call
|
||||
*
|
||||
* @param string $queryString Query-parameters: "&xxx=yyy&zzz=uuu
|
||||
* @return string Hash of all the values
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function generateForParameters($queryString)
|
||||
{
|
||||
$cacheHashParams = $this->getRelevantParameters($queryString);
|
||||
return $this->calculateCacheHash($cacheHashParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a parameter of the given $queryString requires cHash calculation
|
||||
*
|
||||
* @param string $queryString
|
||||
* @return bool
|
||||
*/
|
||||
public function doParametersRequireCacheHash($queryString)
|
||||
{
|
||||
if (empty($this->requireCacheHashPresenceParameters)) {
|
||||
return false;
|
||||
}
|
||||
$hasRequiredParameter = false;
|
||||
$parameterNames = array_keys($this->splitQueryStringToArray($queryString));
|
||||
foreach ($parameterNames as $parameterName) {
|
||||
if (in_array($parameterName, $this->requireCacheHashPresenceParameters, true)) {
|
||||
$hasRequiredParameter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $hasRequiredParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the input query-parameters into an array with certain parameters filtered out.
|
||||
* Used to create the cHash value
|
||||
*
|
||||
* @param string $queryString Query-parameters: "&xxx=yyy&zzz=uuu
|
||||
* @return array Array with key/value pairs of query-parameters WITHOUT a certain list of
|
||||
* @throws \RuntimeException
|
||||
* @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typoLink()
|
||||
*/
|
||||
public function getRelevantParameters($queryString)
|
||||
{
|
||||
$parameters = $this->splitQueryStringToArray($queryString);
|
||||
$relevantParameters = [];
|
||||
foreach ($parameters as $parameterName => $parameterValue) {
|
||||
if ($this->isAdminPanelParameter($parameterName) || $this->isExcludedParameter($parameterName) || $this->isCoreParameter($parameterName)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->hasCachedParametersWhiteList() && !$this->isInCachedParametersWhiteList($parameterName)) {
|
||||
continue;
|
||||
}
|
||||
if (($parameterValue === null || $parameterValue === '') && !$this->isAllowedWithEmptyValue($parameterName)) {
|
||||
continue;
|
||||
}
|
||||
$relevantParameters[$parameterName] = $parameterValue;
|
||||
}
|
||||
if (!empty($relevantParameters)) {
|
||||
if (empty($parameters['id'])) {
|
||||
throw new \RuntimeException('ID parameter needs to be passed for the cHash calculation!', 1467983513);
|
||||
}
|
||||
$relevantParameters['id'] = $parameters['id'];
|
||||
// Finish and sort parameters array by keys:
|
||||
$relevantParameters['encryptionKey'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'];
|
||||
ksort($relevantParameters);
|
||||
}
|
||||
return $relevantParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the query string and converts it to an array.
|
||||
* Unlike parse_str it only creates an array with one level.
|
||||
*
|
||||
* e.g. foo[bar]=baz will be array('foo[bar]' => 'baz')
|
||||
*
|
||||
* @param string $queryString
|
||||
* @return array
|
||||
*/
|
||||
protected function splitQueryStringToArray($queryString)
|
||||
{
|
||||
$parameters = array_filter(explode('&', ltrim($queryString, '?')));
|
||||
$parameterArray = [];
|
||||
foreach ($parameters as $parameter) {
|
||||
// should not remove empty values with trimExplode, otherwise cases like &=value, value is used as parameterName.
|
||||
$parts = GeneralUtility::trimExplode('=', $parameter, false);
|
||||
$parameterName = $parts[0];
|
||||
$parameterValue = $parts[1] ?? '';
|
||||
if (trim($parameterName) === '') {
|
||||
// This parameter cannot appear in $_GET in PHP even if its value is not empty, so it should be ignored!
|
||||
continue;
|
||||
}
|
||||
$parameterArray[rawurldecode($parameterName)] = rawurldecode($parameterValue);
|
||||
}
|
||||
return $parameterArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given parameter is out of a known data-set starting
|
||||
* with ADMCMD or starts with TSFE_ADMIN_PANEL.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAdminPanelParameter($key)
|
||||
{
|
||||
return $key === 'ADMCMD_noBeUser' || $key === 'ADMCMD_view' || $key === 'ADMCMD_editIcons'
|
||||
|| $key === 'ADMCMD_simUser' || $key === 'ADMCMD_simTime' || $key === 'ADMCMD_prev'
|
||||
|| stripos($key, 'TSFE_ADMIN_PANEL') !== false && preg_match('/TSFE_ADMIN_PANEL\\[.*?\\]/', $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given parameter is a core parameter
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
protected function isCoreParameter($key)
|
||||
{
|
||||
return $key === 'id' || $key === 'type' || $key === 'no_cache' || $key === 'cHash' || $key === 'MP' || $key === 'ftu';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given parameter should be excluded from cHash calculation
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
protected function isExcludedParameter($key)
|
||||
{
|
||||
return in_array($key, $this->excludedParameters, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given parameter is an exclusive parameter for cHash calculation
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
protected function isInCachedParametersWhiteList($key)
|
||||
{
|
||||
return in_array($key, $this->cachedParametersWhiteList, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether cachedParametersWhiteList parameters are configured
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasCachedParametersWhiteList()
|
||||
{
|
||||
return !empty($this->cachedParametersWhiteList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given parameter may be used even with an empty value
|
||||
*
|
||||
* @param $key
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAllowedWithEmptyValue($key)
|
||||
{
|
||||
return !($this->excludeAllEmptyParameters || in_array($key, $this->excludedParametersIfEmpty, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through the configuration array and calls the accordant
|
||||
* getters with the value.
|
||||
*
|
||||
* @param array $configuration
|
||||
*/
|
||||
public function setConfiguration(array $configuration)
|
||||
{
|
||||
foreach ($configuration as $name => $value) {
|
||||
$setterName = 'set' . ucfirst($name);
|
||||
if (method_exists($this, $setterName)) {
|
||||
$this->{$setterName}($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $cachedParametersWhiteList
|
||||
*/
|
||||
protected function setCachedParametersWhiteList(array $cachedParametersWhiteList)
|
||||
{
|
||||
$this->cachedParametersWhiteList = $cachedParametersWhiteList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $excludeAllEmptyParameters
|
||||
*/
|
||||
protected function setExcludeAllEmptyParameters($excludeAllEmptyParameters)
|
||||
{
|
||||
$this->excludeAllEmptyParameters = $excludeAllEmptyParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $excludedParameters
|
||||
*/
|
||||
protected function setExcludedParameters(array $excludedParameters)
|
||||
{
|
||||
$this->excludedParameters = $excludedParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $excludedParametersIfEmpty
|
||||
*/
|
||||
protected function setExcludedParametersIfEmpty(array $excludedParametersIfEmpty)
|
||||
{
|
||||
$this->excludedParametersIfEmpty = $excludedParametersIfEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $requireCacheHashPresenceParameters
|
||||
*/
|
||||
protected function setRequireCacheHashPresenceParameters(array $requireCacheHashPresenceParameters)
|
||||
{
|
||||
$this->requireCacheHashPresenceParameters = $requireCacheHashPresenceParameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Page;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* Handles the redirection for external URL pages.
|
||||
* @deprecated since TYPO3 v9.3, will be removed in TYPO3 v10.0. The functionality has been moved into a PSR-15 middleware.
|
||||
*/
|
||||
class ExternalPageUrlHandler implements \TYPO3\CMS\Frontend\Http\UrlHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $externalUrl = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
trigger_error('ExternalPageUrlHandler has been moved into a PSR-15 middleware and will be removed in TYPO3 v10.0. In order to modify the external page redirection, use a PSR-15 middleware as well.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if external URLs are enabled and if the current page points to an external URL.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canHandleCurrentUrl(): bool
|
||||
{
|
||||
$tsfe = $this->getTypoScriptFrontendController();
|
||||
|
||||
if (!empty($tsfe->config['config']['disablePageExternalUrl'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->externalUrl = $tsfe->sys_page->getExtURL($tsfe->page);
|
||||
if (empty($this->externalUrl)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the user to the detected external URL.
|
||||
*/
|
||||
public function handle(): ResponseInterface
|
||||
{
|
||||
return new RedirectResponse($this->externalUrl, 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TypoScriptFrontendController
|
||||
*/
|
||||
protected function getTypoScriptFrontendController(): TypoScriptFrontendController
|
||||
{
|
||||
return $GLOBALS['TSFE'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Frontend\Page;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains a list of all reasons that TYPO3 internally uses when a page cannot be found/rendered etc.
|
||||
* See TypoScriptFrontendController for more details and the usage.
|
||||
*/
|
||||
final class PageAccessFailureReasons
|
||||
{
|
||||
// Page resolving issues
|
||||
public const NO_PAGES_FOUND = 'page.database.empty';
|
||||
public const PAGE_NOT_FOUND = 'page';
|
||||
public const PAGE_ALIAS_NOT_FOUND = 'page.alias';
|
||||
public const ROOTLINE_BROKEN = 'page.rootline';
|
||||
|
||||
// Page configuration issues
|
||||
public const RENDERING_INSTRUCTIONS_NOT_FOUND = 'rendering_instructions';
|
||||
public const RENDERING_INSTRUCTIONS_NOT_CONFIGURED = 'rendering_instructions.type';
|
||||
|
||||
// Validation errors
|
||||
public const CACHEHASH_COMPARISON_FAILED = 'cache_hash.comparison';
|
||||
public const CACHEHASH_EMPTY = 'cache_hash.empty';
|
||||
|
||||
// Language-related issues
|
||||
public const LANGUAGE_NOT_AVAILABLE = 'language';
|
||||
public const LANGUAGE_NOT_AVAILABLE_STRICT_MODE = 'language.strict';
|
||||
public const LANGUAGE_AND_FALLBACKS_NOT_AVAILABLE = 'language.fallbacks';
|
||||
public const LANGUAGE_DEFAULT_NOT_AVAILABLE = 'language.default';
|
||||
|
||||
// Access restrictions
|
||||
public const ACCESS_DENIED_GENERAL = 'access';
|
||||
public const ACCESS_DENIED_PAGE_NOT_RESOLVED = 'access.page';
|
||||
public const ACCESS_DENIED_SUBSECTION_NOT_RESOLVED = 'access.subsection';
|
||||
public const ACCESS_DENIED_HOST_PAGE_MISMATCH = 'access.host_mismatch';
|
||||
public const ACCESS_DENIED_INVALID_PAGETYPE = 'access.pagetype';
|
||||
|
||||
// System errors
|
||||
public const DATABASE_CONNECTION_FAILED = 'system.database';
|
||||
|
||||
/**
|
||||
* Labels for the status codes
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $messages = [
|
||||
self::NO_PAGES_FOUND => 'No page on rootlevel found',
|
||||
self::PAGE_NOT_FOUND => 'The requested page does not exist',
|
||||
self::PAGE_ALIAS_NOT_FOUND => 'The requested page alias does not exist',
|
||||
|
||||
self::RENDERING_INSTRUCTIONS_NOT_FOUND => 'No TypoScript template found',
|
||||
self::RENDERING_INSTRUCTIONS_NOT_CONFIGURED => 'The page is not configured',
|
||||
|
||||
self::CACHEHASH_COMPARISON_FAILED => 'Request parameters could not be validated (&cHash comparison failed)',
|
||||
self::CACHEHASH_EMPTY => 'Request parameters could not be validated (&cHash empty)',
|
||||
|
||||
self::LANGUAGE_NOT_AVAILABLE => 'Page is not available in the requested language',
|
||||
self::LANGUAGE_NOT_AVAILABLE_STRICT_MODE => 'Page is not available in the requested language (strict)',
|
||||
self::LANGUAGE_AND_FALLBACKS_NOT_AVAILABLE => 'Page is not available in the requested language (fallbacks did not apply)',
|
||||
self::LANGUAGE_DEFAULT_NOT_AVAILABLE => 'Page is not available in default language',
|
||||
|
||||
self::ACCESS_DENIED_GENERAL => 'The requested page was not accessible',
|
||||
self::ACCESS_DENIED_PAGE_NOT_RESOLVED => 'ID was not an accessible page',
|
||||
self::ACCESS_DENIED_SUBSECTION_NOT_RESOLVED => 'Subsection was found and not accessible',
|
||||
self::ACCESS_DENIED_HOST_PAGE_MISMATCH => 'ID was outside the domain',
|
||||
self::ACCESS_DENIED_INVALID_PAGETYPE => 'The requested page type cannot be rendered',
|
||||
|
||||
self::DATABASE_CONNECTION_FAILED => 'Database Connection failed',
|
||||
self::ROOTLINE_BROKEN => 'The requested page did not have a proper connection to the tree-root'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $reasonCode a valid reason code (see above)
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageForReason(string $reasonCode): string
|
||||
{
|
||||
if (!isset($this->messages[$reasonCode])) {
|
||||
throw new \InvalidArgumentException('No message for page access reason code "' . $reasonCode . '" found.', 1529299833);
|
||||
}
|
||||
return $this->messages[$reasonCode];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Page;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes which hook into pageSelect and do additional getPage processing
|
||||
*/
|
||||
interface PageRepositoryGetPageHookInterface
|
||||
{
|
||||
/**
|
||||
* Modifies the DB params
|
||||
*
|
||||
* @param int $uid The page ID
|
||||
* @param bool $disableGroupAccessCheck If set, the check for group access is disabled. VERY rarely used
|
||||
* @param \TYPO3\CMS\Frontend\Page\PageRepository $parentObject Parent object
|
||||
*/
|
||||
public function getPage_preProcess(&$uid, &$disableGroupAccessCheck, PageRepository $parentObject);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Page;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes which hook into \TYPO3\CMS\Frontend\Page\PageRepository
|
||||
*/
|
||||
interface PageRepositoryGetPageOverlayHookInterface
|
||||
{
|
||||
/**
|
||||
* enables to preprocess the pageoverlay
|
||||
*
|
||||
* @param array $pageInput The page record
|
||||
* @param int $lUid The overlay language
|
||||
* @param \TYPO3\CMS\Frontend\Page\PageRepository $parent The calling parent object
|
||||
*/
|
||||
public function getPageOverlay_preProcess(&$pageInput, &$lUid, PageRepository $parent);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Frontend\Page;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes which hook into \TYPO3\CMS\Frontend\Page\PageRepository
|
||||
*/
|
||||
interface PageRepositoryGetRecordOverlayHookInterface
|
||||
{
|
||||
/**
|
||||
* Enables to preprocess a record overlay
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $row
|
||||
* @param int $sys_language_content
|
||||
* @param string $OLmode
|
||||
* @param \TYPO3\CMS\Frontend\Page\PageRepository $parent
|
||||
*/
|
||||
public function getRecordOverlay_preProcess($table, &$row, &$sys_language_content, $OLmode, PageRepository $parent);
|
||||
|
||||
/**
|
||||
* Enables to postprocess a record overlay
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $row
|
||||
* @param int $sys_language_content
|
||||
* @param string $OLmode
|
||||
* @param \TYPO3\CMS\Frontend\Page\PageRepository $parent
|
||||
*/
|
||||
public function getRecordOverlay_postProcess($table, &$row, &$sys_language_content, $OLmode, PageRepository $parent);
|
||||
}
|
||||
Loaded 100 of 12716 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user