cObj->... from within you classes written to be USER or USER_INT content objects.
*/
class ContentObjectRenderer implements LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* @var array
*/
public $align = [
'center',
'right',
'left'
];
/**
* stdWrap functions in their correct order
*
* @see stdWrap()
*/
public $stdWrapOrder = [
'stdWrapPreProcess' => 'hook',
// this is a placeholder for the first Hook
'cacheRead' => 'hook',
// this is a placeholder for checking if the content is available in cache
'setContentToCurrent' => 'boolean',
'setContentToCurrent.' => 'array',
'addPageCacheTags' => 'string',
'addPageCacheTags.' => 'array',
'setCurrent' => 'string',
'setCurrent.' => 'array',
'lang.' => 'array',
'data' => 'getText',
'data.' => 'array',
'field' => 'fieldName',
'field.' => 'array',
'current' => 'boolean',
'current.' => 'array',
'cObject' => 'cObject',
'cObject.' => 'array',
'numRows.' => 'array',
// @deprecated - will be removed in TYPO3 v10.0.
'filelist' => 'dir',
// @deprecated - will be removed in TYPO3 v10.0.
'filelist.' => 'array',
'preUserFunc' => 'functionName',
'stdWrapOverride' => 'hook',
// this is a placeholder for the second Hook
'override' => 'string',
'override.' => 'array',
'preIfEmptyListNum' => 'listNum',
'preIfEmptyListNum.' => 'array',
'ifNull' => 'string',
'ifNull.' => 'array',
'ifEmpty' => 'string',
'ifEmpty.' => 'array',
'ifBlank' => 'string',
'ifBlank.' => 'array',
'listNum' => 'listNum',
'listNum.' => 'array',
'trim' => 'boolean',
'trim.' => 'array',
'strPad.' => 'array',
'stdWrap' => 'stdWrap',
'stdWrap.' => 'array',
'stdWrapProcess' => 'hook',
// this is a placeholder for the third Hook
'required' => 'boolean',
'required.' => 'array',
'if.' => 'array',
'fieldRequired' => 'fieldName',
'fieldRequired.' => 'array',
'csConv' => 'string',
'csConv.' => 'array',
'parseFunc' => 'objectpath',
'parseFunc.' => 'array',
'HTMLparser' => 'boolean',
'HTMLparser.' => 'array',
'split.' => 'array',
'replacement.' => 'array',
'prioriCalc' => 'boolean',
'prioriCalc.' => 'array',
'char' => 'integer',
'char.' => 'array',
'intval' => 'boolean',
'intval.' => 'array',
'hash' => 'string',
'hash.' => 'array',
'round' => 'boolean',
'round.' => 'array',
'numberFormat.' => 'array',
'expandList' => 'boolean',
'expandList.' => 'array',
'date' => 'dateconf',
'date.' => 'array',
'strtotime' => 'strtotimeconf',
'strtotime.' => 'array',
'strftime' => 'strftimeconf',
'strftime.' => 'array',
'age' => 'boolean',
'age.' => 'array',
'case' => 'case',
'case.' => 'array',
'bytes' => 'boolean',
'bytes.' => 'array',
'substring' => 'parameters',
'substring.' => 'array',
'cropHTML' => 'crop',
'cropHTML.' => 'array',
'stripHtml' => 'boolean',
'stripHtml.' => 'array',
'crop' => 'crop',
'crop.' => 'array',
'rawUrlEncode' => 'boolean',
'rawUrlEncode.' => 'array',
'htmlSpecialChars' => 'boolean',
'htmlSpecialChars.' => 'array',
'encodeForJavaScriptValue' => 'boolean',
'encodeForJavaScriptValue.' => 'array',
'doubleBrTag' => 'string',
'doubleBrTag.' => 'array',
'br' => 'boolean',
'br.' => 'array',
'brTag' => 'string',
'brTag.' => 'array',
'encapsLines.' => 'array',
'keywords' => 'boolean',
'keywords.' => 'array',
'innerWrap' => 'wrap',
'innerWrap.' => 'array',
'innerWrap2' => 'wrap',
'innerWrap2.' => 'array',
// @deprecated - will be removed in TYPO3 v10.0.
'addParams.' => 'array',
// @deprecated - will be removed in TYPO3 v10.0.
'filelink.' => 'array',
'preCObject' => 'cObject',
'preCObject.' => 'array',
'postCObject' => 'cObject',
'postCObject.' => 'array',
'wrapAlign' => 'align',
'wrapAlign.' => 'array',
'typolink.' => 'array',
'wrap' => 'wrap',
'wrap.' => 'array',
'noTrimWrap' => 'wrap',
'noTrimWrap.' => 'array',
'wrap2' => 'wrap',
'wrap2.' => 'array',
'dataWrap' => 'dataWrap',
'dataWrap.' => 'array',
'prepend' => 'cObject',
'prepend.' => 'array',
'append' => 'cObject',
'append.' => 'array',
'wrap3' => 'wrap',
'wrap3.' => 'array',
'orderedStdWrap' => 'stdWrap',
'orderedStdWrap.' => 'array',
'outerWrap' => 'wrap',
'outerWrap.' => 'array',
'insertData' => 'boolean',
'insertData.' => 'array',
'postUserFunc' => 'functionName',
'postUserFuncInt' => 'functionName',
'prefixComment' => 'string',
'prefixComment.' => 'array',
'editIcons' => 'string',
'editIcons.' => 'array',
'editPanel' => 'boolean',
'editPanel.' => 'array',
'htmlSanitize' => 'boolean',
'htmlSanitize.' => 'array',
'cacheStore' => 'hook',
// this is a placeholder for storing the content in cache
'stdWrapPostProcess' => 'hook',
// this is a placeholder for the last Hook
'debug' => 'boolean',
'debug.' => 'array',
'debugFunc' => 'boolean',
'debugFunc.' => 'array',
'debugData' => 'boolean',
'debugData.' => 'array'
];
/**
* Class names for accordant content object names
*
* @var array
*/
protected $contentObjectClassMap = [];
/**
* Loaded with the current data-record.
*
* If the instance of this class is used to render records from the database those records are found in this array.
* The function stdWrap has TypoScript properties that fetch field-data from this array.
*
* @var array
* @see start()
*/
public $data = [];
/**
* @var string
*/
protected $table = '';
/**
* Used for backup
*
* @var array
*/
public $oldData = [];
/**
* If this is set with an array before stdWrap, it's used instead of $this->data in the data-property in stdWrap
*
* @var string
*/
public $alternativeData = '';
/**
* Used by the parseFunc function and is loaded with tag-parameters when parsing tags.
*
* @var array
*/
public $parameters = [];
/**
* @var string
*/
public $currentValKey = 'currentValue_kidjls9dksoje';
/**
* This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation.
* Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered.
*
* @var string
*/
public $currentRecord = '';
/**
* Set in RecordsContentObject and ContentContentObject to the current number of records selected in a query.
*
* @var int
*/
public $currentRecordTotal = 0;
/**
* Incremented in RecordsContentObject and ContentContentObject before each record rendering.
*
* @var int
*/
public $currentRecordNumber = 0;
/**
* Incremented in RecordsContentObject and ContentContentObject before each record rendering.
*
* @var int
*/
public $parentRecordNumber = 0;
/**
* If the ContentObjectRender was started from ContentContentObject, RecordsContentObject or SearchResultContentObject this array has two keys, 'data' and 'currentRecord' which indicates the record and data for the parent cObj.
*
* @var array
*/
public $parentRecord = [];
/**
* This is used by checkPid, that checks if pages are accessible. The $checkPid_cache['page_uid'] is set TRUE or FALSE upon this check featuring a caching function for the next request.
*
* @var array
*/
public $checkPid_cache = [];
/**
* @var string
*/
public $checkPid_badDoktypeList = '255';
/**
* This will be set by typoLink() to the url of the most recent link created.
*
* @var string
*/
public $lastTypoLinkUrl = '';
/**
* DO. link target.
*
* @var string
*/
public $lastTypoLinkTarget = '';
/**
* @var array
*/
public $lastTypoLinkLD = [];
/**
* array that registers rendered content elements (or any table) to make sure they are not rendered recursively!
*
* @var array
*/
public $recordRegister = [];
/**
* Additionally registered content object types and class names
*
* @var array
*/
protected $cObjHookObjectsRegistry = [];
/**
* @var array
*/
public $cObjHookObjectsArr = [];
/**
* Containing hook objects for stdWrap
*
* @var array
*/
protected $stdWrapHookObjects = [];
/**
* Containing hook objects for getImgResource
*
* @var array
*/
protected $getImgResourceHookObjects;
/**
* @var File Current file objects (during iterations over files)
*/
protected $currentFile;
/**
* Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT
*/
public $doConvertToUserIntObject = false;
/**
* Indicates current object type. Can hold one of OBJECTTYPE_ constants or FALSE.
* The value is set and reset inside USER() function. Any time outside of
* USER() it is FALSE.
*/
protected $userObjectType = false;
/**
* @var array
*/
protected $stopRendering = [];
/**
* @var int
*/
protected $stdWrapRecursionLevel = 0;
/**
* @var TypoScriptFrontendController
*/
protected $typoScriptFrontendController;
/**
* Indicates that object type is USER.
*
* @see ContentObjectRender::$userObjectType
*/
const OBJECTTYPE_USER_INT = 1;
/**
* Indicates that object type is USER.
*
* @see ContentObjectRender::$userObjectType
*/
const OBJECTTYPE_USER = 2;
/**
* @param TypoScriptFrontendController $typoScriptFrontendController
*/
public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null)
{
$this->typoScriptFrontendController = $typoScriptFrontendController;
$this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
}
/**
* Prevent several objects from being serialized.
* If currentFile is set, it is either a File or a FileReference object. As the object itself can't be serialized,
* we have store a hash and restore the object in __wakeup()
*
* @return array
*/
public function __sleep()
{
$vars = get_object_vars($this);
unset($vars['typoScriptFrontendController'], $vars['logger']);
if ($this->currentFile instanceof FileReference) {
$this->currentFile = 'FileReference:' . $this->currentFile->getUid();
} elseif ($this->currentFile instanceof File) {
$this->currentFile = 'File:' . $this->currentFile->getIdentifier();
} else {
unset($vars['currentFile']);
}
return array_keys($vars);
}
/**
* Restore currentFile from hash.
* If currentFile references a File, the identifier equals file identifier.
* If it references a FileReference the identifier equals the uid of the reference.
*/
public function __wakeup()
{
if (isset($GLOBALS['TSFE'])) {
$this->typoScriptFrontendController = $GLOBALS['TSFE'];
}
if ($this->currentFile !== null && is_string($this->currentFile)) {
list($objectType, $identifier) = explode(':', $this->currentFile, 2);
try {
if ($objectType === 'File') {
$this->currentFile = ResourceFactory::getInstance()->retrieveFileOrFolderObject($identifier);
} elseif ($objectType === 'FileReference') {
$this->currentFile = ResourceFactory::getInstance()->getFileReferenceObject($identifier);
}
} catch (ResourceDoesNotExistException $e) {
$this->currentFile = null;
}
}
$this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
}
/**
* Allow injecting content object class map.
*
* This method is private API, please use configuration
* $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
*
* @internal
* @param array $contentObjectClassMap
*/
public function setContentObjectClassMap(array $contentObjectClassMap)
{
$this->contentObjectClassMap = $contentObjectClassMap;
}
/**
* Register a single content object name to class name
*
* This method is private API, please use configuration
* $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
*
* @param string $className
* @param string $contentObjectName
* @internal
*/
public function registerContentObjectClass($className, $contentObjectName)
{
$this->contentObjectClassMap[$contentObjectName] = $className;
}
/**
* Class constructor.
* Well, it has to be called manually since it is not a real constructor function.
* So after making an instance of the class, call this function and pass to it a database record and the tablename from where the record is from. That will then become the "current" record loaded into memory and accessed by the .fields property found in eg. stdWrap.
*
* @param array $data The record data that is rendered.
* @param string $table The table that the data record is from.
*/
public function start($data, $table = '')
{
$this->data = $data;
$this->table = $table;
$this->currentRecord = $table !== ''
? $table . ':' . ($this->data['uid'] ?? '')
: '';
$this->parameters = [];
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'] ?? [] as $classArr) {
$this->cObjHookObjectsRegistry[$classArr[0]] = $classArr[1];
}
$this->stdWrapHookObjects = [];
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] ?? [] as $className) {
$hookObject = GeneralUtility::makeInstance($className);
if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
}
$this->stdWrapHookObjects[] = $hookObject;
}
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] ?? [] as $className) {
$postInitializationProcessor = GeneralUtility::makeInstance($className);
if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
}
$postInitializationProcessor->postProcessContentObjectInitialization($this);
}
}
/**
* Returns the current table
*
* @return string
*/
public function getCurrentTable()
{
return $this->table;
}
/**
* Gets the 'getImgResource' hook objects.
* The first call initializes the accordant objects.
*
* @return array The 'getImgResource' hook objects (if any)
*/
protected function getGetImgResourceHookObjects()
{
if (!isset($this->getImgResourceHookObjects)) {
$this->getImgResourceHookObjects = [];
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] ?? [] as $className) {
$hookObject = GeneralUtility::makeInstance($className);
if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
}
$this->getImgResourceHookObjects[] = $hookObject;
}
}
return $this->getImgResourceHookObjects;
}
/**
* Sets the internal variable parentRecord with information about current record.
* If the ContentObjectRender was started from CONTENT, RECORD or SEARCHRESULT cObject's this array has two keys, 'data' and 'currentRecord' which indicates the record and data for the parent cObj.
*
* @param array $data The record array
* @param string $currentRecord This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation. Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered.
* @internal
*/
public function setParent($data, $currentRecord)
{
$this->parentRecord = [
'data' => $data,
'currentRecord' => $currentRecord
];
}
/***********************************************
*
* CONTENT_OBJ:
*
***********************************************/
/**
* Returns the "current" value.
* The "current" value is just an internal variable that can be used by functions to pass a single value on to another function later in the TypoScript processing.
* It's like "load accumulator" in the good old C64 days... basically a "register" you can use as you like.
* The TSref will tell if functions are setting this value before calling some other object so that you know if it holds any special information.
*
* @return mixed The "current" value
*/
public function getCurrentVal()
{
return $this->data[$this->currentValKey];
}
/**
* Sets the "current" value.
*
* @param mixed $value The variable that you want to set as "current
* @see getCurrentVal()
*/
public function setCurrentVal($value)
{
$this->data[$this->currentValKey] = $value;
}
/**
* Rendering of a "numerical array" of cObjects from TypoScript
* Will call ->cObjGetSingle() for each cObject found and accumulate the output.
*
* @param array $setup array with cObjects as values.
* @param string $addKey A prefix for the debugging information
* @return string Rendered output from the cObjects in the array.
* @see cObjGetSingle()
*/
public function cObjGet($setup, $addKey = '')
{
if (!is_array($setup)) {
return '';
}
$sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
$content = '';
foreach ($sKeyArray as $theKey) {
$theValue = $setup[$theKey];
if ((int)$theKey && strpos($theKey, '.') === false) {
$conf = $setup[$theKey . '.'];
$content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
}
}
return $content;
}
/**
* Renders a content object
*
* @param string $name The content object name, eg. "TEXT" or "USER" or "IMAGE
* @param array $conf The array with TypoScript properties for the content object
* @param string $TSkey A string label used for the internal debugging tracking.
* @return string cObject output
* @throws \UnexpectedValueException
*/
public function cObjGetSingle($name, $conf, $TSkey = '__')
{
$content = '';
// Checking that the function is not called eternally. This is done by interrupting at a depth of 100
$this->getTypoScriptFrontendController()->cObjectDepthCounter--;
if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
$timeTracker = $this->getTimeTracker();
$name = trim($name);
if ($timeTracker->LR) {
$timeTracker->push($TSkey, $name);
}
// Checking if the COBJ is a reference to another object. (eg. name of 'some.object =< styles.something')
if (isset($name[0]) && $name[0] === '<') {
$key = trim(substr($name, 1));
$cF = GeneralUtility::makeInstance(TypoScriptParser::class);
// $name and $conf is loaded with the referenced values.
$confOverride = is_array($conf) ? $conf : [];
list($name, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
$conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
// Getting the cObject
$timeTracker->incStackPointer();
$content .= $this->cObjGetSingle($name, $conf, $key);
$timeTracker->decStackPointer();
} else {
$hooked = false;
// Application defined cObjects
if (!empty($this->cObjHookObjectsRegistry[$name])) {
if (empty($this->cObjHookObjectsArr[$name])) {
$this->cObjHookObjectsArr[$name] = GeneralUtility::makeInstance($this->cObjHookObjectsRegistry[$name]);
}
$hookObj = $this->cObjHookObjectsArr[$name];
if (method_exists($hookObj, 'cObjGetSingleExt')) {
$content .= $hookObj->cObjGetSingleExt($name, $conf, $TSkey, $this);
$hooked = true;
}
}
if (!$hooked) {
$contentObject = $this->getContentObject($name);
if ($contentObject) {
$content .= $this->render($contentObject, $conf);
} else {
// Call hook functions for extra processing
if ($name) {
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'] ?? [] as $className) {
$hookObject = GeneralUtility::makeInstance($className);
if (!$hookObject instanceof ContentObjectGetSingleHookInterface) {
throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetSingleHookInterface::class, 1195043731);
}
/** @var ContentObjectGetSingleHookInterface $hookObject */
$content .= $hookObject->getSingleContentObject($name, (array)$conf, $TSkey, $this);
}
} else {
// Log error in AdminPanel
$warning = sprintf('Content Object "%s" does not exist', $name);
$timeTracker->setTSlogMessage($warning, 2);
}
}
}
}
if ($timeTracker->LR) {
$timeTracker->pull($content);
}
}
// Increasing on exit...
$this->getTypoScriptFrontendController()->cObjectDepthCounter++;
return $content;
}
/**
* Returns a new content object of type $name.
* This content object needs to be registered as content object
* in $this->contentObjectClassMap
*
* @param string $name
* @return AbstractContentObject|null
* @throws ContentRenderingException
*/
public function getContentObject($name)
{
if (!isset($this->contentObjectClassMap[$name])) {
return null;
}
$fullyQualifiedClassName = $this->contentObjectClassMap[$name];
$contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, $this);
if (!($contentObject instanceof AbstractContentObject)) {
throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', $fullyQualifiedClassName), 1422564295);
}
return $contentObject;
}
/********************************************
*
* Functions rendering content objects (cObjects)
*
********************************************/
/**
* Renders a content object by taking exception and cache handling
* into consideration
*
* @param AbstractContentObject $contentObject Content object instance
* @param array $configuration Array of TypoScript properties
*
* @throws ContentRenderingException
* @throws \Exception
* @return string
*/
public function render(AbstractContentObject $contentObject, $configuration = [])
{
$content = '';
// Evaluate possible cache and return
$cacheConfiguration = $configuration['cache.'] ?? null;
if ($cacheConfiguration !== null) {
unset($configuration['cache.']);
$cache = $this->getFromCache($cacheConfiguration);
if ($cache !== false) {
return $cache;
}
}
// Render content
try {
$content .= $contentObject->render($configuration);
} catch (ContentRenderingException $exception) {
// Content rendering Exceptions indicate a critical problem which should not be
// caught e.g. when something went wrong with Exception handling itself
throw $exception;
} catch (\Exception $exception) {
$exceptionHandler = $this->createExceptionHandler($configuration);
if ($exceptionHandler === null) {
throw $exception;
}
$content = $exceptionHandler->handle($exception, $contentObject, $configuration);
}
// Store cache
if ($cacheConfiguration !== null && !$this->getTypoScriptFrontendController()->no_cache) {
$key = $this->calculateCacheKey($cacheConfiguration);
if (!empty($key)) {
/** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
$cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
$tags = $this->calculateCacheTags($cacheConfiguration);
$lifetime = $this->calculateCacheLifetime($cacheConfiguration);
$cacheFrontend->set($key, $content, $tags, $lifetime);
}
}
return $content;
}
/**
* Creates the content object exception handler from local content object configuration
* or, from global configuration if not explicitly disabled in local configuration
*
* @param array $configuration
* @return ExceptionHandlerInterface|null
* @throws ContentRenderingException
*/
protected function createExceptionHandler($configuration = [])
{
$exceptionHandler = null;
$exceptionHandlerClassName = $this->determineExceptionHandlerClassName($configuration);
if (!empty($exceptionHandlerClassName)) {
$exceptionHandler = GeneralUtility::makeInstance($exceptionHandlerClassName, $this->mergeExceptionHandlerConfiguration($configuration));
if (!$exceptionHandler instanceof ExceptionHandlerInterface) {
throw new ContentRenderingException('An exception handler was configured but the class does not exist or does not implement the ExceptionHandlerInterface', 1403653369);
}
}
return $exceptionHandler;
}
/**
* Determine exception handler class name from global and content object configuration
*
* @param array $configuration
* @return string|null
*/
protected function determineExceptionHandlerClassName($configuration)
{
$exceptionHandlerClassName = null;
$tsfe = $this->getTypoScriptFrontendController();
if (!isset($tsfe->config['config']['contentObjectExceptionHandler'])) {
if (GeneralUtility::getApplicationContext()->isProduction()) {
$exceptionHandlerClassName = '1';
}
} else {
$exceptionHandlerClassName = $tsfe->config['config']['contentObjectExceptionHandler'];
}
if (isset($configuration['exceptionHandler'])) {
$exceptionHandlerClassName = $configuration['exceptionHandler'];
}
if ($exceptionHandlerClassName === '1') {
$exceptionHandlerClassName = ProductionExceptionHandler::class;
}
return $exceptionHandlerClassName;
}
/**
* Merges global exception handler configuration with the one from the content object
* and returns the merged exception handler configuration
*
* @param array $configuration
* @return array
*/
protected function mergeExceptionHandlerConfiguration($configuration)
{
$exceptionHandlerConfiguration = [];
$tsfe = $this->getTypoScriptFrontendController();
if (!empty($tsfe->config['config']['contentObjectExceptionHandler.'])) {
$exceptionHandlerConfiguration = $tsfe->config['config']['contentObjectExceptionHandler.'];
}
if (!empty($configuration['exceptionHandler.'])) {
$exceptionHandlerConfiguration = array_replace_recursive($exceptionHandlerConfiguration, $configuration['exceptionHandler.']);
}
return $exceptionHandlerConfiguration;
}
/**
* Retrieves a type of object called as USER or USER_INT. Object can detect their
* type by using this call. It returns OBJECTTYPE_USER_INT or OBJECTTYPE_USER depending on the
* current object execution. In all other cases it will return FALSE to indicate
* a call out of context.
*
* @return mixed One of OBJECTTYPE_ class constants or FALSE
*/
public function getUserObjectType()
{
return $this->userObjectType;
}
/**
* Sets the user object type
*
* @param mixed $userObjectType
*/
public function setUserObjectType($userObjectType)
{
$this->userObjectType = $userObjectType;
}
/**
* Requests the current USER object to be converted to USER_INT.
*/
public function convertToUserIntObject()
{
if ($this->userObjectType !== self::OBJECTTYPE_USER) {
$this->getTimeTracker()->setTSlogMessage(self::class . '::convertToUserIntObject() is called in the wrong context or for the wrong object type', 2);
} else {
$this->doConvertToUserIntObject = true;
}
}
/************************************
*
* Various helper functions for content objects:
*
************************************/
/**
* Converts a given config in Flexform to a conf-array
*
* @param string|array $flexData Flexform data
* @param array $conf Array to write the data into, by reference
* @param bool $recursive Is set if called recursive. Don't call function with this parameter, it's used inside the function only
*/
public function readFlexformIntoConf($flexData, &$conf, $recursive = false)
{
if ($recursive === false && is_string($flexData)) {
$flexData = GeneralUtility::xml2array($flexData, 'T3');
}
if (is_array($flexData) && isset($flexData['data']['sDEF']['lDEF'])) {
$flexData = $flexData['data']['sDEF']['lDEF'];
}
if (!is_array($flexData)) {
return;
}
foreach ($flexData as $key => $value) {
if (!is_array($value)) {
continue;
}
if (isset($value['el'])) {
if (is_array($value['el']) && !empty($value['el'])) {
foreach ($value['el'] as $ekey => $element) {
if (isset($element['vDEF'])) {
$conf[$ekey] = $element['vDEF'];
} else {
if (is_array($element)) {
$this->readFlexformIntoConf($element, $conf[$key][key($element)][$ekey], true);
} else {
$this->readFlexformIntoConf($element, $conf[$key][$ekey], true);
}
}
}
} else {
$this->readFlexformIntoConf($value['el'], $conf[$key], true);
}
}
if (isset($value['vDEF'])) {
$conf[$key] = $value['vDEF'];
}
}
}
/**
* Returns all parents of the given PID (Page UID) list
*
* @param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap
* @param array $pidConf stdWrap array for the list
* @return string A list of PIDs
* @internal
*/
public function getSlidePids($pidList, $pidConf)
{
$pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
if ($pidList === '') {
$pidList = 'this';
}
$tsfe = $this->getTypoScriptFrontendController();
$listArr = null;
if (trim($pidList)) {
$listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $pidList));
$listArr = $this->checkPidArray($listArr);
}
$pidList = [];
if (is_array($listArr) && !empty($listArr)) {
foreach ($listArr as $uid) {
$page = $tsfe->sys_page->getPage($uid);
if (!$page['is_siteroot']) {
$pidList[] = $page['pid'];
}
}
}
return implode(',', $pidList);
}
/**
* Returns a tag with the image file defined by $file and processed according to the properties in the TypoScript array.
* Mostly this function is a sub-function to the IMAGE function which renders the IMAGE cObject in TypoScript.
* This function is called by "$this->cImage($conf['file'], $conf);" from IMAGE().
*
* @param string $file File TypoScript resource
* @param array $conf TypoScript configuration properties
* @return string
tag, (possibly wrapped in links and other HTML) if any image found.
* @internal
* @see IMAGE()
*/
public function cImage($file, $conf)
{
$tsfe = $this->getTypoScriptFrontendController();
$info = $this->getImgResource($file, $conf['file.']);
$tsfe->lastImageInfo = $info;
if (!is_array($info)) {
return '';
}
if (is_file(Environment::getPublicPath() . '/' . $info['3'])) {
$source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
} else {
$source = $info[3];
}
$layoutKey = $this->stdWrap($conf['layoutKey'], $conf['layoutKey.']);
$imageTagTemplate = $this->getImageTagTemplate($layoutKey, $conf);
$sourceCollection = $this->getImageSourceCollection($layoutKey, $conf, $file);
// This array is used to collect the image-refs on the page...
$tsfe->imagesOnPage[] = $source;
$altParam = $this->getAltParam($conf);
$params = $this->stdWrapValue('params', $conf);
if ($params !== '' && $params[0] !== ' ') {
$params = ' ' . $params;
}
$imageTagValues = [
'width' => (int)$info[0],
'height' => (int)$info[1],
'src' => htmlspecialchars($source),
'params' => $params,
'altParams' => $altParam,
'border' => $this->getBorderAttr(' border="' . (int)$conf['border'] . '"'),
'sourceCollection' => $sourceCollection,
'selfClosingTagSlash' => !empty($tsfe->xhtmlDoctype) ? ' /' : '',
];
$markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
$theValue = $markerTemplateEngine->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true);
$linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
if ($linkWrap) {
$theValue = $this->linkWrap($theValue, $linkWrap);
} elseif ($conf['imageLinkWrap']) {
$originalFile = !empty($info['originalFile']) ? $info['originalFile'] : $info['origFile'];
$theValue = $this->imageLinkWrap($theValue, $originalFile, $conf['imageLinkWrap.']);
}
$wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
if ((string)$wrap !== '') {
$theValue = $this->wrap($theValue, $conf['wrap']);
}
return $theValue;
}
/**
* Returns the 'border' attribute for an
tag only if the doctype is not xhtml_strict, xhtml_11 or html5
* or if the config parameter 'disableImgBorderAttr' is not set.
*
* @param string $borderAttr The border attribute
* @return string The border attribute
*/
public function getBorderAttr($borderAttr)
{
$tsfe = $this->getTypoScriptFrontendController();
$docType = $tsfe->xhtmlDoctype;
if (
$docType !== 'xhtml_strict' && $docType !== 'xhtml_11'
&& $tsfe->config['config']['doctype'] !== 'html5'
&& !$tsfe->config['config']['disableImgBorderAttr']
) {
return $borderAttr;
}
return '';
}
/**
* Returns the html-template for rendering the image-Tag if no template is defined via typoscript the
* default
tag template is returned
*
* @param string $layoutKey rendering key
* @param array $conf TypoScript configuration properties
* @return string
*/
public function getImageTagTemplate($layoutKey, $conf)
{
if ($layoutKey && isset($conf['layout.']) && isset($conf['layout.'][$layoutKey . '.'])) {
$imageTagLayout = $this->stdWrap(
$conf['layout.'][$layoutKey . '.']['element'] ?? '',
$conf['layout.'][$layoutKey . '.']['element.'] ?? []
);
} else {
$imageTagLayout = '
';
}
return $imageTagLayout;
}
/**
* Render alternate sources for the image tag. If no source collection is given an empty string is returned.
*
* @param string $layoutKey rendering key
* @param array $conf TypoScript configuration properties
* @param string $file
* @throws \UnexpectedValueException
* @return string
*/
public function getImageSourceCollection($layoutKey, $conf, $file)
{
$sourceCollection = '';
if ($layoutKey
&& isset($conf['sourceCollection.']) && $conf['sourceCollection.']
&& (
isset($conf['layout.'][$layoutKey . '.']['source']) && $conf['layout.'][$layoutKey . '.']['source']
|| isset($conf['layout.'][$layoutKey . '.']['source.']) && $conf['layout.'][$layoutKey . '.']['source.']
)
) {
// find active sourceCollection
$activeSourceCollections = [];
foreach ($conf['sourceCollection.'] as $sourceCollectionKey => $sourceCollectionConfiguration) {
if (substr($sourceCollectionKey, -1) === '.') {
if (empty($sourceCollectionConfiguration['if.']) || $this->checkIf($sourceCollectionConfiguration['if.'])) {
$activeSourceCollections[] = $sourceCollectionConfiguration;
}
}
}
// apply option split to configurations
$tsfe = $this->getTypoScriptFrontendController();
$typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
$srcLayoutOptionSplitted = $typoScriptService->explodeConfigurationForOptionSplit((array)$conf['layout.'][$layoutKey . '.'], count($activeSourceCollections));
// render sources
foreach ($activeSourceCollections as $key => $sourceConfiguration) {
$sourceLayout = $this->stdWrap(
$srcLayoutOptionSplitted[$key]['source'] ?? '',
$srcLayoutOptionSplitted[$key]['source.'] ?? []
);
$sourceRenderConfiguration = [
'file' => $file,
'file.' => $conf['file.'] ?? null
];
if (isset($sourceConfiguration['quality']) || isset($sourceConfiguration['quality.'])) {
$imageQuality = $sourceConfiguration['quality'] ?? '';
if (isset($sourceConfiguration['quality.'])) {
$imageQuality = $this->stdWrap($sourceConfiguration['quality'], $sourceConfiguration['quality.']);
}
if ($imageQuality) {
$sourceRenderConfiguration['file.']['params'] = '-quality ' . (int)$imageQuality;
}
}
if (isset($sourceConfiguration['pixelDensity'])) {
$pixelDensity = (int)$this->stdWrap(
$sourceConfiguration['pixelDensity'] ?? '',
$sourceConfiguration['pixelDensity.'] ?? []
);
} else {
$pixelDensity = 1;
}
$dimensionKeys = ['width', 'height', 'maxW', 'minW', 'maxH', 'minH', 'maxWidth', 'maxHeight', 'XY'];
foreach ($dimensionKeys as $dimensionKey) {
$dimension = $this->stdWrap(
$sourceConfiguration[$dimensionKey] ?? '',
$sourceConfiguration[$dimensionKey . '.'] ?? []
);
if (!$dimension) {
$dimension = $this->stdWrap(
$conf['file.'][$dimensionKey] ?? '',
$conf['file.'][$dimensionKey . '.'] ?? []
);
}
if ($dimension) {
if (strstr($dimension, 'c') !== false && ($dimensionKey === 'width' || $dimensionKey === 'height')) {
$dimensionParts = explode('c', $dimension, 2);
$dimension = ((int)$dimensionParts[0] * $pixelDensity) . 'c';
if ($dimensionParts[1]) {
$dimension .= $dimensionParts[1];
}
} elseif ($dimensionKey === 'XY') {
$dimensionParts = GeneralUtility::intExplode(',', $dimension, false, 2);
$dimension = $dimensionParts[0] * $pixelDensity;
if ($dimensionParts[1]) {
$dimension .= ',' . $dimensionParts[1] * $pixelDensity;
}
} else {
$dimension = (int)$dimension * $pixelDensity;
}
$sourceRenderConfiguration['file.'][$dimensionKey] = $dimension;
// Remove the stdWrap properties for dimension as they have been processed already above.
unset($sourceRenderConfiguration['file.'][$dimensionKey . '.']);
}
}
$sourceInfo = $this->getImgResource($sourceRenderConfiguration['file'], $sourceRenderConfiguration['file.']);
if ($sourceInfo) {
$sourceConfiguration['width'] = $sourceInfo[0];
$sourceConfiguration['height'] = $sourceInfo[1];
$urlPrefix = '';
if (parse_url($sourceInfo[3], PHP_URL_HOST) === null) {
$urlPrefix = $tsfe->absRefPrefix;
}
$sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]);
$sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : '';
$markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
$oneSourceCollection = $markerTemplateEngine->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true);
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] ?? [] as $className) {
$hookObject = GeneralUtility::makeInstance($className);
if (!$hookObject instanceof ContentObjectOneSourceCollectionHookInterface) {
throw new \UnexpectedValueException(
'$hookObject must implement interface ' . ContentObjectOneSourceCollectionHookInterface::class,
1380007853
);
}
$oneSourceCollection = $hookObject->getOneSourceCollection((array)$sourceRenderConfiguration, (array)$sourceConfiguration, $oneSourceCollection, $this);
}
$sourceCollection .= $oneSourceCollection;
}
}
}
return $sourceCollection;
}
/**
* Wraps the input string in link-tags that opens the image in a new window.
*
* @param string $string String to wrap, probably an
tag
* @param string|File|FileReference $imageFile The original image file
* @param array $conf TypoScript properties for the "imageLinkWrap" function
* @return string The input string, $string, wrapped as configured.
* @see cImage()
*/
public function imageLinkWrap($string, $imageFile, $conf)
{
$string = (string)$string;
$enable = isset($conf['enable.']) ? $this->stdWrap($conf['enable'], $conf['enable.']) : $conf['enable'];
if (!$enable) {
return $string;
}
$content = (string)$this->typoLink($string, $conf['typolink.']);
if (isset($conf['file.'])) {
$imageFile = $this->stdWrap($imageFile, $conf['file.']);
}
if ($imageFile instanceof File) {
$file = $imageFile;
} elseif ($imageFile instanceof FileReference) {
$file = $imageFile->getOriginalFile();
} else {
if (MathUtility::canBeInterpretedAsInteger($imageFile)) {
$file = ResourceFactory::getInstance()->getFileObject((int)$imageFile);
} else {
$file = ResourceFactory::getInstance()->getFileObjectFromCombinedIdentifier($imageFile);
}
}
// Create imageFileLink if not created with typolink
if ($content === $string && $file !== null) {
$parameterNames = ['width', 'height', 'effects', 'bodyTag', 'title', 'wrap', 'crop'];
$parameters = [];
$sample = isset($conf['sample.']) ? $this->stdWrap($conf['sample'], $conf['sample.']) : $conf['sample'];
if ($sample) {
$parameters['sample'] = 1;
}
foreach ($parameterNames as $parameterName) {
if (isset($conf[$parameterName . '.'])) {
$conf[$parameterName] = $this->stdWrap($conf[$parameterName], $conf[$parameterName . '.']);
}
if (isset($conf[$parameterName]) && $conf[$parameterName]) {
$parameters[$parameterName] = $conf[$parameterName];
}
}
$parametersEncoded = base64_encode(json_encode($parameters));
$hmac = GeneralUtility::hmac(implode('|', [$file->getUid(), $parametersEncoded]));
$params = '&md5=' . $hmac;
foreach (str_split($parametersEncoded, 64) as $index => $chunk) {
$params .= '¶meters' . rawurlencode('[') . $index . rawurlencode(']') . '=' . rawurlencode($chunk);
}
$url = $this->getTypoScriptFrontendController()->absRefPrefix . 'index.php?eID=tx_cms_showpic&file=' . $file->getUid() . $params;
$directImageLink = isset($conf['directImageLink.']) ? $this->stdWrap($conf['directImageLink'], $conf['directImageLink.']) : $conf['directImageLink'];
if ($directImageLink) {
$imgResourceConf = [
'file' => $imageFile,
'file.' => $conf
];
$url = $this->cObjGetSingle('IMG_RESOURCE', $imgResourceConf);
if (!$url) {
// If no imagemagick / gm is available
$url = $imageFile;
}
}
// Create TARGET-attribute only if the right doctype is used
$target = '';
$xhtmlDocType = $this->getTypoScriptFrontendController()->xhtmlDoctype;
if ($xhtmlDocType !== 'xhtml_strict' && $xhtmlDocType !== 'xhtml_11') {
$target = isset($conf['target.'])
? (string)$this->stdWrap($conf['target'], $conf['target.'])
: (string)$conf['target'];
if ($target === '') {
$target = 'thePicture';
}
}
$a1 = '';
$a2 = '';
$conf['JSwindow'] = isset($conf['JSwindow.']) ? $this->stdWrap($conf['JSwindow'], $conf['JSwindow.']) : $conf['JSwindow'];
if ($conf['JSwindow']) {
if ($conf['JSwindow.']['altUrl'] || $conf['JSwindow.']['altUrl.']) {
$altUrl = isset($conf['JSwindow.']['altUrl.']) ? $this->stdWrap($conf['JSwindow.']['altUrl'], $conf['JSwindow.']['altUrl.']) : $conf['JSwindow.']['altUrl'];
if ($altUrl) {
$url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode($imageFile) . $params);
}
}
$processedFile = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $conf);
$JSwindowExpand = isset($conf['JSwindow.']['expand.']) ? $this->stdWrap($conf['JSwindow.']['expand'], $conf['JSwindow.']['expand.']) : $conf['JSwindow.']['expand'];
$offset = GeneralUtility::intExplode(',', $JSwindowExpand . ',');
$newWindow = isset($conf['JSwindow.']['newWindow.']) ? $this->stdWrap($conf['JSwindow.']['newWindow'], $conf['JSwindow.']['newWindow.']) : $conf['JSwindow.']['newWindow'];
$onClick = 'openPic('
. GeneralUtility::quoteJSvalue($this->getTypoScriptFrontendController()->baseUrlWrap($url)) . ','
. '\'' . ($newWindow ? md5($url) : 'thePicture') . '\','
. GeneralUtility::quoteJSvalue('width=' . ($processedFile->getProperty('width') + $offset[0])
. ',height=' . ($processedFile->getProperty('height') + $offset[1]) . ',status=0,menubar=0')
. '); return false;';
$a1 = 'getTypoScriptFrontendController()->ATagParams . '>';
$a2 = '';
$this->getTypoScriptFrontendController()->setJS('openPic');
} else {
$conf['linkParams.']['directImageLink'] = (bool)$conf['directImageLink'];
$conf['linkParams.']['parameter'] = $url;
$string = $this->typoLink($string, $conf['linkParams.']);
}
if (isset($conf['stdWrap.'])) {
$string = $this->stdWrap($string, $conf['stdWrap.']);
}
$content = $a1 . $string . $a2;
}
return $content;
}
/**
* Sets the SYS_LASTCHANGED timestamp if input timestamp is larger than current value.
* The SYS_LASTCHANGED timestamp can be used by various caching/indexing applications to determine if the page has new content.
* Therefore you should call this function with the last-changed timestamp of any element you display.
*
* @param int $tstamp Unix timestamp (number of seconds since 1970)
* @see TypoScriptFrontendController::setSysLastChanged()
*/
public function lastChanged($tstamp)
{
$tstamp = (int)$tstamp;
$tsfe = $this->getTypoScriptFrontendController();
if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
$tsfe->register['SYS_LASTCHANGED'] = $tstamp;
}
}
/**
* Wraps the input string by the $wrap value and implements the "linkWrap" data type as well.
* The "linkWrap" data type means that this function will find any integer encapsulated in {} (curly braces) in the first wrap part and substitute it with the corresponding page uid from the rootline where the found integer is pointing to the key in the rootline. See link below.
*
* @param string $content Input string
* @param string $wrap A string where the first two parts separated by "|" (vertical line) will be wrapped around the input string
* @return string Wrapped output string
* @see wrap(), cImage(), FILE()
*/
public function linkWrap($content, $wrap)
{
$wrapArr = explode('|', $wrap);
if (preg_match('/\\{([0-9]*)\\}/', $wrapArr[0], $reg)) {
$uid = $this->getTypoScriptFrontendController()->tmpl->rootLine[$reg[1]]['uid'] ?? null;
if ($uid) {
$wrapArr[0] = str_replace($reg[0], $uid, $wrapArr[0]);
}
}
return trim($wrapArr[0] ?? '') . $content . trim($wrapArr[1] ?? '');
}
/**
* An abstraction method which creates an alt or title parameter for an HTML img, applet, area or input element and the FILE content element.
* From the $conf array it implements the properties "altText", "titleText" and "longdescURL"
*
* @param array $conf TypoScript configuration properties
* @param bool $longDesc If set, the longdesc attribute will be generated - must only be used for img elements!
* @return string Parameter string containing alt and title parameters (if any)
* @see IMGTEXT(), FILE(), FORM(), cImage(), filelink()
*/
public function getAltParam($conf, $longDesc = true)
{
$altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
$titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] !== 'html5') {
$longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
} else {
$longDescUrl = trim($conf['longdescURL']);
}
$longDescUrl = strip_tags($longDescUrl);
// "alt":
$altParam = ' alt="' . htmlspecialchars($altText) . '"';
// "title":
$emptyTitleHandling = isset($conf['emptyTitleHandling.']) ? $this->stdWrap($conf['emptyTitleHandling'], $conf['emptyTitleHandling.']) : $conf['emptyTitleHandling'];
// Choices: 'keepEmpty' | 'useAlt' | 'removeAttr'
if ($titleText || $emptyTitleHandling === 'keepEmpty') {
$altParam .= ' title="' . htmlspecialchars($titleText) . '"';
} elseif (!$titleText && $emptyTitleHandling === 'useAlt') {
$altParam .= ' title="' . htmlspecialchars($altText) . '"';
}
// "longDesc" URL
if ($longDesc && !empty($longDescUrl)) {
$altParam .= ' longdesc="' . htmlspecialchars($longDescUrl) . '"';
}
return $altParam;
}
/**
* An abstraction method to add parameters to an A tag.
* Uses the ATagParams property.
*
* @param array $conf TypoScript configuration properties
* @param bool|int $addGlobal If set, will add the global config.ATagParams to the link
* @return string String containing the parameters to the A tag (if non empty, with a leading space)
* @see IMGTEXT(), filelink(), makelinks(), typolink()
*/
public function getATagParams($conf, $addGlobal = 1)
{
$aTagParams = '';
if ($conf['ATagParams.'] ?? false) {
$aTagParams = ' ' . $this->stdWrap($conf['ATagParams'], $conf['ATagParams.']);
} elseif ($conf['ATagParams'] ?? false) {
$aTagParams = ' ' . $conf['ATagParams'];
}
if ($addGlobal) {
$aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
}
// Extend params
$_params = [
'conf' => &$conf,
'aTagParams' => &$aTagParams
];
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] ?? [] as $className) {
$processor = GeneralUtility::makeInstance($className);
$aTagParams = $processor->process($_params, $this);
}
$aTagParams = trim($aTagParams);
if (!empty($aTagParams)) {
$aTagParams = ' ' . $aTagParams;
}
return $aTagParams;
}
/**
* All extension links should ask this function for additional properties to their tags.
* Designed to add for instance an "onclick" property for site tracking systems.
*
* @param string $URL URL of the website
* @param string $TYPE
* @return string The additional tag properties
*/
public function extLinkATagParams($URL, $TYPE)
{
$out = '';
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler'])) {
$extLinkATagParamsHandler = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']);
if (method_exists($extLinkATagParamsHandler, 'main')) {
$out .= trim($extLinkATagParamsHandler->main($URL, $TYPE, $this));
}
}
return trim($out) ? ' ' . trim($out) : '';
}
/***********************************************
*
* HTML template processing functions
*
***********************************************/
/**
* Sets the current file object during iterations over files.
*
* @param File $fileObject The file object.
*/
public function setCurrentFile($fileObject)
{
$this->currentFile = $fileObject;
}
/**
* Gets the current file object during iterations over files.
*
* @return File The current file object.
*/
public function getCurrentFile()
{
return $this->currentFile;
}
/***********************************************
*
* "stdWrap" + sub functions
*
***********************************************/
/**
* The "stdWrap" function. This is the implementation of what is known as "stdWrap properties" in TypoScript.
* Basically "stdWrap" performs some processing of a value based on properties in the input $conf array(holding the TypoScript "stdWrap properties")
* See the link below for a complete list of properties and what they do. The order of the table with properties found in TSref (the link) follows the actual order of implementation in this function.
*
* If $this->alternativeData is an array it's used instead of the $this->data array in ->getData
*
* @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
* @param array $conf TypoScript "stdWrap properties".
* @return string The processed input value
*/
public function stdWrap($content = '', $conf = [])
{
$content = (string)$content;
// If there is any hook object, activate all of the process and override functions.
// The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
if ($this->stdWrapHookObjects) {
$conf['stdWrapPreProcess'] = 1;
$conf['stdWrapOverride'] = 1;
$conf['stdWrapProcess'] = 1;
$conf['stdWrapPostProcess'] = 1;
}
if (!is_array($conf) || !$conf) {
return $content;
}
// Cache handling
if (isset($conf['cache.']) && is_array($conf['cache.'])) {
$conf['cache.']['key'] = $this->stdWrap($conf['cache.']['key'], $conf['cache.']['key.']);
$conf['cache.']['tags'] = $this->stdWrap($conf['cache.']['tags'], $conf['cache.']['tags.']);
$conf['cache.']['lifetime'] = $this->stdWrap($conf['cache.']['lifetime'], $conf['cache.']['lifetime.']);
$conf['cacheRead'] = 1;
$conf['cacheStore'] = 1;
}
// The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
$sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
// Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
$stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
// Additional Array to check whether a function has already been executed
$isExecuted = [];
// Additional switch to make sure 'required', 'if' and 'fieldRequired'
// will still stop rendering immediately in case they return FALSE
$this->stdWrapRecursionLevel++;
$this->stopRendering[$this->stdWrapRecursionLevel] = false;
// execute each function in the predefined order
foreach ($sortedConf as $stdWrapName) {
// eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
if ((!isset($isExecuted[$stdWrapName]) || !$isExecuted[$stdWrapName]) && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
$functionName = rtrim($stdWrapName, '.');
$functionProperties = $functionName . '.';
$functionType = $this->stdWrapOrder[$functionName] ?? null;
// If there is any code on the next level, check if it contains "official" stdWrap functions
// if yes, execute them first - will make each function stdWrap aware
// so additional stdWrap calls within the functions can be removed, since the result will be the same
if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
// Check if there's already content available before processing
// any ifEmpty or ifBlank stdWrap properties
if (($functionName === 'ifBlank' && $content !== '') ||
($functionName === 'ifEmpty' && trim($content) !== '')) {
continue;
}
$conf[$functionName] = $this->stdWrap($conf[$functionName] ?? '', $conf[$functionProperties] ?? []);
}
}
// Check if key is still containing something, since it might have been changed by next level stdWrap before
if ((isset($conf[$functionName]) || $conf[$functionProperties])
&& ($functionType !== 'boolean' || $conf[$functionName])
) {
// Get just that part of $conf that is needed for the particular function
$singleConf = [
$functionName => $conf[$functionName] ?? null,
$functionProperties => $conf[$functionProperties] ?? null
];
// Hand over the whole $conf array to the stdWrapHookObjects
if ($functionType === 'hook') {
$singleConf = $conf;
}
// Add both keys - with and without the dot - to the set of executed functions
$isExecuted[$functionName] = true;
$isExecuted[$functionProperties] = true;
// Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
$functionName = 'stdWrap_' . $functionName;
$content = $this->{$functionName}($content, $singleConf);
} elseif ($functionType === 'boolean' && !$conf[$functionName]) {
$isExecuted[$functionName] = true;
$isExecuted[$functionProperties] = true;
}
}
}
unset($this->stopRendering[$this->stdWrapRecursionLevel]);
$this->stdWrapRecursionLevel--;
return $content;
}
/**
* Gets a configuration value by passing them through stdWrap first and taking a default value if stdWrap doesn't yield a result.
*
* @param string $key The config variable key (from TS array).
* @param array $config The TypoScript array.
* @param string $defaultValue Optional default value.
* @return string Value of the config variable
*/
public function stdWrapValue($key, array $config, $defaultValue = '')
{
if (isset($config[$key])) {
if (!isset($config[$key . '.'])) {
return $config[$key];
}
} elseif (isset($config[$key . '.'])) {
$config[$key] = '';
} else {
return $defaultValue;
}
$stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
return $stdWrapped ?: $defaultValue;
}
/**
* stdWrap pre process hook
* can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
* this hook will execute functions before any other stdWrap function can modify anything
*
* @param string $content Input value undergoing processing in these functions.
* @param array $conf All stdWrap properties, not just the ones for a particular function.
* @return string The processed input value
*/
public function stdWrap_stdWrapPreProcess($content = '', $conf = [])
{
foreach ($this->stdWrapHookObjects as $hookObject) {
/** @var ContentObjectStdWrapHookInterface $hookObject */
$content = $hookObject->stdWrapPreProcess($content, $conf, $this);
}
return $content;
}
/**
* Check if content was cached before (depending on the given cache key)
*
* @param string $content Input value undergoing processing in these functions.
* @param array $conf All stdWrap properties, not just the ones for a particular function.
* @return string The processed input value
*/
public function stdWrap_cacheRead($content = '', $conf = [])
{
if (!isset($conf['cache.'])) {
return $content;
}
$result = $this->getFromCache($conf['cache.']);
return $result === false ? $content : $result;
}
/**
* Add tags to page cache (comma-separated list)
*
* @param string $content Input value undergoing processing in these functions.
* @param array $conf All stdWrap properties, not just the ones for a particular function.
* @return string The processed input value
*/
public function stdWrap_addPageCacheTags($content = '', $conf = [])
{
$tags = isset($conf['addPageCacheTags.'])
? $this->stdWrap($conf['addPageCacheTags'], $conf['addPageCacheTags.'])
: $conf['addPageCacheTags'];
if (!empty($tags)) {
$cacheTags = GeneralUtility::trimExplode(',', $tags, true);
$this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
}
return $content;
}
/**
* setContentToCurrent
* actually it just does the contrary: Sets the value of 'current' based on current content
*
* @param string $content Input value undergoing processing in this function.
* @return string The processed input value
*/
public function stdWrap_setContentToCurrent($content = '')
{
$this->data[$this->currentValKey] = $content;
return $content;
}
/**
* setCurrent
* Sets the value of 'current' based on the outcome of stdWrap operations
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for setCurrent.
* @return string The processed input value
*/
public function stdWrap_setCurrent($content = '', $conf = [])
{
$this->data[$this->currentValKey] = $conf['setCurrent'] ?? null;
return $content;
}
/**
* lang
* Translates content based on the language currently used by the FE
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for lang.
* @return string The processed input value
*/
public function stdWrap_lang($content = '', $conf = [])
{
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
$siteLanguage = $request ? $request->getAttribute('language') : null;
if ($siteLanguage instanceof SiteLanguage) {
$currentLanguageCode = $siteLanguage->getTypo3Language();
} else {
$tsfe = $this->getTypoScriptFrontendController();
$currentLanguageCode = $tsfe->config['config']['language'] ?? null;
}
if ($currentLanguageCode && isset($conf['lang.'][$currentLanguageCode])) {
$content = $conf['lang.'][$currentLanguageCode];
}
return $content;
}
/**
* data
* Gets content from different sources based on getText functions, makes use of alternativeData, when set
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for data.
* @return string The processed input value
*/
public function stdWrap_data($content = '', $conf = [])
{
$content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data);
// This must be unset directly after
$this->alternativeData = '';
return $content;
}
/**
* field
* Gets content from a DB field
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for field.
* @return string The processed input value
*/
public function stdWrap_field($content = '', $conf = [])
{
return $this->getFieldVal($conf['field']);
}
/**
* current
* Gets content that has been perviously set as 'current'
* Can be set via setContentToCurrent or setCurrent or will be set automatically i.e. inside the split function
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for current.
* @return string The processed input value
*/
public function stdWrap_current($content = '', $conf = [])
{
return $this->data[$this->currentValKey];
}
/**
* cObject
* Will replace the content with the value of an official TypoScript cObject
* like TEXT, COA, HMENU
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for cObject.
* @return string The processed input value
*/
public function stdWrap_cObject($content = '', $conf = [])
{
return $this->cObjGetSingle($conf['cObject'] ?? '', $conf['cObject.'] ?? [], '/stdWrap/.cObject');
}
/**
* numRows
* Counts the number of returned records of a DB operation
* makes use of select internally
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for numRows.
* @return string The processed input value
*/
public function stdWrap_numRows($content = '', $conf = [])
{
return $this->numRows($conf['numRows.']);
}
/**
* filelist
* Will create a list of files based on some additional parameters
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for filelist.
* @return string The processed input value
* @deprecated since TYPO3 v9.5, will be removed in TYPO3 v10.0. Use cObject FILES instead.
*/
public function stdWrap_filelist($content = '', $conf = [])
{
return $this->filelist($conf['filelist'], true);
}
/**
* preUserFunc
* Will execute a user public function before the content will be modified by any other stdWrap function
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for preUserFunc.
* @return string The processed input value
*/
public function stdWrap_preUserFunc($content = '', $conf = [])
{
return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'], $content);
}
/**
* stdWrap override hook
* can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
* this hook will execute functions on existing content but still before the content gets modified or replaced
*
* @param string $content Input value undergoing processing in these functions.
* @param array $conf All stdWrap properties, not just the ones for a particular function.
* @return string The processed input value
*/
public function stdWrap_stdWrapOverride($content = '', $conf = [])
{
foreach ($this->stdWrapHookObjects as $hookObject) {
/** @var ContentObjectStdWrapHookInterface $hookObject */
$content = $hookObject->stdWrapOverride($content, $conf, $this);
}
return $content;
}
/**
* override
* Will override the current value of content with its own value'
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for override.
* @return string The processed input value
*/
public function stdWrap_override($content = '', $conf = [])
{
if (trim($conf['override'] ?? false)) {
$content = $conf['override'];
}
return $content;
}
/**
* preIfEmptyListNum
* Gets a value off a CSV list before the following ifEmpty check
* Makes sure that the result of ifEmpty will be TRUE in case the CSV does not contain a value at the position given by preIfEmptyListNum
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for preIfEmptyListNum.
* @return string The processed input value
*/
public function stdWrap_preIfEmptyListNum($content = '', $conf = [])
{
return $this->listNum($content, $conf['preIfEmptyListNum'] ?? null, $conf['preIfEmptyListNum.']['splitChar'] ?? null);
}
/**
* ifNull
* Will set content to a replacement value in case the value of content is NULL
*
* @param string|null $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for ifNull.
* @return string The processed input value
*/
public function stdWrap_ifNull($content = '', $conf = [])
{
return $content ?? $conf['ifNull'];
}
/**
* ifEmpty
* Will set content to a replacement value in case the trimmed value of content returns FALSE
* 0 (zero) will be replaced as well
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for ifEmpty.
* @return string The processed input value
*/
public function stdWrap_ifEmpty($content = '', $conf = [])
{
if (!trim($content)) {
$content = $conf['ifEmpty'];
}
return $content;
}
/**
* ifBlank
* Will set content to a replacement value in case the trimmed value of content has no length
* 0 (zero) will not be replaced
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for ifBlank.
* @return string The processed input value
*/
public function stdWrap_ifBlank($content = '', $conf = [])
{
if (trim($content) === '') {
$content = $conf['ifBlank'];
}
return $content;
}
/**
* listNum
* Gets a value off a CSV list after ifEmpty check
* Might return an empty value in case the CSV does not contain a value at the position given by listNum
* Use preIfEmptyListNum to avoid that behaviour
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for listNum.
* @return string The processed input value
*/
public function stdWrap_listNum($content = '', $conf = [])
{
return $this->listNum($content, $conf['listNum'] ?? null, $conf['listNum.']['splitChar'] ?? null);
}
/**
* trim
* Cuts off any whitespace at the beginning and the end of the content
*
* @param string $content Input value undergoing processing in this function.
* @return string The processed input value
*/
public function stdWrap_trim($content = '')
{
return trim($content);
}
/**
* strPad
* Will return a string padded left/right/on both sides, based on configuration given as stdWrap properties
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for strPad.
* @return string The processed input value
*/
public function stdWrap_strPad($content = '', $conf = [])
{
// Must specify a length in conf for this to make sense
$length = 0;
// Padding with space is PHP-default
$padWith = ' ';
// Padding on the right side is PHP-default
$padType = STR_PAD_RIGHT;
if (!empty($conf['strPad.']['length'])) {
$length = isset($conf['strPad.']['length.']) ? $this->stdWrap($conf['strPad.']['length'], $conf['strPad.']['length.']) : $conf['strPad.']['length'];
$length = (int)$length;
}
if (isset($conf['strPad.']['padWith']) && (string)$conf['strPad.']['padWith'] !== '') {
$padWith = isset($conf['strPad.']['padWith.']) ? $this->stdWrap($conf['strPad.']['padWith'], $conf['strPad.']['padWith.']) : $conf['strPad.']['padWith'];
}
if (!empty($conf['strPad.']['type'])) {
$type = isset($conf['strPad.']['type.']) ? $this->stdWrap($conf['strPad.']['type'], $conf['strPad.']['type.']) : $conf['strPad.']['type'];
if (strtolower($type) === 'left') {
$padType = STR_PAD_LEFT;
} elseif (strtolower($type) === 'both') {
$padType = STR_PAD_BOTH;
}
}
return str_pad($content, $length, $padWith, $padType);
}
/**
* stdWrap
* A recursive call of the stdWrap function set
* This enables the user to execute stdWrap functions in another than the predefined order
* It modifies the content, not the property
* while the new feature of chained stdWrap functions modifies the property and not the content
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for stdWrap.
* @return string The processed input value
*/
public function stdWrap_stdWrap($content = '', $conf = [])
{
return $this->stdWrap($content, $conf['stdWrap.']);
}
/**
* stdWrap process hook
* can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
* this hook executes functions directly after the recursive stdWrap function call but still before the content gets modified
*
* @param string $content Input value undergoing processing in these functions.
* @param array $conf All stdWrap properties, not just the ones for a particular function.
* @return string The processed input value
*/
public function stdWrap_stdWrapProcess($content = '', $conf = [])
{
foreach ($this->stdWrapHookObjects as $hookObject) {
/** @var ContentObjectStdWrapHookInterface $hookObject */
$content = $hookObject->stdWrapProcess($content, $conf, $this);
}
return $content;
}
/**
* required
* Will immediately stop rendering and return an empty value
* when there is no content at this point
*
* @param string $content Input value undergoing processing in this function.
* @return string The processed input value
*/
public function stdWrap_required($content = '')
{
if ((string)$content === '') {
$content = '';
$this->stopRendering[$this->stdWrapRecursionLevel] = true;
}
return $content;
}
/**
* if
* Will immediately stop rendering and return an empty value
* when the result of the checks returns FALSE
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for if.
* @return string The processed input value
*/
public function stdWrap_if($content = '', $conf = [])
{
if (empty($conf['if.']) || $this->checkIf($conf['if.'])) {
return $content;
}
$this->stopRendering[$this->stdWrapRecursionLevel] = true;
return '';
}
/**
* fieldRequired
* Will immediately stop rendering and return an empty value
* when there is no content in the field given by fieldRequired
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for fieldRequired.
* @return string The processed input value
*/
public function stdWrap_fieldRequired($content = '', $conf = [])
{
if (!trim($this->data[$conf['fieldRequired'] ?? null] ?? '')) {
$content = '';
$this->stopRendering[$this->stdWrapRecursionLevel] = true;
}
return $content;
}
/**
* stdWrap csConv: Converts the input to UTF-8
*
* The character set of the input must be specified. Returns the input if
* matters go wrong, for example if an invalid character set is given.
*
* @param string $content The string to convert.
* @param array $conf stdWrap properties for csConv.
* @return string The processed input.
*/
public function stdWrap_csConv($content = '', $conf = [])
{
if (!empty($conf['csConv'])) {
$output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['csConv'])));
return $output !== false && $output !== '' ? $output : $content;
}
return $content;
}
/**
* parseFunc
* Will parse the content based on functions given as stdWrap properties
* Heavily used together with RTE based content
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for parseFunc.
* @return string The processed input value
*/
public function stdWrap_parseFunc($content = '', $conf = [])
{
return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']);
}
/**
* HTMLparser
* Will parse HTML content based on functions given as stdWrap properties
* Heavily used together with RTE based content
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for HTMLparser.
* @return string The processed input value
*/
public function stdWrap_HTMLparser($content = '', $conf = [])
{
if (isset($conf['HTMLparser.']) && is_array($conf['HTMLparser.'])) {
$content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']);
}
return $content;
}
/**
* split
* Will split the content by a given token and treat the results separately
* Automatically fills 'current' with a single result
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for split.
* @return string The processed input value
*/
public function stdWrap_split($content = '', $conf = [])
{
return $this->splitObj($content, $conf['split.']);
}
/**
* replacement
* Will execute replacements on the content (optionally with preg-regex)
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for replacement.
* @return string The processed input value
*/
public function stdWrap_replacement($content = '', $conf = [])
{
return $this->replacement($content, $conf['replacement.']);
}
/**
* prioriCalc
* Will use the content as a mathematical term and calculate the result
* Can be set to 1 to just get a calculated value or 'intval' to get the integer of the result
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for prioriCalc.
* @return string The processed input value
*/
public function stdWrap_prioriCalc($content = '', $conf = [])
{
$content = MathUtility::calculateWithParentheses($content);
if (!empty($conf['prioriCalc']) && $conf['prioriCalc'] === 'intval') {
$content = (int)$content;
}
return $content;
}
/**
* char
* Returns a one-character string containing the character specified by ascii code.
*
* Reliable results only for character codes in the integer range 0 - 127.
*
* @see http://php.net/manual/en/function.chr.php
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for char.
* @return string The processed input value
*/
public function stdWrap_char($content = '', $conf = [])
{
return chr((int)$conf['char']);
}
/**
* intval
* Will return an integer value of the current content
*
* @param string $content Input value undergoing processing in this function.
* @return string The processed input value
*/
public function stdWrap_intval($content = '')
{
return (int)$content;
}
/**
* Will return a hashed value of the current content
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for hash.
* @return string The processed input value
* @link http://php.net/manual/de/function.hash-algos.php for a list of supported hash algorithms
*/
public function stdWrap_hash($content = '', array $conf = [])
{
$algorithm = isset($conf['hash.']) ? $this->stdWrap($conf['hash'], $conf['hash.']) : $conf['hash'];
if (function_exists('hash') && in_array($algorithm, hash_algos())) {
return hash($algorithm, $content);
}
// Non-existing hashing algorithm
return '';
}
/**
* stdWrap_round will return a rounded number with ceil(), floor() or round(), defaults to round()
* Only the english number format is supported . (dot) as decimal point
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for round.
* @return string The processed input value
*/
public function stdWrap_round($content = '', $conf = [])
{
return $this->round($content, $conf['round.']);
}
/**
* numberFormat
* Will return a formatted number based on configuration given as stdWrap properties
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for numberFormat.
* @return string The processed input value
*/
public function stdWrap_numberFormat($content = '', $conf = [])
{
return $this->numberFormat($content, $conf['numberFormat.'] ?? []);
}
/**
* expandList
* Will return a formatted number based on configuration given as stdWrap properties
*
* @param string $content Input value undergoing processing in this function.
* @return string The processed input value
*/
public function stdWrap_expandList($content = '')
{
return GeneralUtility::expandList($content);
}
/**
* date
* Will return a formatted date based on configuration given according to PHP date/gmdate properties
* Will return gmdate when the property GMT returns TRUE
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for date.
* @return string The processed input value
*/
public function stdWrap_date($content = '', $conf = [])
{
// Check for zero length string to mimic default case of date/gmdate.
$content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
$content = !empty($conf['date.']['GMT']) ? gmdate($conf['date'] ?? null, $content) : date($conf['date'] ?? null, $content);
return $content;
}
/**
* strftime
* Will return a formatted date based on configuration given according to PHP strftime/gmstrftime properties
* Will return gmstrftime when the property GMT returns TRUE
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for strftime.
* @return string The processed input value
*/
public function stdWrap_strftime($content = '', $conf = [])
{
// Check for zero length string to mimic default case of strtime/gmstrftime
$content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
$content = (isset($conf['strftime.']['GMT']) && $conf['strftime.']['GMT'])
? gmstrftime($conf['strftime'] ?? null, $content)
: strftime($conf['strftime'] ?? null, $content);
if (!empty($conf['strftime.']['charset'])) {
$output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['strftime.']['charset'])));
return $output ?: $content;
}
return $content;
}
/**
* strtotime
* Will return a timestamp based on configuration given according to PHP strtotime
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for strtotime.
* @return string The processed input value
*/
public function stdWrap_strtotime($content = '', $conf = [])
{
if ($conf['strtotime'] !== '1') {
$content .= ' ' . $conf['strtotime'];
}
return strtotime($content, $GLOBALS['EXEC_TIME']);
}
/**
* age
* Will return the age of a given timestamp based on configuration given by stdWrap properties
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for age.
* @return string The processed input value
*/
public function stdWrap_age($content = '', $conf = [])
{
return $this->calcAge((int)($GLOBALS['EXEC_TIME'] ?? 0) - (int)$content, $conf['age'] ?? null);
}
/**
* case
* Will transform the content to be upper or lower case only
* Leaves HTML tags untouched
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for case.
* @return string The processed input value
*/
public function stdWrap_case($content = '', $conf = [])
{
return $this->HTMLcaseshift($content, $conf['case']);
}
/**
* bytes
* Will return the size of a given number in Bytes *
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for bytes.
* @return string The processed input value
*/
public function stdWrap_bytes($content = '', $conf = [])
{
return GeneralUtility::formatSize($content, $conf['bytes.']['labels'], $conf['bytes.']['base']);
}
/**
* substring
* Will return a substring based on position information given by stdWrap properties
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for substring.
* @return string The processed input value
*/
public function stdWrap_substring($content = '', $conf = [])
{
return $this->substring($content, $conf['substring']);
}
/**
* cropHTML
* Crops content to a given size while leaving HTML tags untouched
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for cropHTML.
* @return string The processed input value
*/
public function stdWrap_cropHTML($content = '', $conf = [])
{
return $this->cropHTML($content, $conf['cropHTML'] ?? '');
}
/**
* stripHtml
* Copmletely removes HTML tags from content
*
* @param string $content Input value undergoing processing in this function.
* @return string The processed input value
*/
public function stdWrap_stripHtml($content = '')
{
return strip_tags($content);
}
/**
* crop
* Crops content to a given size without caring about HTML tags
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for crop.
* @return string The processed input value
*/
public function stdWrap_crop($content = '', $conf = [])
{
return $this->crop($content, $conf['crop']);
}
/**
* rawUrlEncode
* Encodes content to be used within URLs
*
* @param string $content Input value undergoing processing in this function.
* @return string The processed input value
*/
public function stdWrap_rawUrlEncode($content = '')
{
return rawurlencode($content);
}
/**
* htmlSpecialChars
* Transforms HTML tags to readable text by replacing special characters with their HTML entity
* When preserveEntities returns TRUE, existing entities will be left untouched
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for htmlSpecalChars.
* @return string The processed input value
*/
public function stdWrap_htmlSpecialChars($content = '', $conf = [])
{
if (!empty($conf['htmlSpecialChars.']['preserveEntities'])) {
$content = htmlspecialchars($content, ENT_COMPAT, 'UTF-8', false);
} else {
$content = htmlspecialchars($content);
}
return $content;
}
/**
* encodeForJavaScriptValue
* Escapes content to be used inside JavaScript strings. Single quotes are added around the value.
*
* @param string $content Input value undergoing processing in this function
* @return string The processed input value
*/
public function stdWrap_encodeForJavaScriptValue($content = '')
{
return GeneralUtility::quoteJSvalue($content);
}
/**
* doubleBrTag
* Searches for double line breaks and replaces them with the given value
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for doubleBrTag.
* @return string The processed input value
*/
public function stdWrap_doubleBrTag($content = '', $conf = [])
{
return preg_replace('/\R{1,2}[\t\x20]*\R{1,2}/', $conf['doubleBrTag'] ?? null, $content);
}
/**
* br
* Searches for single line breaks and replaces them with a
/
tag
* according to the doctype
*
* @param string $content Input value undergoing processing in this function.
* @return string The processed input value
*/
public function stdWrap_br($content = '')
{
return nl2br($content, !empty($this->getTypoScriptFrontendController()->xhtmlDoctype));
}
/**
* brTag
* Searches for single line feeds and replaces them with the given value
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for brTag.
* @return string The processed input value
*/
public function stdWrap_brTag($content = '', $conf = [])
{
return str_replace(LF, $conf['brTag'] ?? null, $content);
}
/**
* encapsLines
* Modifies text blocks by searching for lines which are not surrounded by HTML tags yet
* and wrapping them with values given by stdWrap properties
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for erncapsLines.
* @return string The processed input value
*/
public function stdWrap_encapsLines($content = '', $conf = [])
{
return $this->encaps_lineSplit($content, $conf['encapsLines.']);
}
/**
* keywords
* Transforms content into a CSV list to be used i.e. as keywords within a meta tag
*
* @param string $content Input value undergoing processing in this function.
* @return string The processed input value
*/
public function stdWrap_keywords($content = '')
{
return $this->keywords($content);
}
/**
* innerWrap
* First of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
* See wrap
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for innerWrap.
* @return string The processed input value
*/
public function stdWrap_innerWrap($content = '', $conf = [])
{
return $this->wrap($content, $conf['innerWrap'] ?? null);
}
/**
* innerWrap2
* Second of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
* See wrap
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for innerWrap2.
* @return string The processed input value
*/
public function stdWrap_innerWrap2($content = '', $conf = [])
{
return $this->wrap($content, $conf['innerWrap2'] ?? null);
}
/**
* addParams
* Adds tag attributes to any content that is a tag
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for addParams.
* @return string The processed input value
* @deprecated since TYPO3 v9.5, will be removed in TYPO3 v10.0.
*/
public function stdWrap_addParams($content = '', $conf = [])
{
return $this->addParams($content, $conf['addParams.'] ?? [], true);
}
/**
* filelink
* Used to make lists of links to files
* See wrap
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for filelink.
* @return string The processed input value
* @deprecated since TYPO3 v9.5, will be removed in TYPO3 v10.0. Use cObject FILES instead.
*/
public function stdWrap_filelink($content = '', $conf = [])
{
return $this->filelink($content, $conf['filelink.'] ?? [], true);
}
/**
* preCObject
* A content object that is prepended to the current content but between the innerWraps and the rest of the wraps
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for preCObject.
* @return string The processed input value
*/
public function stdWrap_preCObject($content = '', $conf = [])
{
return $this->cObjGetSingle($conf['preCObject'], $conf['preCObject.'], '/stdWrap/.preCObject') . $content;
}
/**
* postCObject
* A content object that is appended to the current content but between the innerWraps and the rest of the wraps
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for postCObject.
* @return string The processed input value
*/
public function stdWrap_postCObject($content = '', $conf = [])
{
return $content . $this->cObjGetSingle($conf['postCObject'], $conf['postCObject.'], '/stdWrap/.postCObject');
}
/**
* wrapAlign
* Wraps content with a div container having the style attribute text-align set to the given value
* See wrap
*
* @param string $content Input value undergoing processing in this function.
* @param array $conf stdWrap properties for wrapAlign.
* @return string The processed input value
*/
public function stdWrap_wrapAlign($content = '', $conf = [])
{
$wrapAlign = trim($conf['wrapAlign'] ?? '');
if ($wrapAlign) {
$content = $this->wrap($content, '
' . htmlspecialchars($content) . ''; } /** * debugFunc * Will output the content in a debug table * * @param string $content Input value undergoing processing in this function. * @param array $conf stdWrap properties for debugFunc. * @return string The processed input value */ public function stdWrap_debugFunc($content = '', $conf = []) { debug((int)$conf['debugFunc'] === 2 ? [$content] : $content); return $content; } /** * debugData * Will output the data used by the current record in a debug table * * @param string $content Input value undergoing processing in this function. * @return string The processed input value */ public function stdWrap_debugData($content = '') { debug($this->data, '$cObj->data:'); if (is_array($this->alternativeData)) { debug($this->alternativeData, '$this->alternativeData'); } return $content; } /** * Returns number of rows selected by the query made by the properties set. * Implements the stdWrap "numRows" property * * @param array $conf TypoScript properties for the property (see link to "numRows") * @return int The number of rows found by the select * @internal * @see stdWrap() */ public function numRows($conf) { $conf['select.']['selectFields'] = 'count(*)'; $statement = $this->exec_getQuery($conf['table'], $conf['select.']); return (int)$statement->fetchColumn(0); } /** * Exploding a string by the $char value (if integer its an ASCII value) and returning index $listNum * * @param string $content String to explode * @param string $listNum Index-number. You can place the word "last" in it and it will be substituted with the pointer to the last value. You can use math operators like "+-/*" (passed to calc()) * @param string $char Either a string used to explode the content string or an integer value which will then be changed into a character, eg. "10" for a linebreak char. * @return string */ public function listNum($content, $listNum, $char) { $char = $char ?: ','; if (MathUtility::canBeInterpretedAsInteger($char)) { $char = chr($char); } $temp = explode($char, $content); $last = '' . (count($temp) - 1); // Take a random item if requested if ($listNum === 'rand') { $listNum = rand(0, count($temp) - 1); } $index = $this->calc(str_ireplace('last', $last, $listNum)); return $temp[$index]; } /** * Compares values together based on the settings in the input TypoScript array and returns the comparison result. * Implements the "if" function in TYPO3 TypoScript * * @param array $conf TypoScript properties defining what to compare * @return bool * @see stdWrap(), _parseFunc() */ public function checkIf($conf) { if (!is_array($conf)) { return true; } if (isset($conf['directReturn'])) { return (bool)$conf['directReturn']; } $flag = true; if (isset($conf['isNull.'])) { $isNull = $this->stdWrap('', $conf['isNull.']); if ($isNull !== null) { $flag = false; } } if (isset($conf['isTrue']) || isset($conf['isTrue.'])) { $isTrue = isset($conf['isTrue.']) ? trim($this->stdWrap($conf['isTrue'], $conf['isTrue.'])) : trim($conf['isTrue']); if (!$isTrue) { $flag = false; } } if (isset($conf['isFalse']) || isset($conf['isFalse.'])) { $isFalse = isset($conf['isFalse.']) ? trim($this->stdWrap($conf['isFalse'], $conf['isFalse.'])) : trim($conf['isFalse']); if ($isFalse) { $flag = false; } } if (isset($conf['isPositive']) || isset($conf['isPositive.'])) { $number = isset($conf['isPositive.']) ? $this->calc($this->stdWrap($conf['isPositive'], $conf['isPositive.'])) : $this->calc($conf['isPositive']); if ($number < 1) { $flag = false; } } if ($flag) { $value = isset($conf['value.']) ? trim($this->stdWrap($conf['value'] ?? '', $conf['value.'])) : trim($conf['value'] ?? ''); if (isset($conf['isGreaterThan']) || isset($conf['isGreaterThan.'])) { $number = isset($conf['isGreaterThan.']) ? trim($this->stdWrap($conf['isGreaterThan'], $conf['isGreaterThan.'])) : trim($conf['isGreaterThan']); if ($number <= $value) { $flag = false; } } if (isset($conf['isLessThan']) || isset($conf['isLessThan.'])) { $number = isset($conf['isLessThan.']) ? trim($this->stdWrap($conf['isLessThan'], $conf['isLessThan.'])) : trim($conf['isLessThan']); if ($number >= $value) { $flag = false; } } if (isset($conf['equals']) || isset($conf['equals.'])) { $number = isset($conf['equals.']) ? trim($this->stdWrap($conf['equals'], $conf['equals.'])) : trim($conf['equals']); if ($number != $value) { $flag = false; } } if (isset($conf['isInList']) || isset($conf['isInList.'])) { $number = isset($conf['isInList.']) ? trim($this->stdWrap($conf['isInList'], $conf['isInList.'])) : trim($conf['isInList']); if (!GeneralUtility::inList($value, $number)) { $flag = false; } } } if ($conf['negate'] ?? false) { $flag = !$flag; } return $flag; } /** * Reads a directory for files and returns the filepaths in a string list separated by comma. * Implements the stdWrap property "filelist" * * @param string $data The command which contains information about what files/directory listing to return. See the "filelist" property of stdWrap for details. * @param bool $isCoreCall if set, the deprecation message is suppressed * @return string Comma list of files. * @internal * @see stdWrap() * @deprecated since TYPO3 v9.5, will be removed in TYPO3 v10.0. Use cObject FILES instead. */ public function filelist($data, bool $isCoreCall = false) { if (!$isCoreCall) { trigger_error('ContentObjectRenderer->filelist() will be removed in TYPO3 v10.0. Use cObject FILES instead.', E_USER_DEPRECATED); } $data = trim($data); if ($data === '') { return ''; } list($possiblePath, $ext_list, $sorting, $reverse, $useFullPath) = GeneralUtility::trimExplode('|', $data); // read directory: // MUST exist! $path = ''; // proceeds if no '//', '..' or '\' is in the $theFile if (GeneralUtility::validPathStr($possiblePath)) { // Removes all dots, slashes and spaces after a path. $possiblePath = preg_replace('/[\\/\\. ]*$/', '', $possiblePath); if (!GeneralUtility::isAbsPath($possiblePath) && @is_dir($possiblePath)) { // Now check if it matches one of the FAL storages $storageRepository = GeneralUtility::makeInstance(StorageRepository::class); $storages = $storageRepository->findAll(); foreach ($storages as $storage) { if ($storage->getDriverType() === 'Local' && $storage->isPublic() && $storage->isOnline()) { $folder = $storage->getPublicUrl($storage->getRootLevelFolder(), true); if (GeneralUtility::isFirstPartOfStr($possiblePath . '/', $folder)) { $path = $possiblePath; break; } } } } } if (!$path) { return ''; } $items = [ 'files' => [], 'sorting' => [] ]; $ext_list = strtolower(GeneralUtility::uniqueList($ext_list)); // Read dir: $d = @dir($path); if (is_object($d)) { $count = 0; while ($entry = $d->read()) { if ($entry !== '.' && $entry !== '..') { // Because of odd PHP-error where
', '
' . LF . '', $colParts[$kkk]);
}
$colParts[$kkk] = $this->parseFunc($colParts[$kkk], $conf);
}
$tagStdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.'])
? $cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.']
: $cfg['HTMLtableCells.']['default.']['tagStdWrap.'];
if (is_array($tagStdWrap)) {
$tag = $this->stdWrap($tag, $tagStdWrap);
}
$stdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['stdWrap.'])
? $cfg['HTMLtableCells.'][$cc . '.']['stdWrap.']
: $cfg['HTMLtableCells.']['default.']['stdWrap.'];
if (is_array($stdWrap)) {
$colParts[$kkk] = $this->stdWrap($colParts[$kkk], $stdWrap);
}
$colParts[$kkk] = $tag . $colParts[$kkk] . '' . $tagName . '>';
}
}
$rowParts[$kk] = implode('', $colParts);
}
}
$parts[$k] = implode('', $rowParts);
}
if (is_array($cfg['stdWrap.'])) {
$parts[$k] = $this->stdWrap($parts[$k], $cfg['stdWrap.']);
}
} else {
$parts[$k] = $this->_parseFunc($parts[$k], $conf);
}
}
$result = implode('', $parts);
if ($conf['htmlSanitize']) {
$result = $this->stdWrap_htmlSanitize($result, $conf['htmlSanitize.'] ?? []);
}
return $result;
}
/**
* Helper function for parseFunc()
*
* @param string $theValue The value to process.
* @param array $conf TypoScript configuration for parseFunc
* @return string The processed value
* @internal
* @see parseFunc()
*/
public function _parseFunc($theValue, $conf)
{
if (!empty($conf['if.']) && !$this->checkIf($conf['if.'])) {
return $theValue;
}
// Indicates that the data is from within a tag.
$inside = false;
// Pointer to the total string position
$pointer = 0;
// Loaded with the current typo-tag if any.
$currentTag = '';
$stripNL = 0;
$contentAccum = [];
$contentAccumP = 0;
$allowTags = strtolower(str_replace(' ', '', $conf['allowTags'] ?? ''));
$denyTags = strtolower(str_replace(' ', '', $conf['denyTags'] ?? ''));
$totalLen = strlen($theValue);
do {
if (!$inside) {
if (!is_array($currentTag)) {
// These operations should only be performed on code outside the typotags...
// data: this checks that we enter tags ONLY if the first char in the tag is alphanumeric OR '/'
$len_p = 0;
$c = 100;
do {
$len = strcspn(substr($theValue, $pointer + $len_p), '<');
$len_p += $len + 1;
$endChar = ord(strtolower(substr($theValue, $pointer + $len_p, 1)));
$c--;
} while ($c > 0 && $endChar && ($endChar < 97 || $endChar > 122) && $endChar != 47);
$len = $len_p - 1;
} else {
// If we're inside a currentTag, just take it to the end of that tag!
$tempContent = strtolower(substr($theValue, $pointer));
$len = strpos($tempContent, '' . $currentTag[0]);
if (is_string($len) && !$len) {
$len = strlen($tempContent);
}
}
// $data is the content until the next